Files
craft-libs/crates/prc/src/bits.rs
T
Dylan M. Taylor 7db4645d1f Add craft-prc: the PRC 3D model reader from PdfCraft (#945)
PdfCraft's pdfcraft-prc crate (revision
80e26e674d1aa33078e83c8139875047f3289eb6), moved whole with its tests. It reads
PRC (ISO 14739-1), the model format of PDF 3D annotations: the container, the
bit-level primitives, the schema, tessellations and the product tree.

The code is unchanged. The package and its README are renamed, the doctests
and tests import craft_prc, and the Double code table taken from prc-rs (MIT)
has its row in ATTRIBUTION.md and its licence in licenses/prc-rs.
2026-11-11 18:55:20 -05:00

492 lines
18 KiB
Rust

//! The bit-level reader for PRC compressed sections [WD 10.3, 11.1-11.17].
//!
//! Values are consumed MSB-first from each inflated byte and straddle byte boundaries freely
//! [WD 10.3.1, 11.3]. Multi-byte integers are groups of eight bits, low group first. Two sign
//! conventions coexist: `Integer` is two's complement, the `*WithVariableBitNumber` types are
//! sign-magnitude.
use crate::PrcError;
use crate::acof::{self, Entry};
/// The most array elements the readers of one PRC stream decode together (2^24): at most 8
/// bytes each, so 128 MiB.
pub const MAX_DECODED_ELEMENTS: usize = 1 << 24;
/// The most schema-program steps (tokens executed, loop iterations included) the readers of
/// one PRC stream run together.
pub const MAX_SCHEMA_STEPS: u64 = 1 << 22;
/// What the readers of one stream may still spend, carried from each reader to the next.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Budget {
/// Array elements still to decode.
pub(crate) elements: usize,
/// Schema-program steps still to run.
pub(crate) steps: u64,
}
impl Budget {
/// A whole stream's allowance.
pub(crate) const FULL: Self = Self { elements: MAX_DECODED_ELEMENTS, steps: MAX_SCHEMA_STEPS };
}
/// A cursor over one inflated section.
#[derive(Debug, Clone)]
pub struct BitReader<'a> {
data: &'a [u8],
pos: usize,
budget: Budget,
}
impl<'a> BitReader<'a> {
/// A reader positioned at the first bit of `data`, allowed [`MAX_DECODED_ELEMENTS`] and
/// [`MAX_SCHEMA_STEPS`].
pub fn new(data: &'a [u8]) -> Self {
Self::with_budget(data, Budget::FULL)
}
/// A reader of `data` allowed `budget`: what an earlier reader of the same stream left.
pub(crate) fn with_budget(data: &'a [u8], budget: Budget) -> Self {
Self { data, pos: 0, budget }
}
/// A reader of `data` allowed `elements` array elements and a full step allowance.
#[cfg(test)]
pub(crate) fn with_elements(data: &'a [u8], elements: usize) -> Self {
Self::with_budget(data, Budget { elements, ..Budget::FULL })
}
/// This reader, at its position, allowed `budget` instead: what readers of the same
/// stream left since it stopped.
pub(crate) fn with_remaining(mut self, budget: Budget) -> Self {
self.budget = budget;
self
}
/// What this reader may still spend.
pub(crate) fn budget(&self) -> Budget {
self.budget
}
/// Array elements this reader may still decode.
#[cfg(test)]
pub(crate) fn elements_left(&self) -> usize {
self.budget.elements
}
/// Charge `n` decoded array elements against the allowance.
///
/// # Errors
/// [`PrcError::TooManyElements`] when fewer than `n` are left.
pub(crate) fn take_elements(&mut self, n: usize) -> Result<(), PrcError> {
self.budget.elements = self.budget.elements.checked_sub(n).ok_or(PrcError::TooManyElements { limit: MAX_DECODED_ELEMENTS })?;
Ok(())
}
/// Charge one schema-program step; false when none is left.
pub(crate) fn take_step(&mut self) -> bool {
match self.budget.steps.checked_sub(1) {
Some(left) => {
self.budget.steps = left;
true
}
None => false,
}
}
/// Bits consumed so far.
pub fn position(&self) -> usize {
self.pos
}
/// Bits left before the end of the data.
pub fn remaining(&self) -> usize {
self.data.len().saturating_mul(8).saturating_sub(self.pos)
}
/// Step over `n` bits of opaque data (`UserData` [WD 8.6]).
///
/// # Errors
/// [`PrcError::Truncated`] when fewer than `n` bits remain; the cursor does not move.
pub fn skip_bits(&mut self, n: usize) -> Result<(), PrcError> {
if n > self.remaining() {
return Err(PrcError::Truncated("user data"));
}
self.pos = self.pos.saturating_add(n);
Ok(())
}
/// One bit; a PRC `Boolean` [WD 10.3.3].
///
/// # Errors
/// [`PrcError::Truncated`] at the end of the data.
pub fn bit(&mut self) -> Result<bool, PrcError> {
let byte = self.data.get(self.pos / 8).ok_or(PrcError::Truncated("a bit"))?;
let b = (byte >> (7 - (self.pos % 8))) & 1 == 1;
// `pos / 8` indexed the data, so `pos + 1` is at most `8 * len`.
self.pos = self.pos.saturating_add(1);
Ok(b)
}
/// `n` bits (at most 32), MSB-first; `UnsignedIntegerWithVariableBitNumber` [WD 11.13].
///
/// # Errors
/// [`PrcError::Truncated`] at the end of the data; [`PrcError::Malformed`] for `n > 32`.
pub fn bits(&mut self, n: u32) -> Result<u32, PrcError> {
if n > 32 {
return Err(PrcError::Malformed(format!("{n}-bit field")));
}
if self.remaining() < n as usize {
return Err(PrcError::Truncated("a bit field"));
}
let mut v: u64 = 0;
for _ in 0..n {
v = (v << 1) | u64::from(self.bit()?);
}
// n <= 32, so v fits.
Ok(u32::try_from(v).unwrap_or(u32::MAX))
}
fn byte(&mut self) -> Result<u8, PrcError> {
Ok(u8::try_from(self.bits(8)?).unwrap_or(u8::MAX))
}
/// A `Character`: one byte [WD 10.3.4].
///
/// # Errors
/// [`PrcError::Truncated`].
pub fn character(&mut self) -> Result<u8, PrcError> {
self.byte()
}
/// An `UnsignedInteger` [WD 11.10]: while a 1 bit precedes it, one more eight-bit group,
/// low group first.
///
/// # Errors
/// [`PrcError::Truncated`]; [`PrcError::Malformed`] past four groups.
pub fn unsigned_integer(&mut self) -> Result<u32, PrcError> {
let mut v: u32 = 0;
let mut shift = 0;
while self.bit()? {
if shift >= 32 {
return Err(PrcError::Malformed("UnsignedInteger over 32 bits".into()));
}
v |= u32::from(self.byte()?) << shift;
shift += 8;
}
Ok(v)
}
/// An `Integer` [WD 11.11]: a 0 bit is zero; otherwise eight-bit groups, low group first,
/// each followed by a continue bit, sign-extended from the last group's top bit.
///
/// # Errors
/// [`PrcError::Truncated`]; [`PrcError::Malformed`] past four groups.
pub fn integer(&mut self) -> Result<i32, PrcError> {
if !self.bit()? {
return Ok(0);
}
let mut v: u32 = 0;
let mut shift = 0;
loop {
if shift >= 32 {
return Err(PrcError::Malformed("Integer over 32 bits".into()));
}
let group = self.byte()?;
v |= u32::from(group) << shift;
shift += 8;
if !self.bit()? {
if group & 0x80 != 0 && shift < 32 {
v |= u32::MAX << shift;
}
return Ok(v.cast_signed());
}
}
}
/// An `IntegerWithVariableBitNumber(n)` [WD 11.12]: a negative flag (1 = negative; the WD
/// prose says the opposite, its sample code and real files agree on this), then the
/// magnitude in `n - 1` bits.
///
/// # Errors
/// [`PrcError::Truncated`]; [`PrcError::Malformed`] for `n > 33`.
pub fn integer_vbn(&mut self, n: u32) -> Result<i64, PrcError> {
let Some(magnitude_bits) = n.checked_sub(1) else {
return Ok(0);
};
let negative = self.bit()?;
let m = i64::from(self.bits(magnitude_bits)?);
Ok(if negative { -m } else { m })
}
/// A `DoubleWithVariableBitNumber(tolerance, n)` [WD 11.14]: a sign-magnitude quantum
/// count times `tolerance`.
///
/// # Errors
/// As [`Self::integer_vbn`].
pub fn double_vbn(&mut self, tolerance: f64, n: u32) -> Result<f64, PrcError> {
Ok(self.integer_vbn(n)? as f64 * tolerance)
}
/// `NumberOfBitsThenUnsignedInteger` [WD 11.15]: a 5-bit width, then the value in that
/// many bits.
///
/// # Errors
/// [`PrcError::Truncated`].
pub fn nbits_then_unsigned(&mut self) -> Result<u32, PrcError> {
let n = self.bits(5)?;
self.bits(n)
}
/// A `String` [WD 10.3.7, 11.4]: `None` for the null string, else UTF-8 (invalid
/// sequences replaced).
///
/// # Errors
/// [`PrcError::Truncated`] when the byte count runs past the data.
pub fn string(&mut self) -> Result<Option<String>, PrcError> {
if !self.bit()? {
return Ok(None);
}
let len = self.unsigned_integer()? as usize;
if len.saturating_mul(8) > self.remaining() {
return Err(PrcError::Truncated("a String"));
}
let bytes = (0..len).map(|_| self.byte()).collect::<Result<Vec<u8>, _>>()?;
Ok(Some(String::from_utf8_lossy(&bytes).into_owned()))
}
/// `FloatAsBytes` [WD 11.5]: an IEEE binary32, least-significant byte first.
///
/// # Errors
/// [`PrcError::Truncated`].
pub fn float_as_bytes(&mut self) -> Result<f32, PrcError> {
let b = [self.byte()?, self.byte()?, self.byte()?, self.byte()?];
Ok(f32::from_le_bytes(b))
}
/// A `Double` [WD 11.17]: a prefix code from the Double table, then a sign bit and, for
/// an exponent code, a byte-wise mantissa with back-references.
///
/// The two-bit zero code has no sign bit. Back-reference offsets 1 to 5 are all accepted,
/// distance 1 included (PDF Association pdf-issues #769).
///
/// # Errors
/// [`PrcError::UnknownDoubleCode`], [`PrcError::Truncated`], or [`PrcError::Malformed`]
/// for a back-reference outside the value.
pub fn double(&mut self) -> Result<f64, PrcError> {
// prc-rs: the decoding steps (code lookup, sign, exponent, byte-wise mantissa with
// back-references) follow prc-rs's.
let mut code = 0u32;
let mut entry = None;
for n in 1..=acof::MAX_CODE_BITS {
code = (code << 1) | u32::from(self.bit()?);
if let Some(e) = acof::lookup(n, code) {
if n == 2 && code == 0b01 {
return Ok(0.0);
}
entry = Some(e);
break;
}
}
let entry = entry.ok_or(PrcError::UnknownDoubleCode)?;
let negative = self.bit()?;
let upper = match entry {
Entry::Double(v) => return Ok(if negative { -v } else { v }),
Entry::Exponent(upper) => upper,
};
let mut b = (u64::from(upper) << 32).to_le_bytes();
if self.bit()? {
self.mantissa(&mut b)?;
}
let sign = if negative { 1u64 << 63 } else { 0 };
Ok(f64::from_bits(u64::from_le_bytes(b) | sign))
}
/// Mantissa bits 51..0 into `b` (little-endian byte order): a nibble, then bytes 5 down
/// to 0, each a literal or a back-reference to a byte already set.
fn mantissa(&mut self, b: &mut [u8; 8]) -> Result<(), PrcError> {
let nibble = u8::try_from(self.bits(4)?).unwrap_or(0);
let [.., b6, _] = b;
*b6 |= nibble;
let at = |b: &[u8; 8], i: usize| b.get(i).copied().ok_or_else(|| PrcError::Malformed("Double back-reference past the value".into()));
for cbi in (0..=5usize).rev() {
let prev = at(b, cbi + 1)?;
let value = if self.bit()? {
self.byte()?
} else {
match self.bits(3)? {
// Fill this byte and every lower one with the previous byte.
0 => {
b.iter_mut().take(cbi + 1).for_each(|x| *x = prev);
return Ok(());
}
// As 0, but the lowest byte is a literal.
6 => {
b.iter_mut().take(cbi + 1).skip(1).for_each(|x| *x = prev);
let low = self.byte()?;
let [b0, ..] = b;
*b0 = low;
return Ok(());
}
off @ 1..=5 => at(b, cbi + off as usize)?,
_ => return Err(PrcError::Malformed("Double mantissa offset 7".into())),
}
};
if let Some(x) = b.get_mut(cbi) {
*x = value;
}
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::testw::W;
/// The code for IEEE exponent `e`.
fn exponent_code(e: u32) -> (u32, u32) {
acof::exponent_code(e).expect("the table has a row per exponent")
}
fn read_double(w: &W) -> f64 {
BitReader::new(&w.bytes()).double().unwrap()
}
#[test]
fn zero_is_the_two_bit_code_with_no_sign() {
let mut w = W::default();
w.put(0b01, 2).put(0b1111_1111, 8);
let bytes = w.bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(r.double().unwrap(), 0.0);
assert_eq!(r.position(), 2);
}
#[test]
fn exponent_with_literal_mantissa() {
let v = -1234.5678f64;
let le = v.to_bits().to_le_bytes();
let e = u32::try_from((v.to_bits() >> 52) & 0x7ff).unwrap();
let (n, c) = exponent_code(e);
let mut w = W::default();
w.put(u64::from(c), n).put(1, 1).put(1, 1).put(u64::from(le[6] & 0x0f), 4);
for byte in le[..6].iter().rev() {
w.put(1, 1).put(u64::from(*byte), 8);
}
assert_eq!(read_double(&w), v);
}
#[test]
fn exponent_without_mantissa_is_a_power_of_two() {
let (n, c) = exponent_code(1023 + 3);
let mut w = W::default();
w.put(u64::from(c), n).put(0, 1).put(0, 1);
assert_eq!(read_double(&w), 8.0);
}
#[test]
fn fill_back_reference_and_literal_low_byte() {
// Mantissa bytes 6..0 = 0x05, 0xAA, 0xAA (offset 1), 0xAA (offset 2), fill 0xAA to byte 1, byte 0 = 0x11.
let (n, c) = exponent_code(1023);
let mut w = W::default();
w.put(u64::from(c), n).put(0, 1).put(1, 1).put(0x5, 4);
w.put(1, 1).put(0xAA, 8);
w.put(0, 1).put(1, 3);
w.put(0, 1).put(2, 3);
w.put(0, 1).put(6, 3).put(0x11, 8);
assert_eq!(read_double(&w), f64::from_le_bytes([0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xF5, 0x3F]));
// Byte 5 literal, then fill 4..0.
let mut w = W::default();
w.put(u64::from(c), n).put(0, 1).put(1, 1).put(0x5, 4);
w.put(1, 1).put(0x77, 8).put(0, 1).put(0, 3);
assert_eq!(read_double(&w), f64::from_le_bytes([0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xF5, 0x3F]));
}
#[test]
fn a_back_reference_past_the_value_is_refused() {
let (n, c) = exponent_code(1023);
let mut w = W::default();
w.put(u64::from(c), n).put(0, 1).put(1, 1).put(0, 4);
w.put(0, 1).put(5, 3).put(0, 16); // byte 5 + offset 5 = byte 10
assert!(matches!(BitReader::new(&w.bytes()).double(), Err(PrcError::Malformed(_))));
}
#[test]
fn a_truncated_double_is_an_error_not_a_panic() {
// No code is 8 bits or shorter and a prefix of 0x17, so one byte ends inside a code.
assert!(matches!(BitReader::new(&[0x17]).double(), Err(PrcError::Truncated(_))));
assert!(matches!(BitReader::new(&[]).double(), Err(PrcError::Truncated(_))));
}
#[test]
fn integers_and_strings() {
let mut w = W::default();
// UnsignedInteger 300 = groups 0x2C, 0x01.
w.put(1, 1).put(0x2C, 8).put(1, 1).put(0x01, 8).put(0, 1);
// Integer -2 = group 0xFE, stop.
w.put(1, 1).put(0xFE, 8).put(0, 1);
// Integer 200 = 0xC8 then 0x00 (top bit clear), stop.
w.put(1, 1).put(0xC8, 8).put(1, 1).put(0x00, 8).put(0, 1);
// IntegerWithVariableBitNumber(4) = -5.
w.put(1, 1).put(5, 3);
// String "hé".
w.put(1, 1).put(1, 1).put(3, 8).put(0, 1).put(0x68, 8).put(0xC3, 8).put(0xA9, 8);
// Null string.
w.put(0, 1);
let bytes = w.bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(r.unsigned_integer().unwrap(), 300);
assert_eq!(r.integer().unwrap(), -2);
assert_eq!(r.integer().unwrap(), 200);
assert_eq!(r.integer_vbn(4).unwrap(), -5);
assert_eq!(r.string().unwrap().as_deref(), Some("hé"));
assert_eq!(r.string().unwrap(), None);
}
#[test]
fn the_writer_round_trips_through_the_reader() {
let mut w = W::default();
w.uint(0).uint(u32::MAX).int(-129).int(i32::MIN).int(i32::MAX).string(Some("glass")).string(None);
let bytes = w.bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(r.unsigned_integer().unwrap(), 0);
assert_eq!(r.unsigned_integer().unwrap(), u32::MAX);
assert_eq!(r.integer().unwrap(), -129);
assert_eq!(r.integer().unwrap(), i32::MIN);
assert_eq!(r.integer().unwrap(), i32::MAX);
assert_eq!(r.string().unwrap().as_deref(), Some("glass"));
assert_eq!(r.string().unwrap(), None);
}
#[test]
fn oversized_groups_and_widths_are_malformed() {
// Five UnsignedInteger groups.
let mut w = W::default();
(0..5).for_each(|_| {
w.put(1, 1).put(0xFF, 8);
});
w.put(0, 1);
assert!(matches!(BitReader::new(&w.bytes()).unsigned_integer(), Err(PrcError::Malformed(_))));
assert!(matches!(BitReader::new(&[0; 8]).bits(33), Err(PrcError::Malformed(_))));
assert!(matches!(BitReader::new(&[0; 8]).integer_vbn(34), Err(PrcError::Malformed(_))));
assert_eq!(BitReader::new(&[]).integer_vbn(0), Ok(0));
}
#[test]
fn truncation_is_an_error_not_a_panic() {
let mut r = BitReader::new(&[0xFF]);
assert!(r.unsigned_integer().is_err());
let mut r = BitReader::new(&[0xC0, 0x40]);
assert!(matches!(r.string(), Err(PrcError::Truncated(_))));
let mut r = BitReader::new(&[0xAB]);
assert!(matches!(r.skip_bits(9), Err(PrcError::Truncated(_))));
assert_eq!(r.position(), 0, "a refused skip does not move the cursor");
r.skip_bits(8).unwrap();
assert_eq!(r.remaining(), 0);
}
}