feat(reporting): add one-plate nest report PDF library slice

Add OpenNest.Reporting (net8.0, PDFsharp-MigraDoc 6.2.4) with a detached
report snapshot and a PDF writer for an empty/demand-only job or a single
plate layout: Letter portrait summary with vector part thumbnails and a
landscape plate page with a vector sheet diagram, both with Page X of Y.

- Snapshot uses reference identity for document-local R### IDs, recounts
  nested quantities from placements x copies with checked wide integers,
  excludes cutoffs from accounting, and retains no live domain objects.
- Geometry keeps Cut/Display material paths, drops rapid/scribe/lead paths,
  preserves native arcs, holes and intentional tab gaps, and rejects
  malformed programs with plate/part identification.
- Open contours and cutoffs are stroked per contour so PDFsharp cannot join
  a tab gap to the next contour.
- Bundled DejaVu Sans via a custom resolver (including MigraDoc's error
  font); unsupported text fails with the field and code point.
- Layouts beyond this slice (multiple plates, page overflow, labels that
  do not fit) fail with NotSupportedException before the destination is
  replaced; output is rendered to a temporary sibling and moved atomically.
This commit is contained in:
aj
2026-09-29 13:13:32 -04:00
parent 64d0e50df3
commit 74d2b6aa68
20 changed files with 3201 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
# Nest report PDF library
The first delivery is a bounded, cross-platform library slice, not yet a desktop
menu command or a general report layout engine. Capture a stable `Nest` on its
owning thread, then render only the detached snapshot:
```csharp
var snapshot = NestReportBuilder.Capture(nest, DateTimeOffset.Now);
NestPdfWriter.Write(snapshot, destinationPath);
```
Reporting does not select a post, generate CNC, refresh drawing quantities, change
the selected plate, or certify that a layout passed geometry/pre-post checks.
## Snapshot contract
- IDs (`R001`, ...) are local to the report. Visit non-cutoff placements in plate
and part order, then append unseen demanded drawings ordered by ordinal name and
source path. Identity is by reference: distinct same-named drawings stay distinct,
including placed drawings absent from the name-keyed drawing collection.
- Recount nested quantities from placements times sheet copies exactly once using
checked wide integers. Include unplaced demand; report shortage and extra
separately. Cached `Quantity.Nested` is not authoritative.
- Distinguish distinct layouts from physical sheets. Per-sheet quantities do not
include copies; total quantities do. Utilization uses net part area divided by
full sheet area, excluding cutoffs.
- Actual placed programs already contain rotations. Capture their material paths
with the location applied once. Preserve holes and intentional tab gaps; exclude
rapid, scribe, lead-in and lead-out paths. Keep Cut and Display contours.
- If any material contour of a part is open, show all its contours without fill.
Do not close the gap or substitute the drawing's nominal outline. Cutoffs are
separate strokes with no product ID or quantity.
- 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
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:
- 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.
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.
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.
The diagram is drawn into a fixed-height table row reserved in MigraDoc's flow,
located after layout with `DocumentRenderer.GetRenderInfoFromPage`. Closed parts
use one even-odd filled path, so holes stay unfilled. Parts with any open contour,
and cutoffs (dashed), stroke each contour as its own path: PDFsharp's
`StartFigure` does not start a new subpath after an open figure, and would
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.
Advanced timing, cutting distances, pierce counts, weights, costs, gas use, and
machine/NC identity are intentionally omitted until their semantics are verified.
The PDF can be opened or printed through a normal PDF viewer; its fitted diagram
is not a dimensioned cutting drawing.
## Backend, fonts and redistribution
- Official `PDFsharp-MigraDoc` Core **6.2.4**, with `PDFsharp` **6.2.4**, targeting
net8.0. No GDI/WPF dependency or printer driver is used. MigraDoc owns flowing
document layout; PDFsharp draws vectors into its measured reserved areas.
- DejaVu Sans **2.37**, regular and bold, is bundled unmodified as assembly
resources. Source: https://dejavu-fonts.github.io/ ; binary provenance:
Ubuntu `fonts-dejavu-core` **2.37-8**. Both required faces are embedded in PDFs;
neither generation nor viewing needs a platform font installation.
- A custom resolver is installed once before fonts are created. Repeated exports
reuse it. An already-installed foreign resolver is an explicit integration
error, never silently replaced. A host that already uses PDFsharp needs resolver
composition before adding report support. MigraDoc's predefined error font is
pointed at the bundled family; its default (`Courier New`) is a platform font
the bundled resolver deliberately cannot supply.
- The initial text contract is printable ASCII/Latin-1 and line/tab separators.
Other codepoints, controls and soft hyphens fail with the field and codepoint;
there is no silent missing-glyph substitution. Broader Unicode/shaping support
is deferred rather than implied by the font's larger character inventory.
- PDFsharp/MigraDoc are MIT-licensed. The actual v6.2.4 license and the Microsoft
transitive dependency notices are in `OpenNest.Reporting/THIRD-PARTY-NOTICES.txt`.
Font permission/redistribution terms are in `OpenNest.Reporting/Fonts/LICENSE.txt`
(Bitstream Vera permission with DejaVu changes in the public domain). Preserve
both files in `ReportingNotices/` when publishing; embedded fonts do not replace
the obligation to ship their notices. Package notices are deduplicated, not
rewritten or shortened.
## Verification
```sh
dotnet test OpenNest.Tests/OpenNest.Tests.csproj --filter FullyQualifiedName~Reporting
```
The writer tests read page sizes and content streams with PDFsharp. Text
assertions extract with Poppler's `pdftotext -layout` and are skipped, not
passed, when poppler-utils is absent. For manual inspection also use `pdfinfo`,
`pdffonts` and `pdftoppm -png` (Linux verification baseline: poppler-utils
24.02.0). Confirm page sizes, quantity rows, page X of Y, embedded fonts, native
vector curves, unfilled holes and visible tab gaps with no connecting stroke.
Pixel inspection supplements geometry assertions; it is not a CNC-validation
result.
Windows compilation is not runtime acceptance. The later desktop adapter must be
tested on Windows for busy-operation guards, cancellation, overwrite handling,
unchanged job state and PDF viewing/printing, including the packaged application.