Files
pdfcraft/crates/prc
490287fc9d prc: rebuild compressed tessellations whose normals are recalculated (#1297) (#7034)
* prc: rebuild compressed tessellations whose normals are recalculated

A TESS_3D_Compressed (WD 7.8.9.7) was read to its end but never rebuilt,
so prc-render drew nothing for it. Rebuild the form whose normals are
recalculated, with three edge statuses per triangle, into a Mesh grouped
by face and styled by each face's line attribute.

The walk (tess/compressed.rs) takes a seed, then one apex per triangle,
from a reference or the next point in the frame of the edge it continues
across (WD 7.8.9.2), with the WD 12.3 Length, Unitize and MakeOrthoRep
arithmetic bit for bit. Arrays that do not fit, or an edge shared by more
than two triangles, leave the tessellation Compressed. Every triangle,
style and face range is charged to the element allowance first.

Stored-normals meshes, the one-status-per-triangle form, the planar bend
check, the best-fit search and partial salvage are later PRs.

Adapted from pdfcer (MIT, same author) at 0a249a73.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prc: charge rebuilt compressed meshes after they fit; model_render test

The rebuilt triangles, their styles and the face ranges are now charged
to the element allowance once the arrays fit and before the face ranges
are made. Arrays the walk cannot fit stay Compressed and spend nothing,
so they no longer fail the file over a face count they never use.

Tests:
- a_rebuilt_compressed_mesh_is_charged_its_triangles_and_faces pins the
  exact charge (4 triangles x 3 elements + 2 faces);
- arrays_that_do_not_fit_spend_no_triangles_or_faces;
- rebuilt_triangles_are_grouped_by_face_with_their_styles, a direct
  by_face test with out-of-order faces, empty faces and styles;
- model_render's a_compressed_prc_model_is_rebuilt_and_drawn, end to end
  from a PDF, cited in misc.3d-view; its hex fixture is the prc-render
  builder's output, kept so by the_automation_fixture_is_the_compressed_triangle.

The walk's working tables are documented as bounded by the triangle
count rather than charged. The module docs now say the arithmetic
matches the WD pseudocode as read and that the traversal rules are
inferred, not WD text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prc: charge compressed styles and face ranges before making them

A rebuilt compressed mesh's per-triangle styles were built before the
budget was charged, and also when the normals were stored and the mesh
was read past. The face attributes are now kept as read, and the styles
are built only for a rebuilt mesh, after the charge.

Each face range is a Range<usize>, two budget elements on 64-bit
targets, so it is charged at its size, not as one element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prc: bound the compressed rebuild walk by the allowance left

The walk's vertices, triangles, edge counts, edge log and stack were
allocated before anything was charged, and could hold several times the
decoded arrays. The walk now gets the elements the reader has left and
stops once its arrays, by length, would hold more; the tessellation then
stays compressed, as when its arrays do not fit the walk.

A face whose line attribute is 0 but whose behaviour is not now styles
its triangles, so the behaviour bits are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prc: say the rebuild walk is bounded by the allowance

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prc: cap the rebuild walk's first reservation by its limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: KenM76 <KenM76>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:58:14 +01:00
..
…

pdfcraft-prc

A reader for PRC (ISO 14739-1), the 3D model format a PDF /3D stream of /Subtype /PRC carries (ISO 32000-2 §13.6.3).

  • Layer: L0, standalone. No dependency on any other PdfCraft crate.
  • Licence: MIT OR Apache-2.0. Inflation through flate2 with the pure-Rust miniz_oxide backend.
  • #![forbid(unsafe_code)]. Never panics on input: inflated bytes, decoded elements, schema steps and walk depth are capped, and damage returns PrcError.

API

PrcFile::parse(data) -> Result<PrcFile, PrcError>                 // header, file structures, model file
PrcFile::parse_with_limit(data, max_inflated) -> Result<PrcFile, PrcError>
FileStructure::section(SectionKind) -> &[u8]                       // inflated globals / tree / tessellation / geometry / extra geometry
FileStructure::schema() -> Result<Schema, PrcError>                // the globals section's leading schema
FileStructure::tessellations() -> Result<Vec<Tessellation>, PrcError> // meshes, wires, markups, compressed meshes
PrcFile::placements() / placements_with(StyleAlpha, EntityOverrides) -> Result<Vec<Placement>, PrcError>
PrcFile::model_tree() -> Result<Vec<ModelNode>, PrcError>         // the assembly tree as a model-tree panel lists it
Placement::triangle_colours(&TriangleMesh) -> Option<Vec<Option<Rgba>>>
TriangleMesh::transformed(&Matrix) -> TriangleMesh
Schema::read(&mut BitReader) / Schema::skip_added_fields(entity_type, &mut BitReader)
BitReader: bit, bits, character, unsigned_integer, integer, integer_vbn, double_vbn, nbits_then_unsigned, string, float_as_bytes, double
  • MAX_INFLATED_BYTES (512 MiB) bounds every section together; TooLarge reports the limit hit.
  • MAX_DECODED_ELEMENTS (2^24) bounds the array elements one pass decodes (a tree walk, or one tessellations call), since a single bit can decode to a 4-8 byte element; TooManyElements reports it.
  • Schema programs (how a newer producer describes fields it added) run under a nesting ceiling (64) and MAX_SCHEMA_STEPS (2^22), shared like MAX_DECODED_ELEMENTS by every program one pass runs.
  • Unsupported names constructs the reader cannot step over (schema pointer fields, Value_CurveIs3D).
  • Tessellation::Mesh is a TriangleMesh: positions, triangles, one triangle range per face, the stored normals and texture coordinates with each triangle's corner indices into them, and per-triangle line-attribute styles. Wire is polylines; Markup is read past.
  • Tessellation::Compressed is read to its end so later entities decode; its triangles are not rebuilt yet.
  • A face may carry at most 8 texture-coordinate sets; more is Malformed. Optimised vertex colours are Unsupported.
  • The product tree: each drawable tessellation placed by its occurrence path's locations and the item's coordinate systems (a 4x4 Matrix), coloured by the style inheritance rules, with occurrences' entity references applied (EntityOverrides: subtree, everywhere or ignored) and style transparency combined with material alpha by StyleAlpha. A prototype's part is coloured by its own file structure's styles. Each ModelNode lists its name, where the name came from, its visibility and the range of placements it drew.
  • Occurrence and prototype cycles are Malformed; the walk is capped at depth 64, 2^20 visits and MAX_PLACEMENTS.
  • A tree's markups (PMI), views, scene lights and clipping planes are read past, not modelled; so are textures.
  • Not yet in the crate: rebuilding compressed meshes, textures.
  • The Double code table's rows come from prc-rs (MIT); its notice is in src/acof/table.rs. A few reading choices also follow prc-rs; each is marked // prc-rs: in the source.