Commit Graph

3 Commits

Author SHA1 Message Date
ai-ad4 17512022e9 feat(spike-C): hb-subset font growth; new glyph renders (§14 step 6)
Implement Spike C: grow an existing embedded subset font with a new glyph
using hb-subset and verify the result, exercising §4.2 resolution ladder
step 1.

SubsetGrowth.cpp runs a three-step scenario with the HarfBuzz subset C API
and FreeType:
1. Build the 'originally embedded' subset of DejaVu Sans with glyphs for
   'Hello' (H,e,l,o).
2. Grow it by adding U+03A9 (Ω) — the character the user typed that wasn't
   in the original subset. Produces a NEW font; original untouched (§4.2:
   'never mutate a font program in place').
3. Verify: grown subset contains the glyph, cmap maps U+03A9 → gid, FreeType
   loads its outline (it renders); original subset does NOT contain it.

Gate MET: original 5 glyphs/4300 bytes → grown 6 glyphs/4488 bytes, new
glyph present and renders. Verified clean under ASan+UBSan. Result and
findings in docs/spike-results/0003-spike-c-subset-growth.md.

Findings: (1) hb-subset growth works and is cheap (+188 bytes/glyph). (2)
HarfBuzz subset API changed across versions — HB_SUBSET_SETS_DROP doesn't
exist in 10.2; pin the baseline (vcpkg manifest does). (3) FT_Get_Char_Index
+ FT_Load_Glyph is the right render-verification pair; the real viewer
check (Acrobat/Preview/Chrome) remains a release gate (§8.3). (4) the
'never mutate in place' rule is honoured by construction (hb_subset_or_fail
returns a new face).

CI: add Spike C to the spike-gates job (libharfbuzz-dev, libfreetype-dev,
fonts-dejavu-core); fails the build on regression.

Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>
2026-07-25 20:41:02 +00:00
ai-ad4 c299236743 feat(spike-B): reconstruction pipeline; F1=0.963 on synthetic corpus (§14 step 5)
Implement the text reconstruction pipeline (§4.1 steps 2-5) for the M0
Spike B gate:
- GlyphRun.h: input model (Glyph, GlyphRun, Line, Paragraph, GroundTruth)
  mirroring §3.2/§3.3
- Reconstruct.cpp: flatten → line detection (baseline cluster, 0.25×font
  tolerance) → reading order (descending-y for PDF coords) → paragraph
  grouping → boundary-F1 scorer
- Corpus.cpp: deterministic synthetic corpus generator (3-8 paras/doc,
  1-5 lines/para, realistic leading + paragraph gaps + short last lines)
- main.cpp: emits the contract JSON; exit 0 if F1 ≥ target

Gate MET: F1=0.963 (precision 0.929, recall 1.000) on 500 synthetic docs,
target ≥0.85. Verified clean under ASan+UBSan. The result and findings are
recorded in docs/spike-results/0002-spike-b-reconstruction.md.

Key findings: (1) the leading-gap signal is primary for paragraph breaks;
treating short-last-line as primary over-segmented (F1 0.37→0.96 fixed).
(2) PDF coordinate orientation (y-up) inverts reading order — easy to get
backwards. (3) column-width inference must use a percentile not the max.
(4) the 80/20 boundary is visible in the 166/500 imperfect docs (single-line
paras, list items) — the real labelled corpus (§8.1) is needed for the 0.93
release gate.

Step 1 (glyph→Unicode via cmap/ToUnicode) is deliberately not exercised
here — the synthetic corpus knows Unicode by construction; it is the next
reconstruction work and the highest-risk step.

CI: add a spike-gates job to .gitea/workflows/build.yml that runs Spike A
(informational — the byte-identity gate is met by §4.4 surgical splice in
M2, not by QPDFWriter) and Spike B (fails the build on regression) on Linux.

Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>
2026-07-25 20:38:24 +00:00
ai-ad4 e778a56540 chore: initial repository scaffold (§14 steps 1-3, 9)
Stand up the repository foundation described in docs/plan.md §14:
- CMake + vcpkg manifest mode (pinned baseline), presets for debug/release/asan/tsan/ci-release
- Release-build hardening module (§7.2): stack protector, libc++ hardening, CFI/CET
- Skeleton CPack packaging for all nine artifact formats (§12): .deb/.rpm/.tar.xz/.AppImage, .msi/.exe/.zip, .dmg/.pkg
- Linux integration: .desktop, hicolor icon, AppStream metainfo, man page, bash completion
- Empty-window Qt Widgets application shell with the §9 chrome layout
- Spike A harness: QPDF open → parse content streams → re-emit verbatim → save, measuring byte-identical round-trip rate over a corpus
- Pixel-diff harness scaffolding (§8.2 gate) with the spike-runner JSON contract
- Contract test pinning the spike-runner report format (dependency-free)
- Gitea Actions CI matrix (§13.3): build × {linux,macos,windows} × {debug,release,asan,tsan}, plus nightly packaging pipeline
- Tooling: clang-format, clang-tidy, gitleaks, REUSE config, pre-commit hooks, CODEOWNERS
- Governance: README, CONTRIBUTING, SECURITY, TRADEMARK, docs/plan.md, four M0 ADRs (§2.5, §4.4, §7.2, §2.1)
- REUSE-compliant SPDX headers and LICENSES/ directory (GPL-3.0-or-later)

No production parsing, rendering, or editing code yet — that lands in M1-M6.
The scaffolding is the hard-to-retrofit foundation: build system, packaging
pipeline, CI gates, governance, and the ADRs that fix the architectural
invariants before any code that depends on them is written.

Signed-off-by: ai-ad4 <ai-ad4@users.noreply.gitea.lm.je>
2026-07-25 20:14:22 +00:00