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.
60 lines
2.6 KiB
C#
60 lines
2.6 KiB
C#
using PdfSharp.Fonts;
|
|
|
|
namespace OpenNest.Reporting;
|
|
|
|
/// <summary>Process-wide, embedded fonts; never consult platform font directories.</summary>
|
|
internal static class ReportFonts
|
|
{
|
|
internal const string Family = "OpenNest Report Sans";
|
|
private static readonly object Sync = new();
|
|
private static readonly BundledResolver Resolver = new();
|
|
|
|
internal static void Initialize()
|
|
{
|
|
lock (Sync)
|
|
{
|
|
if (GlobalFontSettings.FontResolver == null)
|
|
GlobalFontSettings.FontResolver = Resolver;
|
|
else if (!ReferenceEquals(GlobalFontSettings.FontResolver, Resolver))
|
|
throw new InvalidOperationException("Nest reports require their bundled font resolver before PDF fonts are created. Another resolver is already installed.");
|
|
// MigraDoc otherwise asks the resolver for a platform "Courier New" error font.
|
|
MigraDoc.PredefinedFontsAndChars.ErrorFontName = Family;
|
|
}
|
|
}
|
|
|
|
internal static void ValidateText(string text, string field)
|
|
{
|
|
// Slice 1 deliberately supports printable Latin-1, not general Unicode shaping.
|
|
// Reject unavailable text explicitly instead of substituting .notdef boxes or hiding it.
|
|
foreach (var rune in text.EnumerateRunes())
|
|
{
|
|
var value = rune.Value;
|
|
if (value is '\r' or '\n' or '\t' || value is >= 0x20 and <= 0x7e
|
|
|| value is >= 0xa0 and <= 0xff && value != 0xad)
|
|
continue;
|
|
throw new InvalidOperationException($"{field}: unsupported report character U+{value:X4}. This report slice supports printable Latin-1 text only.");
|
|
}
|
|
}
|
|
|
|
private sealed class BundledResolver : IFontResolver
|
|
{
|
|
public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
|
|
{
|
|
if (!string.Equals(familyName, Family, StringComparison.OrdinalIgnoreCase) || isItalic)
|
|
return null;
|
|
return new FontResolverInfo(isBold ? "DejaVuSans-Bold" : "DejaVuSans");
|
|
}
|
|
|
|
public byte[] GetFont(string faceName)
|
|
{
|
|
if (faceName is not ("DejaVuSans" or "DejaVuSans-Bold"))
|
|
throw new InvalidOperationException($"Unknown report font face: {faceName}.");
|
|
using var source = typeof(ReportFonts).Assembly.GetManifestResourceStream($"OpenNest.Reporting.Fonts.{faceName}.ttf")
|
|
?? throw new InvalidOperationException($"Bundled report font missing: {faceName}.");
|
|
using var bytes = new MemoryStream();
|
|
source.CopyTo(bytes);
|
|
return bytes.ToArray();
|
|
}
|
|
}
|
|
}
|