feat: T6 wave 2 – medicine book, document rights, label particulars, herd events
- Veterinary medicine catalogue, lots and append-only movement ledger (rest always computed; corrections as counter-bookings; inactive medicines take no new stock; unit must match on lot moves). - Documents of medicine lots, authority notices and restrictions need the domain right on top of documents:* (403 before any lookup). - Label particulars: pure checkLabel with six mandatory gaps and four warnings, AFM font metrics, Berlin-day dates, geometric reading order for split addresses, new master-data fields with row-locked updates. - Herd events ColonyCreated, ColonyMoved, ApiaryChanged and TskReferenceDayReached (ids only, never forwarded to webhooks); CSV import emits ColonyCreated with audit. - ARCHITECTURE.md §12 Compliance (T6), Annahmen 59–70. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
8a31c42578
commit
c18860c75c
73 files changed
+7121
-309
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import PDFDocument from 'pdfkit';
|
||||
import { LabelFontFamily, LabelTextElement } from './label-element.types';
|
||||
|
||||
export const MM_TO_PT = 72 / 25.4;
|
||||
|
||||
/** Glyph metrics in AFM units (1/1000 of the font size). */
|
||||
export interface FontMetrics {
|
||||
xHeight: number;
|
||||
capHeight: number;
|
||||
}
|
||||
|
||||
export interface TextMeasurer {
|
||||
/** Height in pt the element's text needs at the element's width and font. */
|
||||
heightOfStringPt(text: string, element: LabelTextElement): number;
|
||||
}
|
||||
|
||||
/** The pdfkit standard font a family/style combination prints with. */
|
||||
export function pdfFontName(
|
||||
family: LabelFontFamily,
|
||||
bold: boolean,
|
||||
italic: boolean,
|
||||
): string {
|
||||
const base =
|
||||
family === 'serif' ? 'Times' : family === 'mono' ? 'Courier' : 'Helvetica';
|
||||
if (base === 'Times') {
|
||||
if (bold && italic) return 'Times-BoldItalic';
|
||||
if (bold) return 'Times-Bold';
|
||||
if (italic) return 'Times-Italic';
|
||||
return 'Times-Roman';
|
||||
}
|
||||
const suffix =
|
||||
bold && italic ? '-BoldOblique' : bold ? '-Bold' : italic ? '-Oblique' : '';
|
||||
return `${base}${suffix}`;
|
||||
}
|
||||
|
||||
/** Height in mm of a glyph class: size in pt x metric / 1000 x 25.4 / 72. */
|
||||
export function heightMm(fontSizePt: number, metricPer1000: number): number {
|
||||
return ((fontSizePt * metricPer1000) / 1000) * (25.4 / 72);
|
||||
}
|
||||
|
||||
// pdfkit keeps the loaded font in the private field `_font`; this is the one
|
||||
// place that reads it. font-metrics.spec.ts pins the values, so a pdfkit
|
||||
// update that moves or renames the field fails there and nowhere else.
|
||||
interface PdfDocumentInternals {
|
||||
_font?: { xHeight?: unknown; capHeight?: unknown };
|
||||
}
|
||||
|
||||
const metricsCache = new Map<string, FontMetrics>();
|
||||
|
||||
/** AFM x-height and cap height of a built-in PDF font. */
|
||||
export function fontMetricsOf(
|
||||
family: LabelFontFamily,
|
||||
bold: boolean,
|
||||
italic: boolean,
|
||||
): FontMetrics {
|
||||
const name = pdfFontName(family, bold, italic);
|
||||
const cached = metricsCache.get(name);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 0 });
|
||||
doc.font(name);
|
||||
const font = (doc as unknown as PdfDocumentInternals)._font;
|
||||
const { xHeight, capHeight } = font ?? {};
|
||||
if (
|
||||
typeof xHeight !== 'number' ||
|
||||
typeof capHeight !== 'number' ||
|
||||
!(xHeight > 0) ||
|
||||
!(capHeight > 0)
|
||||
) {
|
||||
throw new Error(`No AFM metrics for font ${name}`);
|
||||
}
|
||||
const metrics = { xHeight, capHeight };
|
||||
metricsCache.set(name, metrics);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/** Measures text with a throw-away PDF document; nothing is written out. */
|
||||
export function pdfTextMeasurer(): TextMeasurer {
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 0 });
|
||||
return {
|
||||
heightOfStringPt(text, element) {
|
||||
if (text === '') {
|
||||
return 0;
|
||||
}
|
||||
return doc
|
||||
.font(pdfFontName(element.fontFamily, element.bold, element.italic))
|
||||
.fontSize(element.fontSize)
|
||||
.heightOfString(text, { width: element.width * MM_TO_PT });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user