Commit graph

23 commits

Author SHA1 Message Date
a994213e8b feat(pdf): tagged structure tree, and the BMC bug that turned red green
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
Phase 8 #7 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md ("PDF/UA
structure tree"). Design and merge criteria in
REVIEWS/adr/0011-pdf-structure-tree.md.

Investigating the accessibility gap surfaced four defects in the
marked-content operators the structure tree depends on. The first is not
an accessibility problem at all.

1. BMC never parsed, and corrupted the colour of everything after it.
   The dispatcher matched b'B'+b'M' only when the third byte was a
   delimiter; BMC's third byte is 'C', so the arm never fired. Because
   the operator was never recognised it never CLEARED ITS OPERAND, and
   the leftover /Tag shifted the operands of whatever came next:

     1 0 0 rg            -> RgbFill(1.0, 0.0, 0.0)   red
     /Span BMC 1 0 0 rg  -> RgbFill(0.0, 1.0, 0.0)   green

   Tagged documents are precisely the ones containing BMC, so the
   documents that tried hardest to be accessible rendered wrong colours.

   This is the fifth instance of the operator-shadowing family already
   fixed for cm, rg, gs, b/b* and end-of-stream text operators. ADR
   0009's every_multi_char_operator_parses_as_itself test exists to stop
   exactly this - and would have caught it, except BMC was one of two
   operators excluded from its table as "genuinely unimplemented".
   Excluding a known-broken operator from the test whose job is finding
   broken operators is how it survived. The exclusion list is gone.

2. BDC discarded its property list, which carries /MCID - the only link
   between a run of page content and the structure element describing
   it. Without it a tree can be parsed but never attached to anything.

3. The op produced by BDC was named MarkContentBmc, and BMC produced
   nothing. The names were the wrong way round, which is how the missing
   arm survived review: the enum looked like it had a BMC case.

4. Found while fixing 2: the content lexer had no dictionary support at
   all. It read `<` as a hex string without checking for a second `<`,
   so `<</MCID 0>>` parsed as the string "0C0D0". Content streams now
   parse direct objects properly, bounded at 16 levels; a single `<` is
   still a hex string and a test pins that.

On top of that, new pdf-document/src/structure.rs: /StructTreeRoot,
/StructElem trees, /RoleMap resolution, depth-first reading order,
/Alt, /ActualText, /E, /Lang, and MCID-to-element lookup. Cycles in /K
are cut at the first repeat and reported by object number rather than
expanded to the depth bound.

Accessibility findings are mechanical checks reported as findings, NOT a
conformance verdict: there is no is_pdf_ua and no ComplianceReport, and
a mutation-checked tripwire test fails if either appears. Real PDF/UA
conformance needs human judgement - whether /Alt text is accurate is not
mechanically decidable - so claiming it from six checks would be exactly
the overclaim this codebase keeps removing.

7 corpus fixtures, 14 acceptance tests, 22 unit tests, and a
parse_content_dict fuzz target for the new object parser.

TEST_TARGET=pdf 536 -> 575, TEST_TARGET=pdf-ui 580 -> 619.
rustfmt and clippy -D warnings clean.
2026-07-31 22:19:45 +00:00
e45a717ce7 fix(pdf-ui): enable the makepad-widgets test feature so ui.rs compiles
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
456cfa5 rewrote tests/ui.rs to import makepad_test through the
makepad-widgets re-export, but did not add the `test` feature that gates
that re-export, so the pdf-ui target stopped compiling:

  error[E0432]: unresolved import `makepad_widgets::makepad_test`
  note: the item is gated behind the `test` feature

Enabling the feature alone is not sufficient. The `#[makepad_test]`
attribute expands to an absolute `::makepad_test::` path, which resolves
only if the crate is also a direct dependency:

  error[E0433]: cannot find `makepad_test` in the crate root

So both are needed, and the manifest now says why - the direct
dev-dependency looks redundant next to the re-export and would otherwise
be an obvious thing for someone to delete. crates/apps/map keeps the
same pair for the same reason.

TEST_TARGET=pdf-ui 580 passing (was: failed to compile).
2026-07-31 20:02:29 +00:00
Arena Agent
5e714577fb ci: require full 40-character SHAs for git dependency revs
Some checks failed
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (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
repo hygiene / hygiene (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (push) Has been cancelled
Upstream adopted the makepad fork fix (8fdff3f) but pinned it as
`rev = "a79f0dc"` -- a 7-character abbreviation. The pinning gate passed,
because it only checked that `rev = ` was present at all. Its own error
message has said "Add rev = \"<full-40-char-sha>\"" since it was written,
without ever enforcing it.

An abbreviated rev resolves only while no other object in the repository
shares its prefix. That is a property of the current object count, not a
guarantee -- it is why git's own auto-abbreviation length grows with a
repo. A short pin therefore degrades on its own over time, and someone
who can push to the fork can attempt to manufacture a colliding prefix.
For a dependency that executes at build time, that is a supply-chain
weakness rather than a style preference.

Checked before assuming: a79f0dc currently resolves uniquely in the fork
(exactly one matching object), so nothing is broken today. This closes it
while it is still cheap.

- All 34 manifests expanded to the full SHA
  a79f0dce4d477e2232344facca0798d3f25043ec. Cargo.lock is unchanged by
  the expansion, confirming it is the same commit and purely notational.
- The gate now also rejects any rev that is not exactly 40 hex chars.
  Negative-tested: restoring the 7-char form makes it fire.

685 lib tests pass; all nine gates pass.
2026-07-31 19:53:49 +00:00
456cfa5a68 fix: use re-exported makepad-test from makepad-widgets
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (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
repo hygiene / hygiene (push) Has been cancelled
Remove direct dependencies on makepad-test and use the re-exported
version from makepad-widgets instead. This avoids path dependency
issues and follows the correct pattern for using Makepad crates.

Changes:
- Add 'test' feature to makepad-widgets dependencies
- Remove direct makepad-test dependencies
- Update imports to use makepad_widgets::makepad_test

Affected crates:
- crates/apps/map
- crates/apps/pdf/pdf-makepad
- crates/apps/spreadsheet/spreadsheet-ui
2026-07-31 19:49:54 +00:00
8fdff3ff55 Update makepad fork to a79f0dc (remove duplicate dependencies)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
nigig-map / test (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
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
Commit a79f0dc fixes the duplicate dependency declarations that were
causing TOML parsing errors. This is the correct commit to use after
the parallel fixes in 5eda8056 and 11375214.

All 34 Cargo.toml files updated to reference the correct commit.
2026-07-31 19:43:08 +00:00
Arena Agent
80425bbfb8 fix: repair the makepad fork and unblock the build
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (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
The repository has been uncompilable since the d6d1f99c fork bump. Root
cause was in gitdab.com/andodeki/makepad, not here, so the fix went there
first: commit 5eda8056 on portallist_flow_adaptive_view.

TWO defects, both introduced by the "Update fork to upstream dev 5d4483f"
merge, both pure losses rather than intentional changes:

1. widgets/Cargo.toml: the makepad-gltf / makepad-csg / makepad-test
   dependency lines were relocated from [dependencies] to below
   [features]. Cargo then parses each as a feature whose value should be
   an array, giving "invalid type: map, expected a sequence", and the
   gltf/csg/test/maps features cease to exist.

2. widgets/src/lib.rs: the feature-gated re-export block for those same
   crates (plus makepad_fast_inflate and makepad_mbtile_reader) was
   deleted outright. Fixing only the manifest surfaced this as
   "no `makepad_csg` in the root".

Both restored verbatim from 2c5cd97, the last rev that resolved. Neither
is a judgement call: the moved lines are byte-identical and the deleted
block is copied back unchanged.

This repo is then repinned from d6d1f99c to the fixed rev, full 40-char
SHA per the pinning convention CI enforces.

Verified end to end after removing the local git redirect used during
development, so this resolves against the real remote:
  cargo metadata            resolves
  nigig-build --lib         685 passed
  cad_integration           154 passed
  spreadsheet-engine        225 passed
  doc-engine                 53 passed
  nigig-map (maps feature)  compiles
  Cargo.lock                unchanged, --locked passes

Also resolved committed conflict markers in two workflow files, which
had made nigig-build.yml invalid YAML -- the CI config could not be
parsed at all:

- nigig-build.yml: kept --include='*.rs' on the by-value-getter gate.
  Without it the gate scans ARCHITECTURE.md and fails on its own
  documentation, which is the bug fixed in 4f32b1c.
- pdf.yml: kept upstream's side. Enumerating targets via
  `cargo fuzz list` and failing when the list is empty is strictly
  better than a hardcoded target list that silently passes vacuously if
  a target is renamed.

That makes four files in three commits now carrying committed conflict
markers from this merge. Worth checking how they are reaching main --
`git diff --check` catches exactly this and is already a step in the
nigig-build workflow, but it only runs on paths under that workflow's
filter.
2026-07-31 19:32:08 +00:00
4eebae14f2 Update makepad fork to 11375214 (Cargo.toml fix)
Some checks failed
nigig-build.yml / Update makepad fork to 11375214 (Cargo.toml fix) (push) Failing after 0s
pdf.yml / Update makepad fork to 11375214 (Cargo.toml fix) (push) Failing after 0s
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
- Fixed TOML parsing error where fork-specific dependencies were in wrong section
- Dependencies now correctly placed in [dependencies] before [features]
- Maps feature should now be properly recognized
2026-07-31 19:24:22 +00:00
d8d29c226d feat(pdf): transparency, and four operator-parsing bugs it exposed
Some checks failed
nigig-build.yml / feat(pdf): transparency, and four operator-parsing bugs it exposed (push) Failing after 0s
pdf.yml / feat(pdf): transparency, and four operator-parsing bugs it exposed (push) Failing after 0s
Phase 8 #5 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md
("Advanced transparency"). Design and merge criteria in
REVIEWS/adr/0009-pdf-transparency.md.

The headline defect was not that transparency was missing. `gs` was
MIS-PARSED as CloseStroke:

  /GS0 gs 1 0 0 rg 100 100 200 200 re f
    -> [CloseStroke, RgbFill, Rectangle, FillWinding]

so every page setting a graphics state gained a stroked path the
document never asked for, and lost its alpha, blend mode and soft mask
silently. Downstream everything was dead: StrokeExtGState/FillExtGState
were never constructed, set_fill_opacity was never called and emitted no
command when it was, and PdfPage::ext_gstate was read by no code at all.

New pdf-graphics/src/transparency.rs: full /ExtGState (ca, CA, BM,
SMask, LW, LC, LJ, ML, D, AIS, TK), all sixteen blend modes including
the four non-separable ones, soft masks with /S, /G, /BC and a /TR
evaluated through ADR 0006's PdfFunction, and /Group parsing. Wired end
to end: parser -> PdfOp::SetExtGState -> device -> RenderCommand ->
Makepad renderer.

Compositing is NOT claimed. Backdrop blending needs render-to-texture,
which an engine-neutral crate has no framebuffer for. Constant alpha is
applied because it needs no backdrop; blend modes and soft masks are
reported through TransparencyError::Unsupported rather than dropped,
because a silently ignored /Multiply looks exactly like a correct
/Normal.

The ADR required a test enumerating every multi-character operator, on
the grounds that fixing the third instance of a shadowing bug (after cm
and rg) without preventing the fourth is not a fix. It immediately found
three more live bugs, none of them transparency-related:

- `b` and `b*` dropped their close-path, mapping to FillStroke* instead
  of CloseFillStroke*, so every closed-and-stroked path drew with a gap.
- Text operators within three bytes of the end of a content stream were
  mis-parsed: the b'T' arm guarded `*i + 3 < len` while reading only two
  bytes, so a stream ending in `/F1 12 Tf` parsed as FillWinding.

And the transparency-group fixture found a fourth:

- PdfPage::xobjects was empty for every page of every document.
  extract_xobjects called doc.resolve(), which follows the reference,
  then asked the resolved object for as_ref() - always None - and only
  accepted a bare dict when every XObject is a stream. No `Do` operator
  could be resolved through the page model. Same defect as the one that
  once destroyed annotation object references; it now has its own
  regression test.

T* and BMC are genuinely unimplemented and are deliberately excluded
from the guard's table rather than papered over.

7 corpus fixtures, 12 acceptance tests, 32 unit tests asserting the
§11.3.5 formulas (not our own output), and a parse_ext_gstate fuzz
target.

TEST_TARGET=pdf 451 -> 497, TEST_TARGET=pdf-ui 495 -> 541.
rustfmt and clippy -D warnings clean.
2026-07-31 18:58:31 +00:00
8c9ccb92cc Update makepad fork to latest dev branch (d6d1f99c)
Some checks failed
nigig-build.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
pdf.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
- Sync with upstream commit 5d4483f (latest map improvements + platform updates)
- Include location API, audio echo cancellation, bridge-dz overlay
- Add new libraries: geodata, map_nav, i_float, i_shape, i_tree, converse, llama vision
- Preserve all fork-specific re-exports (gltf, csg, test)
- All 102+ map improvements now available: 2D/3D toggle, shadows, labels, overlays, pattern fills
2026-07-31 18:47:37 +00:00
bea1fd884e chore: update makepad fork to include upstream map improvements
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (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
Updated all makepad dependencies from rev 2c5cd97 to 817d881 which includes:
- Terrain hillshade landcover draping (drape.rs)
- Route overlays, markers, and position puck (overlay.rs)
- Map icon management system (icons.rs + 50 SVG icons)
- 3D road elevation and seamless joins
- Building shadow geometry and terrain shadows
- Night themes and emissive roads
- Water, grass, and shrub rendering
- Optimized road geometry with 2D/3D mode transitions
- i_overlay library for polygon boolean operations

This brings nigig-map in sync with the latest makepad dev branch improvements.
2026-07-31 18:39:39 +00:00
7d21532ebf feat(pdf): real colour spaces, ICC profiles and PDF functions
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
Phase 8 #4 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md
("Advanced colour"). Design and merge criteria in
REVIEWS/adr/0006-pdf-advanced-color.md.

The interpreter tracked only the *name* of the active colour space and
then passed sc/scn operands to the device as if they were already RGBA.
Every non-device space therefore rendered a confident wrong colour with
no error:

  /Spot cs 1.0 scn      full tint of a spot ink   -> pure red
  /Idx  cs 3   scn      palette entry 3           -> near-black
  /Lab  cs 50 0 0 scn   mid gray                  -> white (clamped)
  /DevN cs (5 inks)     five colorants            -> inks 5+ discarded
  /ICCBased             profile-defined colour    -> profile discarded

DeviceCMYK also used the additive 1-c-k conversion, which crushes any
colour printed over black.

Three new modules in pdf-graphics:

- function.rs  PDF functions, all four types. Type 4 runs on a bounded
               interpreter: depth 32, 32768 tokens, stack 100, 100000
               steps, and an unknown operator is an error rather than a
               no-op that would leave a plausible wrong colour.
- icc.rs       ICC matrix/TRC and gray kTRC profiles, applied exactly.
               LUT-class profiles are reported as such and the caller
               falls back to /Alternate; they are never pretended to be
               matrix profiles.
- colorspace.rs  All eleven families, converting through XYZ with
               Bradford adaptation and a real sRGB transfer function.

Wiring:

- PdfDevice gains set_stroke_components/set_fill_components, so SC/SCN
  reach the device as components of the active space instead of being
  read positionally as RGBA.
- cs/CS now resets to the space's initial colour (table 74), which is
  why golden/colors.txt gains a line.
- PdfPage::color_spaces carries /Resources /ColorSpace fully
  dereferenced with streams decoded; a half-resolved space would make
  every ICC profile, palette and type 0/4 transform silently fall back.
- A space that cannot be resolved keeps the previous colour and records
  a typed ColorError. No colour is invented, and no error is swallowed.

Tests: 12 corpus fixtures under tests/corpus/color/, 14 acceptance
tests in pdf-document/tests/color.rs asserting numeric RGB (the broken
code produced a colour for every one of these; only the value was
wrong), plus unit tests per function type and per curve type. Two fuzz
targets added: eval_function and parse_colorspace.

TEST_TARGET=pdf 387 -> 447 passing, TEST_TARGET=pdf-ui 431 -> 491.
rustfmt and clippy -D warnings clean.
2026-07-28 20:48:55 +00:00
60db0f21db build: update Nigig to Makepad dev reexport fork
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (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
2026-07-28 17:14:18 +00:00
Arena Agent
b5471e32e3 fix(cad): make the crate buildable, testable and safe to ship
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (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
Phases 0-2 of CAD_ASSESSMENT_AND_PLAN.md. The crate did not compile and no
test had ever run; it now builds clean with a green suite.

Build and CI (Phase 0)
- Pin all 33 git dependency manifests to an explicit rev. A branch
  dependency re-resolves on every build and is a code-execution path into
  CI if force-pushed.
- Commit Cargo.lock (540 packages). Producing it required fixing three
  resolution failures the workspace had always had: a non-existent
  makepad-widgets feature, two rusqlite versions both linking sqlite3, and
  four missed CellId call sites in spreadsheet-ui.
- Add .forgejo/workflows/nigig-build.yml.
- Replace five stale CAD docs that contradicted the code with one
  ARCHITECTURE.md; add PHASE0/1/2_STATUS.md and TEST_BASELINE.md.

Correctness (Phase 1)
- Rotation units: transform_point bound sin_cos() backwards, transposed X
  and Z, and applied axes in reverse order, so every exported STL was wrong
  even at zero rotation. It now shares the renderer's matrix helpers.
- GLB quaternions had norm 0.125 (half-angle applied to cos/sin, degrees
  read as radians) - invalid per the glTF spec.
- PDF wall/door/window yaw fed degrees to cos/sin.
- Fix a TOCTOU unwrap in touch picking; viewport.rs now has no unwrap().
- CommandContext gains update_node/insert_node_at/node_index: resize and
  modify were delete+create, silently moving nodes to the end of the scene.
- Wire MAX_UNDO_LEVELS (defined, exported, never read) and switch the undo
  stack to VecDeque; this also made the existing drag-merge logic reachable.
- CadNode::size() returned a fake 1x1x1 for CSG and extruded solids, making
  them unpickable outside a 1x1x1 box at their origin.
- Reject non-finite script input; makepad_csg clamps NaN rather than
  propagating it, so bad input produced silently wrong geometry.

Test baseline: 0 -> 722 passing, 0 failing
- 17 pre-existing failures fixed: 10 real defects (dependency-cycle
  detection, over-allocation of unassigned tasks, quote/backslash
  corruption on save, default rooms lost for all but the first region,
  RGA text ordering) and 7 tests that were themselves wrong, each checked
  against its production caller first.

Security (Phase 2)
- env!("CARGO_MANIFEST_DIR") was used as a runtime path in three places,
  including as the AI agent's working directory. All runtime data now goes
  under app_data_dir().
- Remove the hardcoded LAN LLM endpoint. It is now opt-in via
  NIGIG_CAD_LOCAL_OPENAI_URL/_MODEL and refuses plaintext HTTP to anything
  but loopback.
- Bound and content-sniff AI image attachments (8 MB cap, magic bytes);
  the MIME type came from the filename extension.
- Escape SVG/HTML output, and add SRI to the exported viewer's script tag.
  The pinned model-viewer@3.5.1 does not exist, so every exported viewer
  was silently broken; now 4.0.0 with a verified hash.
- Stop embedding $USER in exported PDFs and logging document content in
  release builds.
- CI now rejects reintroducing the runtime-path and hardcoded-endpoint
  classes; both gates were verified to fail on a reintroduced defect.

Add system_prompt.md and embed it with include_str!. The file was missing
from the repository, so the agent silently used a one-line fallback.
2026-07-28 16:49:30 +00:00
d3ccc2e00f test(pdf): close the three gaps carried from Phases 4 to 7
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
Three items were carried forward as known gaps rather than quietly dropped.
This addresses all three; two are closed outright and one is bounded by an
environment limit that is now documented rather than implied.

1. Fuzzing had never actually run (Phase 6 step 6.3).

The five cargo-fuzz targets were only compile-checked, so "zero panics on
arbitrary input" was an aspiration. They have now been run under nightly
libFuzzer:

  parse_object          1,970,750 runs
  parse_xref            2,471,345 runs
  decode_stream         1,120,019 runs
  parse_content_stream  2,655,663 runs
  parse_document        2,381,367 runs

About 10.6 million executions in total, no crashes and no new findings. That
is a real result rather than a green checkmark: the three crashes the corpus
found in Phase 6 were the ones worth finding, and the fuzzer confirms the
fixes hold under adversarial input.

2. Combo dropdown overlay (Phase 4 step 4.3).

A combo box that cannot be opened is a text field with extra steps, so the
open list is real state, not a rendering detail. Clicking a combo box opens
its options; the dropdown takes a click before any field underneath it,
matching the draw order; choosing a row sets the value through
DocumentFormEditor; clicking elsewhere dismisses it without changing the
value. render_open_combo() returns placement data so the drawing code stays
trivial and the geometry is testable without a renderer.

3. Makepad event delivery.

Upstream added a makepad_test framework, so this is now testable in
principle. Adds a test host binary and six UI tests that drive the widget
through the Studio protocol: a real click on the fixture link must surface
OpenUri on the host, typing must reach the field, and a click on empty space
must emit nothing so the positive assertions are not vacuous.

They are #[ignore] by default because the Studio hub cannot start an app in
this sandbox: the harness launches with --stdin-loop, which Makepad refuses
without a Studio websocket, and the build exits 101 before startup.
Upstream own spreadsheet-ui and map UI suites fail identically here with the
same error, so this is the environment rather than this code. The tests are
checked in and compiled by cargo test so they cannot rot, CI runs them where
a hub exists, and the module documents how to run them by hand.

Getting there also fixed a real defect in the test host: it copied
ui.main_view.render() from the spreadsheet app startup hook, but a plain
View has no render method, so the app errored at startup.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (270 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (321 tests, 6 ignored)
  cargo +nightly fuzz run <target> -- -max_total_time=60   (5 targets)
Both rustfmt and clippy -D warnings clean.
2026-07-27 18:09:57 +00:00
1c7dc9e9c0 feat(pdf): complete Phase 7 performance work
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
Phase 7 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md, taken only now
because the review is explicit that it comes after correctness is proven.

The caching and threading live in pdf-graphics rather than the Makepad crate
because none of it needs a GPU. That is what lets the staleness, eviction and
cancellation rules be tested without a window; the widget keeps only the
parts that genuinely need a Cx.

Step 7.1, off-thread parsing (new pdf-graphics/src/worker.rs):
- RenderWorker interprets content streams on a background thread and returns
  results tagged with the Generation they were requested for.
- PendingPages tracks in-flight pages so the widget can draw a placeholder
  and never queues the same page twice.
- Drop joins the thread rather than detaching it: a detached thread writing
  into a dropped channel is the kind of shutdown race that surfaces as a
  flaky test months later.
- A content stream that fails to parse yields an empty page, so one broken
  page cannot take down the document.

Step 7.2, texture and memory management (new pdf-graphics/src/cache.rs):
- PageCache is an LRU keyed by page index with a byte budget, not an entry
  count: one image-heavy page can outweigh fifty text pages, so counting
  entries would evict the wrong things.
- retain_around() releases pages that scrolled out of view, keeping a margin
  so a small scroll does not immediately re-render.
- Decoding produces DecodedImage bytes off-thread; GPU upload stays on the
  UI thread.
- A page larger than the whole budget is still stored, since refusing it
  would mean re-rendering it every frame.

Step 7.3, render command cache:
- CachedPage holds the interpreted Vec<RenderCommand>, so replaying a page
  skips re-parsing its content stream.
- invalidate_appearance() marks a form edit dirty without discarding the
  commands, because a form edit changes what is drawn over the page, not the
  page content stream.

Widget wiring: PdfPageWidget carries a generation, refuses PageContent from a
superseded document, and draws a placeholder while a page is still rendering.

Exit criterion (new pdf-document/tests/phase7_exit_criterion.rs, 9 tests)
against a new 60-page corpus fixture. Measured here: first page 3ms against
the 200ms budget, and a warm cache read 389x faster than re-parsing (1us vs
389us). The budget is deliberately loose because a shared CI runner is
unpredictable and a flaky performance test gets muted, and a muted test is
worse than none; it still catches the order-of-magnitude regression the
review is guarding against. Memory is asserted bounded across 30 document
switches, and every page of an abandoned document is refused.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (270 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (307 tests)
Both rustfmt and clippy -D warnings clean.
2026-07-27 17:30:50 +00:00
eacc86077e feat(pdf): complete Phase 5 font engine and text metrics
Phase 5 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md.

Step 5.1, real metrics (new pdf-graphics/src/sfnt.rs):
- Reads head, hhea, OS/2 and hmtx from embedded TrueType/OpenType programs,
  which the review asks for by name. sTypoAscender/Descender are preferred
  over hhea when non-zero, since subset fonts often zero them; sCapHeight
  and sxHeight are only read from OS/2 version 2 and later, because reading
  them from a v1 table returns whatever bytes follow it.
- Every read is bounds-checked. A table pointing outside the file, a
  truncated directory or a zero unitsPerEm yields None rather than a panic
  or a divide by zero. These are untrusted embedded programs.
- resolve_font() now reads FontFile/FontFile2/FontFile3 and falls back to
  the descriptors declared Ascent/Descent only when no program is embedded.
- renderer.rs looks up a per-font ascent instead of leaving ascent_em
  permanently None, which had left the fallback ratio always in effect.

Step 5.2, accumulated advances (new pdf-graphics/src/advance.rs):
- measure_advance() implements PDF 32000-1 9.4.4 properly:
  (w0/1000 * Tfs + Tc + Tw) * Th/100, with Tw restricted to single-byte
  code 32. Applying Tw to a two-byte code whose low byte is 32 is a classic
  composite-font bug and is now covered by a test.
- glyph_advances() gives per-glyph widths so a caret or a partial selection
  rectangle no longer assumes even spacing.
- GlyphAdvanceCache is keyed by font name and size as the review specifies,
  and carries a generation so it rebuilds when page data changes rather
  than every frame. Redefining a font drops its stale measurements.

Step 5.3, composite fonts:
- The old build_cid_widths truncated every CID to u8, so any glyph above
  255 silently took the default width. It is replaced by CidWidths keyed by
  the real CID, with the CMap resolved from a predefined name or an
  embedded stream.
- ResolvedFont::decode_text returns None for a composite font with no
  ToUnicode map instead of guessing Latin-1 and producing plausible
  nonsense.

Tests: new tests/phase5_exit_criterion.rs asserts the exit criterion.
Selection across a proportional and a monospaced run verifies the second
run starts at the first ones true end; per-glyph advances sum to the run
advance and H measures wider than l, which an even-spacing estimate would
not. Copy returns correct Unicode through ToUnicode including a non-ASCII
scalar, and through WinAnsi for simple fonts. Search finds both hits across
two lines with rectangles inside their own runs and the right vertical
order. Cache reuse is asserted through hit and miss counts.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (203 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (238 tests)
Both rustfmt and clippy -D warnings clean.
2026-07-27 16:51:18 +00:00
8bc94253ff feat(pdf): complete Phase 4 with an interactive page widget
Phase 4 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md. The previous
commit added the interaction logic but left it an orphan: nothing routed
Makepad events into it, which is the exact defect the review names when it
says PdfPageView declares is_interactive() -> false and an empty
handle_event.

New pdf-makepad/src/page_view.rs, PdfPageWidget:
- is_interactive() returns true and handle_event routes real events.
- Step 4.1 ordering: form field events, then annotation hit testing, then
  text selection. The order is enforced inside InteractionState::click so
  it cannot drift between the widget and the tests.
- Step 4.2: link underlines and form widget borders are drawn, and the
  cursor changes over links and text fields.
- Step 4.3: field values draw inside their widget rects, a focused field is
  highlighted and shows its uncommitted buffer.
- Step 4.4: navigation is emitted as PdfPageAction to the host. The widget
  never opens a URL (rule 5).
- Key focus loss commits the pending edit, so an edit is never silently
  lost, while Escape still abandons it.
- Only keys the editor handles are consumed; everything else falls through
  to the host instead of being swallowed.

The widget keeps no decision logic of its own: it syncs the viewport from
its on-screen rect and delegates. That keeps the part that needs a live Cx
as small as possible, because it is the part that cannot be unit tested.

Also adds Selection to interaction.rs so drag-to-select and copy read
through the Phase 5 PageText API rather than re-deriving positions.

Tests: new tests/phase4_exit_criterion.rs drives the exit criterion against
the real two-page AcroForm fixture, not synthetic dictionaries. Click a link
gives OpenUri; click a field, type, commit, and the value changes, the
appearance regenerates with the new text and the draw list shows it. It also
asserts an abandoned edit changes nothing, the checkbox uses the /On state
declared in the file, and form fields route before annotations.

Validation: TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh
202 tests pass; rustfmt and clippy -D warnings clean.
2026-07-27 16:36:34 +00:00
66c7590d66 feat(pdf): start Phases 4 and 5, and make pdf-makepad compile
Some checks failed
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
Phases 4 and 5 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md.

pdf-makepad had never compiled. It was missing eight PdfDevice methods, had
a non-exhaustive RenderCommand match, called a function through the wrong
path and held a borrow conflict. Everything previously claimed about the
Makepad rendering path was therefore unverified. It now builds and is tested
under a new pdf-ui target; the makepad build needs system GUI libraries, so
that target checks for them with pkg-config and names the missing packages
rather than failing in the linker.

Phase 4 (new pdf-makepad/src/interaction.rs):
- Viewport maps between PDF space (y up) and screen space (y down) in one
  place, so hit testing and rendering cannot disagree. Inverted /Rect
  corners are normalised.
- Click routing follows the review order: form fields first, then annotation
  hit testing. A field overlapping a link takes the click.
- Clicking a link emits PdfAction::OpenUri to the host. The viewer never
  opens a URL itself (rule 5).
- Text editing is buffered: typing changes a working copy and only Enter,
  Tab or blur commits it, so Escape abandons an edit with the document
  untouched. Caret arithmetic is in characters, not bytes, so a non-ASCII
  value cannot panic.
- Checkboxes toggle on click using the state the widget declares.
- Hover reporting for cursor feedback; read-only fields are not targets.
- render_fields() returns placement data so a focused field shows its
  uncommitted buffer while drawing stays trivial.

Phase 5:
- New pdf-graphics/src/cid.rs: composite font support. Codespace ranges give
  variable-width code decoding (the previous CMap was u8 to char, so every
  two-byte CID font decoded as garbage), plus cidchar/cidrange, ToUnicode
  bfchar/bfrange including array destinations and multi-character values
  such as ligatures, /W and /DW CID widths, and Identity-H/V. An unknown
  predefined CMap returns None instead of silently substituting Identity;
  unmapped codes decode to U+FFFD instead of vanishing. Width ranges are
  bounded so a malformed /W cannot exhaust memory.
- New pdf-graphics/src/text.rs: the PageText extraction API. Segments carry
  origin, advance and font size; find() returns matches with rectangles;
  selection spans runs and works in either drag direction. Runs sharing a
  baseline stay on one line even with mixed font sizes.
- renderer.rs: the two unexplained `fs * 0.75` constants are replaced by a
  named DEFAULT_EM_TO_CAP_RATIO used only as a fallback, with the real
  ascent preferred when the descriptor supplies one. Ts (text rise) was
  stored but never applied, so superscripts drew on the baseline.

Also fixes clippy across pdf-makepad, which had never been linted.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (167 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (191 tests)
Both rustfmt and clippy -D warnings clean.
2026-07-27 16:21:37 +00:00
af4ef6f170 fix(pdf): restore a compiling, warning-free PDF baseline
Some checks failed
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
Phase 0 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md requires a clean
baseline before any feature work. The four pdf crates did not compile at all,
so every test claim about them was unverified.

Compile fixes:
- decode_lzw/decode_run_length returned Vec<u8> where callers expected
  PdfResult, so decode_stream_with_params did not type-check.
- interpret_ops called a current_state_mut() method that PdfDevice does not
  have; colour-space tracking now goes through explicit device hooks.
- image.rs used miniz_oxide without depending on it; PNG inflate now reuses
  the COS crate through a new pdf_cos::filter::inflate_zlib.

Correctness fixes found while making the code build:
- LZW and RunLength silently truncated malformed input and indexed unchecked;
  both now return typed errors (rule 6: no silent degradation).
- Tw/Tc/Tz were parsed and thrown away, and the " operator dropped its word
  and character spacing, so every advance after them drifted.
- TJ attached each kern to the preceding string instead of the following one
  and discarded a trailing kern entirely.
- GlyphWidths::width() fell back to default_width for out-of-range codes;
  PDF 32000-1 9.6.2.1 requires /MissingWidth.
- Text advances silently substituted a guessed font_size * 0.6 when no width
  table was present; ShowTextWithMetrics now carries advance_is_measured so
  callers can distinguish a measurement from an unknown.

Tests: two tests had never compiled and were wrong once they ran (WinAnsi
0x99 is U+2122 not U+2019; q/cm/l/Q records four commands not three). The
document test asserted only that the writer emits a %PDF header; replaced
with real page-tree, out-of-range and malformed-input coverage.

Adds a pdf target to tools/test-rust-clean.sh that tests the three
UI-independent crates bottom-up under rustfmt and clippy -D warnings.

Validation: TEST_TARGET=pdf ./tools/test-rust-clean.sh
72 tests pass; rustfmt and clippy -D warnings clean.
2026-07-27 15:33:15 +00:00
62dada264e build: consume Makepad sibling APIs through widgets 2026-07-27 04:22:42 +00:00
e73def6d1b build: align Makepad dependencies with Robrix fork 2026-07-27 03:32:08 +00:00
a2ea0ffc7c updated map 2026-07-26 18:00:48 +03:00
cc05abdc71 Initial commit 2026-07-26 19:38:26 +03:00