@json-to-office/jto-ops 4.1.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { FontRuntimeOpts, ServicesConfig, GenerationWarning, RendererStatus, PptxBatchRasterizer, PptxRasterizer, ResolvedFont } from '@json-to-office/shared';
2
- import { QualityProfile, QualityPolicy, PreparedDocument, QualityAnalysis, QualityDiagnostic } from '@json-to-office/quality';
1
+ import { FontRuntimeOpts, ServicesConfig, GenerationWarning, RendererStatus, PptxBatchRasterizer, PptxRasterizer, JsonBlockDefinition, BlockSlot, ResolvedFont } from '@json-to-office/shared';
2
+ import { QualityProfile, QualityPolicy, PreparedDocument, QualityAnalysis, QualityRulePack, QualityDiagnostic, QualityAnalyzeOptions } from '@json-to-office/quality';
3
3
 
4
4
  type FormatName = 'docx' | 'pptx';
5
5
  interface GeneratorOptions {
@@ -401,6 +401,20 @@ declare function extractPdfFonts(pdfPath: string, options?: {
401
401
  timeoutMs?: number;
402
402
  }): Promise<PdfFontInfo[]>;
403
403
 
404
+ /**
405
+ * The rendered pass as a quality rule pack (#344).
406
+ *
407
+ * `draftRenderedFindings` measures; these rules are how the measurement
408
+ * enters the quality contract. Each rule owns one code, one description the
409
+ * design guide prints, and the defaults a profile or policy can move. All of
410
+ * them read a single `rendered/geometry` fact — the PDF's word boxes, its
411
+ * embedded fonts and the document's text inventory — and share one matching
412
+ * pass over it, memoised per fact, so eight rules cost one search.
413
+ */
414
+
415
+ type RenderedRuleId = 'rendered/clip' | 'rendered/spill' | 'rendered/overlap' | 'rendered/text-missing' | 'rendered/font-substituted' | 'rendered/empty-page' | 'rendered/heading-stranded' | 'rendered/paragraph-split';
416
+ declare const RENDERED_QUALITY_RULES: QualityRulePack;
417
+
404
418
  /**
405
419
  * Locate authored text in rendered PDF geometry.
406
420
  *
@@ -456,12 +470,34 @@ interface TextOccurrence {
456
470
  endPageIndex: number;
457
471
  parts: OccurrencePart[];
458
472
  }
473
+ /**
474
+ * Word indices in the order a reader takes them, decided from geometry
475
+ * rather than from the order poppler emitted them: poppler 24 lists a table
476
+ * row line by line across every cell, poppler 26 lists it cell by cell, and
477
+ * an authored string in a wrapped cell only survives the second.
478
+ *
479
+ * Rows are grouped by vertical overlap and split into fragments at gaps
480
+ * wider than half the line is tall. A row of two or more fragments opens a
481
+ * column block; the rows under it belong to the block while each of their
482
+ * fragments sits inside one column (a right-aligned cell's second line
483
+ * starts further right; a wrapped label's later lines are the only
484
+ * fragment of their row) and the rows stay a line apart. A block reads
485
+ * column by column, so a cell's lines come out together; a row of one
486
+ * fragment outside any block — prose — reads as it lies.
487
+ */
488
+ declare function readingOrder(words: readonly PdfTextWord[]): number[];
459
489
  /** What an inventory entry needs to be matched: its text, in reading order. */
460
490
  interface InventoryEntry {
461
491
  path: string;
462
492
  text: string;
463
493
  /** Header/footer text repeats per page: every occurrence is legitimate. */
464
494
  repeats?: boolean;
495
+ /**
496
+ * Painted by the renderer from other authored text — a contents entry —
497
+ * so it claims its occurrence when there is one and is `skipped`, never
498
+ * `missing`, when there is not.
499
+ */
500
+ optional?: boolean;
465
501
  }
466
502
  type MappingStatus = 'mapped' | 'ambiguous' | 'missing' | 'skipped';
467
503
  interface InventoryMatch<T extends InventoryEntry = InventoryEntry> {
@@ -493,8 +529,8 @@ interface InventoryAssignment<T extends InventoryEntry> {
493
529
  * stream without those words; each claims the first unclaimed occurrence at
494
530
  * or after the previous claim, which is how "Total" in the second table
495
531
  * finds the second "Total". An entry with no occurrence is `missing` —
496
- * fully clipped, or never set — and one whose every occurrence was already
497
- * claimed is `ambiguous`.
532
+ * fully clipped, or never set — unless it is optional, and one whose every
533
+ * occurrence was already claimed is `ambiguous`.
498
534
  */
499
535
  declare function assignInventory<T extends InventoryEntry>(pages: readonly PdfTextPage[], inventory: readonly T[]): InventoryAssignment<T>;
500
536
 
@@ -518,11 +554,17 @@ declare function assignInventory<T extends InventoryEntry>(pages: readonly PdfTe
518
554
  * Pure: geometry and fonts are extracted by the caller (see
519
555
  * `extractPdfTextGeometry`, `extractPdfFonts`), so every rule here is
520
556
  * testable from captured fixtures with no converter on the host.
557
+ *
558
+ * The checks draft findings; the quality engine turns them into
559
+ * diagnostics. Each check is one rule of `RENDERED_QUALITY_RULES`, so a
560
+ * profile can switch one off or move its severity, a policy can suppress
561
+ * one at a pointer and a gate can make one blocking — the same levers every
562
+ * static rule answers to, applied to the pass that measures.
521
563
  */
522
564
 
523
565
  /** One authored string the pass can match rendered words back to. */
524
566
  interface RenderedTextEntry extends InventoryEntry {
525
- role: 'heading' | 'body' | 'list-item' | 'table-header' | 'table-cell' | 'statistic' | 'caption' | 'chrome' | 'slide-text';
567
+ role: 'heading' | 'body' | 'list-item' | 'table-header' | 'table-cell' | 'statistic' | 'caption' | 'toc-entry' | 'chrome' | 'slide-text';
526
568
  level?: number;
527
569
  /** A declared box the text must fit: a docx frame, a pptx text box. */
528
570
  box?: {
@@ -544,6 +586,8 @@ interface RequestedFont {
544
586
  }
545
587
  interface RenderedAnalysisInput {
546
588
  format: 'docx' | 'pptx';
589
+ /** Renderer identity, for a profile that declares renderer targets. */
590
+ renderer?: string;
547
591
  pages: readonly PdfTextPage[];
548
592
  inventory: readonly RenderedTextEntry[];
549
593
  /** Fonts the PDF carries; `undefined` when the host could not inspect them. */
@@ -557,21 +601,159 @@ interface RenderedAnalysisSummary {
557
601
  words: number;
558
602
  /** Inventory entries by mapping outcome. */
559
603
  inventory: Record<MappingStatus, number>;
560
- /** Findings by mapping outcome. */
604
+ /** Findings by mapping outcome, after suppressions. */
561
605
  findings: Record<RenderedMapping, number>;
562
606
  fonts?: {
563
607
  requested: number;
564
608
  substituted: number;
565
609
  };
610
+ /** Findings a policy suppression removed. */
611
+ suppressed: number;
612
+ /** Whether a policy gate made any finding blocking. */
613
+ blocked: boolean;
614
+ /** Whether a policy `maxDiagnostics` budget cut the findings returned. */
615
+ truncated: boolean;
616
+ /** The quality profile the pass ran under, when one applied. */
617
+ profileId?: string;
566
618
  }
567
619
  interface RenderedAnalysis {
568
- findings: QualityDiagnostic[];
620
+ /** The pass's diagnostics, as the quality engine finalised them. */
621
+ findings: readonly QualityDiagnostic[];
569
622
  summary: RenderedAnalysisSummary;
623
+ /** The engine's own account: evaluated rules, rule errors, truncation. */
624
+ analysis: QualityAnalysis;
570
625
  }
571
626
  /** Spill below this is sub-visual: renderer rounding, descender fuzz. */
572
627
  declare const VISIBLE_SPILL_PT = 2;
573
- /** Run the pass. */
574
- declare function analyzeRenderedDocument(input: RenderedAnalysisInput): RenderedAnalysis;
628
+ /**
629
+ * Run the pass. Geometry, fonts and inventory become one `rendered/geometry`
630
+ * fact; the rendered rule pack reads it under the caller's profile and
631
+ * policy, so the findings come back suppressed, re-severed and gated the
632
+ * way `jto_validate`'s would.
633
+ */
634
+ declare function analyzeRenderedDocument(input: RenderedAnalysisInput, options?: QualityAnalyzeOptions): RenderedAnalysis;
635
+
636
+ /**
637
+ * The block boundary matrix (#343, report portion): every JSON block
638
+ * definition a playground template embeds, invoked at the edges of its own
639
+ * slot schema — minimum and maximum cardinality, every string at its word
640
+ * budget, every figure at its widest — on each bundled theme, with the
641
+ * design fonts and with the fallback faces LibreOffice substitutes when a
642
+ * host lacks them, on A4 and on Letter.
643
+ *
644
+ * The generator is pure and reads nothing but the template it is handed:
645
+ * a definition gains coverage by being embedded, never by being listed here.
646
+ * What the suites do with a case — the static rules under the archetype's
647
+ * profile, the rendered pass over the LibreOffice PDF — is theirs; this
648
+ * module only says what a boundary document is.
649
+ *
650
+ * Two shapes come out. A *block case* is a small report — cover, running
651
+ * head, section opener — with one block at an edge in the body, so a
652
+ * finding names the block that caused it. A *report case* is the template's
653
+ * own document with every invocation at that edge, so the blocks meet each
654
+ * other the way they do in a real report. Both carry their definitions and
655
+ * dependencies inline, so nothing is resolved at render time.
656
+ */
657
+
658
+ type Rec = Record<string, unknown>;
659
+ type MatrixEdge = 'min' | 'max';
660
+ type MatrixFont = 'design' | 'fallback';
661
+ type MatrixCanvas = 'A4' | 'LETTER';
662
+ interface BlockMatrixDefinition {
663
+ name: string;
664
+ /** The template the definition is embedded in, as the catalog names it. */
665
+ template: string;
666
+ /** Where inside that template: `/props/blocks/<name>`. */
667
+ pointer: string;
668
+ definition: JsonBlockDefinition;
669
+ /** The template's own first invocation, when it has one: the nominal fill. */
670
+ example?: Rec;
671
+ }
672
+ interface BlockMatrixCase {
673
+ id: string;
674
+ /** The block at its edge, or `report` for the whole template at that edge. */
675
+ block: string;
676
+ edge: MatrixEdge;
677
+ theme: string;
678
+ font: MatrixFont;
679
+ canvas: MatrixCanvas;
680
+ document: Rec;
681
+ }
682
+ interface BlockMatrixOptions {
683
+ themes: readonly string[];
684
+ fonts?: readonly MatrixFont[];
685
+ edges?: readonly MatrixEdge[];
686
+ canvases?: readonly MatrixCanvas[];
687
+ /** Whether the whole-template report cases are generated. Default true. */
688
+ report?: boolean;
689
+ /** Whether the per-block cases are generated. Default true. */
690
+ blocks?: boolean;
691
+ /**
692
+ * Slot roles the profile under test requires present, so `min` keeps them:
693
+ * the client-report profile's `takeaway` and `source`.
694
+ */
695
+ requiredRoles?: readonly string[];
696
+ }
697
+ interface CaseConditions {
698
+ font?: MatrixFont;
699
+ canvas?: MatrixCanvas;
700
+ requiredRoles?: readonly string[];
701
+ }
702
+ /**
703
+ * The face every LibreOffice ships with, bundled inside the application on
704
+ * each platform: what a design font degrades to when the host lacks it, and
705
+ * wider than Arial or Calibri, so a fit at these metrics is a fit anywhere.
706
+ * Only the roles a report paints are overridden: a family declared for a
707
+ * role nothing uses is never embedded, and the rendered pass would report
708
+ * it as substituted.
709
+ */
710
+ declare const FALLBACK_FONTS: {
711
+ readonly heading: {
712
+ readonly family: "DejaVu Sans";
713
+ };
714
+ readonly body: {
715
+ readonly family: "DejaVu Sans";
716
+ };
717
+ };
718
+ /** A 4x2 PNG: an image with an aspect ratio, and no bytes outside the process. */
719
+ declare const MATRIX_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC";
720
+ /**
721
+ * The widest number of a given length: a true minus, thousands separators
722
+ * and one decimal, every glyph at tabular width. `−1,234,567.0` for 12.
723
+ */
724
+ declare function widestNumber(length: number, seed?: number): string;
725
+ /** A slot's value at an edge; `undefined` means "leave it out". */
726
+ declare function boundarySlotValue(slot: BlockSlot, name: string, edge: MatrixEdge, seed?: number): unknown;
727
+ /**
728
+ * One invocation of a definition with every slot at the edge: optional slots
729
+ * left out at `min` (so defaults are what renders), every slot at `max`.
730
+ * A slot whose role the profile requires — a source under every figure —
731
+ * is never optional to that profile, so `requiredRoles` keeps it at `min`.
732
+ * Columns whose cells must count the rows do; nothing else knows a block.
733
+ */
734
+ declare function boundaryInvocation(name: string, definition: JsonBlockDefinition, edge: MatrixEdge, seed?: number, requiredRoles?: readonly string[]): Rec;
735
+ /**
736
+ * The same invocation one past the edge: the first budgeted string a word
737
+ * over, or the first bounded array an item over. What a slot violation
738
+ * looks like, so a suite can prove it is reported at the authored pointer.
739
+ */
740
+ declare function overBudgetInvocation(name: string, definition: JsonBlockDefinition, seed?: number): {
741
+ invocation: Rec;
742
+ slot: string;
743
+ kind: 'words' | 'items';
744
+ } | undefined;
745
+ /** Every definition a template embeds, with the template's own example of it. */
746
+ declare function enumerateBlockDefinitions(template: unknown, templateName: string): BlockMatrixDefinition[];
747
+ /**
748
+ * A small report with one block at its edge: the chrome at the template's
749
+ * nominal values (or at the edge, when the chrome block is the one under
750
+ * test), the block in the first body section, body copy after it.
751
+ */
752
+ declare function blockCaseDocument(entries: readonly BlockMatrixDefinition[], name: string, edge: MatrixEdge, theme: string, { font, canvas, requiredRoles }?: CaseConditions): Rec;
753
+ /** The whole template with every invocation at the edge. */
754
+ declare function reportCaseDocument(template: unknown, edge: MatrixEdge, theme: string, { font, canvas, requiredRoles }?: CaseConditions): Rec;
755
+ /** Every case the options span, in a stable order. */
756
+ declare function generateBlockMatrix(template: unknown, templateName: string, options: BlockMatrixOptions): BlockMatrixCase[];
575
757
 
576
758
  /**
577
759
  * Make resolved fonts visible to the LibreOffice child process for the
@@ -764,4 +946,4 @@ declare function emitDiagnostic(text: string, tone?: DiagnosticTone): void;
764
946
  */
765
947
  declare const stderrDiagnosticSink: DiagnosticSink;
766
948
 
767
- export { type DiagnosticSink, type DiagnosticTone, DocxFormatAdapter, type FontStageHandle, type FontStageOptions, type FontStager, FontconfigStager, type FormatAdapter, type FormatName, type GeneratorOptions, type GeneratorResult, type InventoryEntry, type InventoryMatch, MacOSCoreTextStager, type MappingStatus, NoopFontStager, type PdfFontInfo, type PdfTextLine, type PdfTextPage, type PdfTextWord, PptxFormatAdapter, type RasterizerCacheStats, type RenderedAnalysis, type RenderedAnalysisInput, type RenderedAnalysisSummary, type RenderedMapping, type RenderedTextEntry, type RequestedFont, type TextOccurrence, VISIBLE_SPILL_PT, WindowsFontStager, analyzeRenderedDocument, assignInventory, authoredTextForMatch, clearRasterizerCache, createAdapter, createLibreOfficePptxBatchRasterizer, createLibreOfficePptxRasterizer, emitDiagnostic, extractPdfFonts, extractPdfTextGeometry, familyRendered, getFontStager, getRasterizerCacheStats, normalizeForMatch, parsePdfFonts, parsePdfTextBbox, pdffontsAvailable, pdftotextAvailable, runWithDiagnosticSink, stderrDiagnosticSink };
949
+ export { type BlockMatrixCase, type BlockMatrixDefinition, type BlockMatrixOptions, type CaseConditions, type DiagnosticSink, type DiagnosticTone, DocxFormatAdapter, FALLBACK_FONTS, type FontStageHandle, type FontStageOptions, type FontStager, FontconfigStager, type FormatAdapter, type FormatName, type GeneratorOptions, type GeneratorResult, type InventoryEntry, type InventoryMatch, MATRIX_IMAGE, MacOSCoreTextStager, type MappingStatus, type MatrixCanvas, type MatrixEdge, type MatrixFont, NoopFontStager, type PdfFontInfo, type PdfTextLine, type PdfTextPage, type PdfTextWord, PptxFormatAdapter, RENDERED_QUALITY_RULES, type RasterizerCacheStats, type RenderedAnalysis, type RenderedAnalysisInput, type RenderedAnalysisSummary, type RenderedMapping, type RenderedRuleId, type RenderedTextEntry, type RequestedFont, type TextOccurrence, VISIBLE_SPILL_PT, WindowsFontStager, analyzeRenderedDocument, assignInventory, authoredTextForMatch, blockCaseDocument, boundaryInvocation, boundarySlotValue, clearRasterizerCache, createAdapter, createLibreOfficePptxBatchRasterizer, createLibreOfficePptxRasterizer, emitDiagnostic, enumerateBlockDefinitions, extractPdfFonts, extractPdfTextGeometry, familyRendered, generateBlockMatrix, getFontStager, getRasterizerCacheStats, normalizeForMatch, overBudgetInvocation, parsePdfFonts, parsePdfTextBbox, pdffontsAvailable, pdftotextAvailable, readingOrder, reportCaseDocument, runWithDiagnosticSink, stderrDiagnosticSink, widestNumber };