Commit graph

2 commits

Author SHA1 Message Date
728fbc3ad0 fix(pdf): mesh shadings — three bugs in code that had no fixture
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
ADR 0028 shipped types 4-7 and said honestly that they were unproven: the
uncovered lines of `shading.rs` were exactly `parse_mesh`, "the position
`image.rs` was in before ADR 0016 found the JPEG decoder was a stub".

Writing the fixtures found three real bugs.

- Type 5 has no per-vertex flag; `/VerticesPerRow` delimits it. Reading 8
  phantom bits shifted every vertex after the first, decoding plausible
  coordinates that were entirely wrong.
- Types 6 and 7 are patches: 12 or 16 control points carrying no colour,
  then four corner colours. The old loop read a colour per point, consumed
  three times too many components, ran off the stream, and the
  None-on-truncation path swallowed it as "the mesh ended".
- A flag-0 triangle is three vertices whose second and third flags are
  ignored (§8.7.4.5.5). Acting on them cleared the strip every time and
  produced no triangles at all. Caught in new code, before it shipped.

And one omission: `color_at_point` returned None for a mesh, so a mesh that
parsed perfectly still painted nothing — indistinguishable from one that
failed. `MeshTriangle::color_at` now interpolates the corner colours by
barycentric coordinates, None outside, because black is a colour a mesh can
legitimately produce.

Shared-edge patches (flags 1-3) inherit the previous patch's edge rather
than being read as fresh patches, which desynchronised the rest of the
stream.

Five corpus fixtures, generated from named coordinates and colours so every
expected value in the tests is one the generator wrote deliberately. Eight
tests, five mutations, all killed. Coons flattening is still an
approximation and still reports `is_approximate`.

ADR 0029.
2026-08-18 19:10:49 +00:00
c1d1e67f3a feat(pdf): shadings — the sh operator was parsed and thrown away
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
ADR 0028, the first of Phase 7's eight bullets.

content.rs contained `PdfOp::Shading(_name) => {}`. The operator was lexed,
given its own variant, matched during interpretation, and discarded. A page
whose background is a gradient rendered as nothing.

Nothing caught it for the usual reason: a blank region is a legal thing for
a page to contain, so "drew nothing" and "drew what was asked" are
indistinguishable without an assertion naming the expected colour. The
golden corpus had no shading page, so there was nothing to be wrong.

Two of the three pieces already existed — function.rs evaluates the colour
function and colorspace.rs converts it to RGB. What was missing was the
geometry between them.

Sampling rather than a gradient primitive: a PDF shading is defined by an
arbitrary function, possibly a sampled table or a PostScript program, and
neither reduces to a stop list without loss. A device with a native
gradient can still recognise the two-stop case from the samples.

"No colour here" is None, not black. Black is a colour a shading can
legitimately produce, so returning it for "outside an unextended shading"
would paint a rectangle the author never asked for and the caller could not
tell the two apart.

Types 1-5 exact. Coons and tensor patches are flattened to their corners,
which loses the curvature, and is_approximate says so rather than leaving a
caller to assume fidelity. An unknown type is refused by number: a mesh
drawn as a flat fill is a plausible-looking wrong answer.

paint_shading is a new trait method, so the compiler found every
implementor. The Makepad renderer records the request in pending_shadings,
mirroring pending_xobjects — it cannot resolve a /Shading resource because
it does not own the page dictionary, and recording the request is what
stops the operator vanishing a second time. That holds even for types we
refuse, so a host can warn the user.

Four mutations, all killed. The first — discarding sh again — fails three
tests.

Stated plainly and left unticked: the mesh path is written but NOT
exercised by any real stream. shading.rs is at 68% and the uncovered part
is exactly parse_mesh and triangulate. Mesh support should be treated as
unproven, not working: the code runs and produces triangles, and nothing
yet demonstrates they are the right triangles. That is the position
image.rs was in before ADR 0016 found the JPEG decoder was a stub.

The Phase 7 status line is a table from the start this time — one row per
spec bullet, seven of them saying "not started". Per ADR 0021, written
before the work rather than after it.

pdf: 1321 passed (was 1291). pdf-ui: 1366. Coverage 87.60%, floors met.
2026-08-18 17:30:39 +00:00