mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 21:11:52 +00:00
Read the tessellation section of a PRC file structure: FileStructure::tessellations() returns each entity as a Tessellation. - TESS_3D becomes a TriangleMesh: positions, triangles from the triangle/fan/strip blocks (one-normal and textured variants included), one triangle range per face, stored normals and texture coordinates with per-corner indices, and per-triangle line-attribute styles. - TESS_3D_Wire becomes polylines (closing and continuous flags); vertex colours are read past, sized by points (closing point included). - TESS_Markup is read past. - TESS_3D_Compressed is read field by field to its end so the entities after it decode; its triangles are not rebuilt (later step). - The compressed Character/Short/Integer/Indice arrays and the Huffman container they use (WD 11.6-11.9, 12.2) live in arrays.rs; the base fields every entity shares (attributes, name, user data) in entity.rs. Never-panic hardening beyond the source this is adapted from: - Huffman symbol width must be 1..=16; the leaf table and element count are bounded by the payload before anything is allocated. - A face may carry at most 8 texture-coordinate sets (an unbounded count allocated per set and padded per triangle). - Every count is checked against the remaining bits; indice sums and deltas use checked arithmetic. A few reading choices (Huffman bit order and code-length width, indice running sums, 64-bit attribute order, three version gates, the is_segment_color bit, the compressed field order) follow prc-rs (MIT); each is marked `// prc-rs:` in the source. Tests are synthetic, written with the crate's test bit writer. Each fix was reverted and its test seen failing (17 reversions). Adapted from pdfcer (MIT, same author), pdfcer-3d at 0c9742b5936232af89cd707138ef149dc1f4a660. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
167 lines
5.7 KiB
Rust
167 lines
5.7 KiB
Rust
//! Test-only PRC bit writer: the inverse of [`crate::BitReader`].
|
|
|
|
#![allow(clippy::expect_used)]
|
|
|
|
use crate::acof;
|
|
|
|
/// An MSB-first bit sink for building test streams.
|
|
#[derive(Debug, Default, Clone)]
|
|
pub(crate) struct W {
|
|
bits: Vec<bool>,
|
|
}
|
|
|
|
impl W {
|
|
/// The low `n` bits of `v` (`n` ≤ 64), most significant first.
|
|
pub(crate) fn put(&mut self, v: u64, n: u32) -> &mut Self {
|
|
for i in (0..n).rev() {
|
|
self.bits.push((v >> i) & 1 == 1);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// One bit; a PRC `Boolean`.
|
|
pub(crate) fn bit(&mut self, b: bool) -> &mut Self {
|
|
self.bits.push(b);
|
|
self
|
|
}
|
|
|
|
/// Bits written so far.
|
|
pub(crate) fn len(&self) -> usize {
|
|
self.bits.len()
|
|
}
|
|
|
|
/// Every bit of `other`, unaligned.
|
|
pub(crate) fn append(&mut self, other: &Self) -> &mut Self {
|
|
self.bits.extend_from_slice(&other.bits);
|
|
self
|
|
}
|
|
|
|
/// An `UnsignedInteger` [WD 11.10].
|
|
pub(crate) fn uint(&mut self, mut v: u32) -> &mut Self {
|
|
while v != 0 {
|
|
self.bit(true).put(u64::from(v & 0xff), 8);
|
|
v >>= 8;
|
|
}
|
|
self.bit(false)
|
|
}
|
|
|
|
/// An `Integer` [WD 11.11]: two's-complement groups, low first, each followed by a
|
|
/// continue bit.
|
|
pub(crate) fn int(&mut self, v: i32) -> &mut Self {
|
|
if v == 0 {
|
|
return self.bit(false);
|
|
}
|
|
self.bit(true);
|
|
let mut v = i64::from(v);
|
|
loop {
|
|
let group = v & 0xff;
|
|
self.put(group.cast_unsigned(), 8);
|
|
v >>= 8;
|
|
let done = (v == 0 && group & 0x80 == 0) || (v == -1 && group & 0x80 != 0);
|
|
self.bit(!done);
|
|
if done {
|
|
return self;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A `String` [WD 11.4]: presence bit, byte count, raw UTF-8 bytes.
|
|
pub(crate) fn string(&mut self, s: Option<&str>) -> &mut Self {
|
|
let Some(s) = s else {
|
|
return self.bit(false);
|
|
};
|
|
self.bit(true).uint(u32::try_from(s.len()).expect("a short test string"));
|
|
for b in s.bytes() {
|
|
self.put(u64::from(b), 8);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// A finite `Double` [WD 11.17]: zero as the two-bit zero code; anything else as its
|
|
/// exponent row, the sign, then the mantissa as literal bytes.
|
|
pub(crate) fn double(&mut self, v: f64) -> &mut Self {
|
|
if v == 0.0 {
|
|
return self.put(0b01, 2);
|
|
}
|
|
let bits = v.to_bits();
|
|
let exponent = u32::try_from((bits >> 52) & 0x7ff).expect("11 bits");
|
|
let (n, c) = acof::exponent_code(exponent).expect("the Double table has a row per exponent");
|
|
let le = bits.to_le_bytes();
|
|
self.put(u64::from(c), n).bit(v < 0.0).bit(true).put(u64::from(le[6] & 0x0f), 4);
|
|
for b in le[..6].iter().rev() {
|
|
self.bit(true).put(u64::from(*b), 8);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// A Huffman container [WD 12.2]: `leaves` are `(symbol, stored code, length)`, each value
|
|
/// is emitted as its leaf's code.
|
|
pub(crate) fn huffman(&mut self, nbits: u32, len_width: u32, leaves: &[(u32, u32, u32)], values: &[i32]) -> &mut Self {
|
|
let mask = (1u32 << nbits) - 1;
|
|
let codes: Vec<(u32, u32)> = values
|
|
.iter()
|
|
.map(|&v| {
|
|
let l = leaves.iter().find(|l| l.0 & mask == v.cast_unsigned() & mask).expect("a leaf per value");
|
|
(l.1, l.2)
|
|
})
|
|
.collect();
|
|
self.huffman_raw(nbits, len_width, leaves, &codes)
|
|
}
|
|
|
|
/// As [`Self::huffman`] with each element's `(code, length)` given.
|
|
pub(crate) fn huffman_raw(&mut self, nbits: u32, len_width: u32, leaves: &[(u32, u32, u32)], codes: &[(u32, u32)]) -> &mut Self {
|
|
let mut blob = Self::huffman_head(nbits, len_width, leaves);
|
|
lsb(&mut blob, u32::try_from(codes.len()).expect("a short test array"), 32);
|
|
for &(c, l) in codes {
|
|
for i in (0..l).rev() {
|
|
blob.push((c >> i) & 1 == 1);
|
|
}
|
|
}
|
|
self.huffman_words(blob)
|
|
}
|
|
|
|
/// A Huffman container whose element count is `count` with no elements written.
|
|
pub(crate) fn huffman_count(&mut self, nbits: u32, len_width: u32, leaves: &[(u32, u32, u32)], count: u32) -> &mut Self {
|
|
let mut blob = Self::huffman_head(nbits, len_width, leaves);
|
|
lsb(&mut blob, count, 32);
|
|
self.huffman_words(blob)
|
|
}
|
|
|
|
fn huffman_head(nbits: u32, len_width: u32, leaves: &[(u32, u32, u32)]) -> Vec<bool> {
|
|
let mut blob = Vec::new();
|
|
lsb(&mut blob, u32::try_from(leaves.len()).expect("a short test table"), nbits + 1);
|
|
lsb(&mut blob, len_width, 8);
|
|
for &(s, c, l) in leaves {
|
|
lsb(&mut blob, s, nbits);
|
|
lsb(&mut blob, l, len_width);
|
|
lsb(&mut blob, c, l);
|
|
}
|
|
blob
|
|
}
|
|
|
|
/// The payload as whole 32-bit words of LSB-first bytes, then the last word's used bits.
|
|
fn huffman_words(&mut self, mut blob: Vec<bool>) -> &mut Self {
|
|
let bits = blob.len();
|
|
let words = bits.div_ceil(32);
|
|
blob.resize(words * 32, false);
|
|
self.uint(u32::try_from(words).expect("a short test payload"));
|
|
for byte in blob.chunks(8) {
|
|
let b = byte.iter().enumerate().fold(0u64, |a, (i, &x)| a | (u64::from(x) << i));
|
|
self.put(b, 8);
|
|
}
|
|
self.uint(u32::try_from(bits - (words - 1) * 32).expect("at most 32"))
|
|
}
|
|
|
|
/// The bits packed MSB-first, the last byte zero-padded.
|
|
pub(crate) fn bytes(&self) -> Vec<u8> {
|
|
self.bits.chunks(8).map(|c| c.iter().enumerate().fold(0u8, |a, (i, &b)| a | (u8::from(b) << (7 - i)))).collect()
|
|
}
|
|
}
|
|
|
|
/// The low `n` bits of `v`, least significant first.
|
|
fn lsb(blob: &mut Vec<bool>, v: u32, n: u32) {
|
|
for i in 0..n {
|
|
blob.push((v >> i) & 1 == 1);
|
|
}
|
|
}
|