feat(reporting): add multi-plate pagination and dense-label fallback

Slice 2 of the nest report plan: pagination, overflow and dense-label
coverage on top of the Slice 1 one-plate library.

- NestPdfWriter: general multi-plate pagination. Summary/plate tables
  continue across pages with repeated (HeadingFormat) header rows; notes
  flow as an ordinary paragraph instead of a bounded cell; page headers
  are wrapped and sized into the top margin; drawing areas are located
  per page via DocumentRenderer.GetRenderInfoFromPage.
- ReportText: lossless pre-wrapping (MigraDoc clips an over-tall row and
  lets an unbroken token overflow a narrow cell without warning), capped
  table-cell line counts, plate-range compression ("1-2, 4"), and
  A/B/.../AA map-grid row names.
- NestReportDiagram: label placement centers each part ID on its
  PolyLabel pole (matching PlateView's LayoutPart), computed on a
  placement-independent quantized copy so identical parts share one
  label position. Parts whose ID cannot fit legibly at overview scale
  get a lettered/numbered map grid and a zoomed, framed detail page per
  crowded cell; the writer fails with plate/part/ID when even that
  cannot place a label, or a plate would need more than 24 detail views.
- Tests: NestPdfLayoutTests (multi-plate totals/ranges/same-named
  references, table continuation with no lost rows, long name/notes
  wrapping without column overflow, mm units in all four quadrants,
  dense-label detail views with hole avoidance, save/reload of the
  tabbed/lead-in fixture with stale tab flags, invalid later-plate data,
  late write failures against an existing destination, source
  unchanged on success/failure), ReportPdf test helper (page/word/
  content-stream extraction).
- docs/nest-reports.md: replace the Slice 1 one-plate limits section
  with the general pagination/dense-label contract and the process-wide
  PDFsharp/MigraDoc render lock.

Verification: Reporting filter 70/70; full OpenNest.Tests 2524
passed/21 skipped/0 failed; Engine 351/351; IO 77/77;
EnableWindowsTargeting=true full-solution build 0 errors; scoped
dotnet format --verify-no-changes exit 0. Preview PDFs rendered and
visually inspected (evidence: /home/aj/extracted/2026-09-29/opennest-report-slice2/).
Windows runtime, the desktop adapter and packaged-app font deployment
remain unverified (Slice 3).
This commit is contained in:
aj
2026-09-29 15:43:00 -04:00
parent e4d64a88eb
commit b15c4cf6c6
9 changed files with 1328 additions and 228 deletions
+32 -14
View File
@@ -33,22 +33,38 @@ the selected plate, or certify that a layout passed geometry/pre-post checks.
- Snapshots retain values, not live drawings, parts, CNC programs or mutable
geometry. Invalid/missing/nonfinite geometry fails with drawing/plate context.
## Initial layout and safety limits
## Layout, pagination and dense-label fallback
Slice 1 supports an empty/demand-only job or one plate layout: a Letter portrait
summary page and a Letter landscape plate page, each with "Page X of Y". The
writer rejects, with `NotSupportedException` and before touching the destination:
An empty/demand-only job, a single plate, or many plates and drawings all
produce one document: a Letter portrait summary followed by one Letter
landscape section per plate, every page carrying a repeated header and
"Page X of Y". Long notes, long drawing names, many drawings and many parts
per sheet paginate naturally: MigraDoc continues the Plates/Parts tables and
each plate's part table across pages with the heading row repeated, and notes
flow as an ordinary paragraph. No row, table or note text is ever dropped or
truncated to fit a page.
- more than one distinct plate layout;
- a summary (job fields, plate list, part rows with thumbnails) longer than one page;
- a plate page (header, diagram, part table) longer than one page;
- a part ID label that does not fit inside its part's fitted bounds at 7 pt, or
that overlaps another label.
Each part ID is centered on its part's pole of inaccessibility (`PolyLabel`,
the same method `PlateView`'s `LayoutPart` uses), computed on a
placement-independent quantized copy so identical parts always get the
identical label position and the label naturally clears a central hole. When
an ID cannot sit legibly inside its own material at overview scale (for
example, a cluster of tiny repeated parts), the plate gains a lettered
(rows)/numbered (columns) map grid drawn beneath the sheet, and only the
crowded cells get a zoomed, framed detail page listing that cell's real
coordinates. Detail-cell outlines use a long dash-dot stroke, distinct from
the shorter dashed scrap-cutoff stroke and the dotted grid lines. IDs are
never shrunk below 7 pt or silently dropped.
It never shrinks text, truncates rows or drops labels to make a layout fit.
Multi-plate pagination, dense-label callouts and the desktop command belong to
subsequent slices. Labels are centered on the part's bounds, which can place them
inside a central hole; smarter placement is part of the dense-label work.
The writer still rejects, with `NotSupportedException` and before touching
the destination:
- a page header (nest name plus plate/material line) needing more than 3
wrapped lines;
- a table cell needing more than 20 wrapped lines;
- a part ID that cannot be placed legibly even in the most zoomed supported
detail view, or a plate that would need more than 24 detail views to label
every part — named with the plate, part index and ID.
Summary thumbnails and the sheet diagram are vector paths, never raster images.
Each thumbnail is a small PDFsharp page embedded by MigraDoc as a form XObject.
@@ -62,7 +78,9 @@ otherwise draw a false segment across a tab gap.
A report is fully rendered to a unique temporary sibling before replacement of
its destination. A failed render or write leaves an existing report untouched and
removes temporary output. Applications should obtain overwrite consent before
calling the library.
calling the library. PDFsharp/MigraDoc layout and font state is process-wide;
`NestPdfWriter.Write` serializes every export behind one static lock so
concurrent calls cannot lay out text differently from a sequential export.
Advanced timing, cutting distances, pierce counts, weights, costs, gas use, and
machine/NC identity are intentionally omitted until their semantics are verified.