pdf-codec 5.6.1 → 5.7.1

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/README.md CHANGED
@@ -283,7 +283,7 @@ The package is layered from generic primitives outward to the codec itself:
283
283
  - **Embedded text faces**: `embedded-font.ts` (parses one TrueType-outline face's metrics and `GPOS` pair kerning, and `encodeForShowEmbedded` — the single code path both measurement and text-showing go through so encoding and measuring cannot disagree). Every geometry field is converted into PDF's 1000-units-per-em glyph space. `embedded-font-write.ts` builds the `/Type0`/`/CIDFontType2`/`/FontDescriptor`/`/FontFile2`/ToUnicode group, with `/CIDToGIDMap /Identity` written explicitly and `/Length1` set to the **uncompressed** subset length. Its subset tag is a CRC32 over the face's PostScript name and glyph-ID list, so identical input yields byte-identical output.
284
284
  - **ToUnicode CMaps**: `tounicode.ts`, shared by both embedded-font writers — a character code → Unicode code point mapping written as a bfchar CMap (9.10.3), with supplementary-plane code points encoded as UTF-16BE surrogate pairs and entries emitted in blocks of at most 100.
285
285
  - **CFF reading**: `cff.ts` (shared `INDEX`/`DICT` container structures), `cff-bounds.ts` (a Type 2 charstring interpreter computing each glyph's tight ink bounding box by tracking the current point through every path operator and solving each cubic's real extrema from the roots of its derivative — a path walker, not a rasteriser; verified against the vendored STIX Two Math font's whole 5,543-glyph repertoire, matching fontTools' `BoundsPen` to within 0.01 design units). `cff-probe.ts` reads a bare CFF program's header/Name INDEX/Top DICT to detect the `ROS` operator defining a CID-keyed font — the guard a future source-embedded-font phase needs before it can trust CID == GID against an arbitrary caller-supplied font.
286
- - **Read**: `lexer.ts`/`parse.ts` (byte tokenizer and tokens → `PdfObject`), `filters.ts`/`predictors.ts` (Flate/LZW/ASCII85/ASCIIHex/RunLength/CCITTFax, TIFF/PNG predictors), `xref.ts`/`document.ts` (classic and cross-reference-stream resolution, object streams, `/Prev` chains, linear-scan recovery, the page tree with attribute inheritance), `encrypt.ts` (standard security handler: `/Encrypt` parsing, empty-user-password key derivation and `/U` verification, per-object keys, transparent string/stream decryption), `content-read.ts`/`interpret.ts` (content-stream tokenizer and graphics/text state machine, form-XObject recursion, general vector-path tracking), `cmap.ts`/`font-style.ts`/`font-read.ts` (`/ToUnicode` CMaps, font-dictionary resolution), `builtin-encoding.ts` (a font's own built-in encoding, read out of the embedded program itself — see [Text extraction and font encodings](#text-extraction-and-font-encodings)), `images-read.ts` (Image XObjects → PNG/JPEG bytes), `names.ts` (the document-level name-tree walker: one flattening pass for every `/Names` tenant), `navigation.ts` (named destinations from `/Dests` and `/Names` `/Dests`, reader-minted entries for direct destination arrays, and the `/Outlines` bookmark tree), `attachments.ts` (embedded files from the name tree, `/FileAttachment` filespecs, and `/AF`), `optional-content.ts` (`/OCProperties` groups and default-configuration visibility, plus the `/OC`-to-name resolution `interpret.ts` stamps onto span items), `annotations.ts` (sticky notes, FreeText, the `/QuadPoints` markup family, and residue for the opaque kinds), `form.ts` (the AcroForm field tree), `structure.ts` (the tagged-PDF `/StructTreeRoot` element tree with `/RoleMap` resolution and `/ClassMap` attribute merging, plus the `/ParentTree` walk that resolves each `(page, MCID)` pair to the owning element `read.ts` stamps onto an item), `xmp.ts` (a bounded Dublin Core extractor for the `/Metadata` packet), `pdf-text.ts` (PDF string/date scalar decoding), `read.ts` (`readPdf`, assembling all of the above into a `LayoutDocument`).
286
+ - **Read**: `lexer.ts`/`parse.ts` (byte tokenizer and tokens → `PdfObject`), `filters.ts`/`predictors.ts` (Flate/LZW/ASCII85/ASCIIHex/RunLength/CCITTFax, TIFF/PNG predictors), `xref.ts`/`document.ts` (classic and cross-reference-stream resolution, object streams, `/Prev` chains, linear-scan recovery, the page tree with attribute inheritance), `encrypt.ts` (standard security handler: `/Encrypt` parsing, empty-user-password key derivation and `/U` verification, per-object keys, transparent string/stream decryption), `content-read.ts`/`interpret.ts`/`interpret-state.ts` (content-stream tokenizer and graphics/text state machine, with the state records and the per-glyph displacement arithmetic in the last, form-XObject recursion, general vector-path tracking), `cmap.ts`/`font-style.ts`/`font-read.ts` (`/ToUnicode` CMaps, font-dictionary resolution), `builtin-encoding.ts` (a font's own built-in encoding, read out of the embedded program itself — see [Text extraction and font encodings](#text-extraction-and-font-encodings)), `images-read.ts` (Image XObjects → PNG/JPEG bytes), `names.ts` (the document-level name-tree walker: one flattening pass for every `/Names` tenant), `navigation.ts` (named destinations from `/Dests` and `/Names` `/Dests`, reader-minted entries for direct destination arrays, and the `/Outlines` bookmark tree), `attachments.ts` (embedded files from the name tree, `/FileAttachment` filespecs, and `/AF`), `optional-content.ts` (`/OCProperties` groups and default-configuration visibility, plus the `/OC`-to-name resolution `interpret.ts` stamps onto span items), `annotations.ts` (sticky notes, FreeText, the `/QuadPoints` markup family, and residue for the opaque kinds), `form.ts` (the AcroForm field tree), `structure.ts` (the tagged-PDF `/StructTreeRoot` element tree with `/RoleMap` resolution and `/ClassMap` attribute merging, plus the `/ParentTree` walk that resolves each `(page, MCID)` pair to the owning element `read.ts` stamps onto an item), `xmp.ts` (a bounded Dublin Core extractor for the `/Metadata` packet), `pdf-text.ts` (PDF string/date scalar decoding), `read.ts` (`readPdf`, assembling all of the above into a `LayoutDocument`).
287
287
  - **Raster**: `raster.ts` — the `PageRasteriser` port and `renderPdfPage` (#1198): re-walks one page through `interpretContentStream` (the same interpreter `readPdf` runs), recomputes the page's crop/rotation geometry (test-pinned to agree with `readPdf` bit for bit, since the `clipPt` contract rides on it), and drives a caller-supplied rasteriser with device-space draw ops — rect fills, filled/stroked vector paths, affine-placed images, and text as filled glyph-outline paths resolved from the page's own embedded sfnt programs (`glyf-contours.ts`'s decoded contours through the run's text matrices, per-glyph advances corrected against the run's exact end matrix). Optional-content layers the default configuration leaves OFF are not drawn; faces with no sfnt outlines are refused by name. No canvas dependency, no backend, no OCR/vision/network — the port is pure data plus callbacks, so `pdf-raster-cpu` (or a browser canvas wrapper) implements it elsewhere.
288
288
  - `codec.ts` — `pdfCodec`, a `z.codec()` pair over `readPdf`/`writePdf`, plus a standalone local copy of the `%PDF-` header check.
289
289
  - **`src/test-support/`** — hand-built PDF fixtures (`pdf.ts`) built by literal byte/string concatenation and deliberately importing NOTHING from this package's own writer (a fixture built by `writePdf` would let a writer bug hide from the corresponding reader test). `encrypted-pdfs.ts` applies the same principle: real PDFs encrypted by [qpdf](https://qpdf.sourceforge.io/), embedded as base64, so a bug in key derivation cannot cancel out between write and read halves. `fonts.ts` holds the real vendored Carlito and Caladea faces as raw sfnt bytes, and asserts values read out of the `.ttf` files by a standalone script with a bare `DataView`, not by this package's own parsers — external cross-checks rather than a parser's output compared against itself.
@@ -334,7 +334,7 @@ Reading a program's built-in encoding is what stops a symbol-encoded subset —
334
334
 
335
335
  ## Grouping text runs into lines and words
336
336
 
337
- `readPdf` reports one positioned run per text-showing operator, and a PDF says nothing about lines or words: a line of prose can arrive as one run, as one run per word, or as one run per glyph, entirely at the producer's discretion. `groupPdfTextRuns` turns those runs back into lines of words, and `pdf-codec/text-group` serves it without the write half or the vendored fonts, alongside `pdf-codec/read`.
337
+ `readPdf` reports one positioned run per text-showing operator, split wherever the operator itself opens a gap at least a word space wide between two of its glyphs (a `TJ` adjustment, a character spacing `Tc`, or the two summed, since a producer can set one against the other), and a PDF says nothing about lines or words: a line of prose can arrive as one run, as one run per word, or as one run per glyph, entirely at the producer's discretion. `groupPdfTextRuns` turns those runs back into lines of words, and `pdf-codec/text-group` serves it without the write half or the vendored fonts, alongside `pdf-codec/read`.
338
338
 
339
339
  ```ts
340
340
  import { readPdf } from "pdf-codec/read";
@@ -353,7 +353,7 @@ Two runs are on one line when their baselines sit within a fraction of an em of
353
353
 
354
354
  A run whose `widthPt` is absent states no advance, so where it ends is unknown and the gap after it is underivable. `runGapPt` reports `undefined` for that, and an undefined gap is **no evidence of a space**. Reading it as zero puts the previous run's end at its own start, turns its whole advance into an apparent gap, and produces "Com plete ly".
355
355
 
356
- Gaps that are derivable become a word space at an eighth of an em (the narrowest genuine one: the standard fourteen faces set their space glyph at 250/1000 em, and justified setting compresses to no less than half of that) and a column boundary past a full em (wider than the em space, the widest single space character there is), so a table's row reads as `"North\t4.2m\t11%"` rather than as one sentence. `runsShareBaseline` and `runGapPt` are exported on their own for a consumer with its own pipeline around them, and every threshold is overridable.
356
+ Gaps that are derivable become a word space at an eighth of an em (the narrowest genuine one: the standard fourteen faces set their space glyph at 250/1000 em, and justified setting compresses to no less than half of that) and a column boundary past a full em (wider than the em space, the widest single space character there is), so a table's row reads as `"North\t4.2m\t11%"` rather than as one sentence. `runsShareBaseline` and `runGapPt` are exported on their own for a consumer with its own pipeline around them, and every threshold is overridable. `scriptPositionOf(base, candidate)` says whether a run is a superscript or subscript of the run it touches: set at no more than three quarters of its size, with its baseline moved by more than a twentieth and at most half of its em (fonts declare script sizes about 0.65 em and offsets between 0.07 and 0.48 em), and no further than a word space from it along the baseline. A script raised past the same-baseline tolerance would otherwise be clustered as a line of its own, so `groupPdfTextRuns` returns it to its base's line, and `attachScriptRuns` does the same for a consumer that clusters lines itself. A line's text joins a script to its base like any touching run (`280m2`); `scriptPositionOf` on a word's `runs` is what tells a reader to raise or lower it.
357
357
 
358
358
  Limits, each inherent to what a PDF states rather than to the implementation: grouping is by baseline alone with no page segmentation, so on a multi-column page whose columns sit at different vertical offsets a line of one can fall within tolerance of a line of the next and the two are reported as one line with a column boundary between them, which is the cut a caller needs (`document-outline.js`'s `segmentPdfRegions` finds the gutter first if you want them genuinely apart); text comes out in visual order, so a right-to-left script needs the Unicode Bidi Algorithm applied to the result; a vertically set run groups into a column instead of a line, reported with a `rotationDeg` of 270 and its own `writingMode`, with columns ordered right to left; and a word hyphenated across a line end stays split, hyphen intact, because rejoining it is a paragraph-level judgement this package leaves to its consumers.
359
359
 
package/dist/index.cjs CHANGED
@@ -21,7 +21,7 @@ const require_write_skipped_surfaces = require("./write-skipped-surfaces.cjs");
21
21
  const require_write = require("./write.cjs");
22
22
  const require_codec = require("./codec.cjs");
23
23
  const require_font_face = require("./font-face.cjs");
24
- const require_raster = require("./raster-B8i3Tgjj.cjs");
24
+ const require_raster = require("./raster-De_zVqCu.cjs");
25
25
  exports.DEFAULT_BASELINE_TOLERANCE_EM = require_text_group.DEFAULT_BASELINE_TOLERANCE_EM;
26
26
  exports.DEFAULT_COLUMN_GAP_EM = require_text_group.DEFAULT_COLUMN_GAP_EM;
27
27
  exports.DEFAULT_VERTICAL_METRIC_POLICY = require_measure.DEFAULT_VERTICAL_METRIC_POLICY;
@@ -68,6 +68,7 @@ exports.PdfPasswordRequiredError = require_diagnostics.PdfPasswordRequiredError;
68
68
  exports.PdfSkippedSurfacesError = require_write_skipped_surfaces.PdfSkippedSurfacesError;
69
69
  exports.STANDARD_METRICS = require_afm_widths.STANDARD_METRICS;
70
70
  exports.assembleStretchyGlyph = require_math_stretch.assembleStretchyGlyph;
71
+ exports.attachScriptRuns = require_text_group.attachScriptRuns;
71
72
  exports.createFontMeasurer = require_measure.createFontMeasurer;
72
73
  exports.createFontRegistry = require_font_registry.createFontRegistry;
73
74
  exports.createStandardFontMeasurer = require_measure.createStandardFontMeasurer;
@@ -89,6 +90,7 @@ exports.resolveStandardFont = require_fonts.resolveStandardFont;
89
90
  exports.runGapPt = require_text_group.runGapPt;
90
91
  exports.runsShareBaseline = require_text_group.runsShareBaseline;
91
92
  exports.scaleMathStretchConstruction = require_math_stretch.scaleMathStretchConstruction;
93
+ exports.scriptPositionOf = require_text_group.scriptPositionOf;
92
94
  exports.sourcePageIndex = require_layout.sourcePageIndex;
93
95
  exports.unfilterScanlines = require_image_png_filter.unfilterScanlines;
94
96
  exports.writePdf = require_write.writePdf;
package/dist/index.d.cts CHANGED
@@ -24,8 +24,8 @@ import { a as MathGlyphPart, c as MathVariants, n as MathGlyphAssembly, o as Mat
24
24
  import { a as scaleMathStretchConstruction, i as assembleStretchyGlyph, n as MathStretchOptions, r as MathStretchPlacement, t as MathStretchConstruction } from "./math-stretch-C2JanDAz.cjs";
25
25
  import { LoadedMathFont, MathFont, MathFontDescriptorMetrics, loadMathFont } from "./math-font.cjs";
26
26
  import { DEFAULT_VERTICAL_METRIC_POLICY, FontMeasurerOptions, StandardFontMeasurerOptions, VerticalMetricPolicy, createFontMeasurer, createStandardFontMeasurer } from "./measure.cjs";
27
- import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, groupPdfTextRuns, runGapPt, runsShareBaseline } from "./text-group.cjs";
27
+ import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfLineWithScripts, PdfScriptPosition, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf } from "./text-group.cjs";
28
28
  import { PageRasteriser, RasterDrawOp, RasterFillRectOp, RasterFillSpec, RasterImageOp, RasterMatrix, RasterPageGeometry, RasterPathOp, RasterPathSegment, RasterRegionPt, RasterStrokeSpec, RasterSubpath, RenderPdfPageOptions, renderPdfPage } from "./raster.cjs";
29
29
  import { FontRegistryOptions, FontSubstitution, MathAssembledGlyphs, MathBox, MathColor, MathFontMetrics, MathGlyphMetrics, MathGlyphPlacement, MathGlyphRun, MathLayoutItem, MathRule, MathStretchAxis, MathStretchGlyph, MathStretchResult, MathStroke, Point, PositionedFormula, ProvidedFont, StyledFragment, StyledRun, TextMeasurer, UnderlineMetrics, WrapOptions, WrappedLine } from "document-schema.js";
30
30
  export * from "byte-codec";
31
- export { type CcittFaxImage, type CcittFaxOptions, DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, type EmbeddedFace, type EmbeddedFaceMetrics, type EmbeddedFaceSubstitution, type FontFace, FontFaceParseError, type FontMeasurerOptions, type FontMetrics, type FontRegistry, type FontRegistryOptions, type FontSubstitution, type GlyphInkBounds, type Jbig2DecodeOptions, type Jbig2Image, Jbig2ParseError, Jbig2UnsupportedError, type Jp2ChannelDefinition, type Jp2ColourSpace, type Jp2Container, type Jp2ImageHeader, type Jpeg2000ComponentMetadata, type Jpeg2000DecodeOptions, type Jpeg2000Image, type Jpeg2000Metadata, Jpeg2000ParseError, type Jpeg2000ProgressionOrder, type Jpeg2000QuantizationStyle, type Jpeg2000Transform, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutDocumentSurface, LayoutDocumentSurfaceSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema, type LoadedMathFont, type MathAssembledGlyphs, type MathBox, type MathColor, type MathFont, type MathFontDescriptorMetrics, type MathFontMetrics, type MathGlyphAssembly, type MathGlyphConstruction, type MathGlyphMetrics, type MathGlyphPart, type MathGlyphPlacement, type MathGlyphRun, type MathGlyphVariant, type MathLayoutItem, type MathRule, type MathStretchAxis, type MathStretchConstruction, type MathStretchGlyph, type MathStretchOptions, type MathStretchPlacement, type MathStretchResult, type MathStroke, type MathVariants, NOOP_DIAGNOSTIC_SINK, type PageRasteriser, PdfBytesSchema, type PdfDiagnostic, type PdfDiagnosticSeverity, type PdfDiagnosticSink, PdfEncryptedError, PdfEncryptionError, type PdfEncryptionOptions, type PdfEncryptionPermissions, type PdfEncryptionScheme, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, PdfSkippedSurfacesError, type PdfTextBox, type PdfTextGroupingOptions, type PdfTextLine, type PdfTextRunGeometry, type PdfTextWord, type PdfWordSeparator, type Point, type PositionedFormula, type ProvidedFont, type RasterDrawOp, type RasterFillRectOp, type RasterFillSpec, type RasterImageOp, type RasterMatrix, type RasterPageGeometry, type RasterPathOp, type RasterPathSegment, type RasterRegionPt, type RasterStrokeSpec, type RasterSubpath, type ReadPdfOptions, type ReadPdfSurfaces, type RenderPdfPageOptions, type ResolvedFace, type ResolvedFont, STANDARD_METRICS, type StandardFontMeasurerOptions, type StandardFontName, type StyledFragment, type StyledRun, type TextMeasurer, type UnderlineMetrics, type VerticalMetricPolicy, type WinAnsiSubstitution, type WrapOptions, type WrappedLine, type WritePdfOptions, assembleStretchyGlyph, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, sourcePageIndex, unfilterScanlines, writePdf };
31
+ export { type CcittFaxImage, type CcittFaxOptions, DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, type EmbeddedFace, type EmbeddedFaceMetrics, type EmbeddedFaceSubstitution, type FontFace, FontFaceParseError, type FontMeasurerOptions, type FontMetrics, type FontRegistry, type FontRegistryOptions, type FontSubstitution, type GlyphInkBounds, type Jbig2DecodeOptions, type Jbig2Image, Jbig2ParseError, Jbig2UnsupportedError, type Jp2ChannelDefinition, type Jp2ColourSpace, type Jp2Container, type Jp2ImageHeader, type Jpeg2000ComponentMetadata, type Jpeg2000DecodeOptions, type Jpeg2000Image, type Jpeg2000Metadata, Jpeg2000ParseError, type Jpeg2000ProgressionOrder, type Jpeg2000QuantizationStyle, type Jpeg2000Transform, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutDocumentSurface, LayoutDocumentSurfaceSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema, type LoadedMathFont, type MathAssembledGlyphs, type MathBox, type MathColor, type MathFont, type MathFontDescriptorMetrics, type MathFontMetrics, type MathGlyphAssembly, type MathGlyphConstruction, type MathGlyphMetrics, type MathGlyphPart, type MathGlyphPlacement, type MathGlyphRun, type MathGlyphVariant, type MathLayoutItem, type MathRule, type MathStretchAxis, type MathStretchConstruction, type MathStretchGlyph, type MathStretchOptions, type MathStretchPlacement, type MathStretchResult, type MathStroke, type MathVariants, NOOP_DIAGNOSTIC_SINK, type PageRasteriser, PdfBytesSchema, type PdfDiagnostic, type PdfDiagnosticSeverity, type PdfDiagnosticSink, PdfEncryptedError, PdfEncryptionError, type PdfEncryptionOptions, type PdfEncryptionPermissions, type PdfEncryptionScheme, type PdfLineWithScripts, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, type PdfScriptPosition, PdfSkippedSurfacesError, type PdfTextBox, type PdfTextGroupingOptions, type PdfTextLine, type PdfTextRunGeometry, type PdfTextWord, type PdfWordSeparator, type Point, type PositionedFormula, type ProvidedFont, type RasterDrawOp, type RasterFillRectOp, type RasterFillSpec, type RasterImageOp, type RasterMatrix, type RasterPageGeometry, type RasterPathOp, type RasterPathSegment, type RasterRegionPt, type RasterStrokeSpec, type RasterSubpath, type ReadPdfOptions, type ReadPdfSurfaces, type RenderPdfPageOptions, type ResolvedFace, type ResolvedFont, STANDARD_METRICS, type StandardFontMeasurerOptions, type StandardFontName, type StyledFragment, type StyledRun, type TextMeasurer, type UnderlineMetrics, type VerticalMetricPolicy, type WinAnsiSubstitution, type WrapOptions, type WrappedLine, type WritePdfOptions, assembleStretchyGlyph, attachScriptRuns, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, scriptPositionOf, sourcePageIndex, unfilterScanlines, writePdf };
package/dist/index.d.ts CHANGED
@@ -24,8 +24,8 @@ import { a as MathGlyphPart, c as MathVariants, n as MathGlyphAssembly, o as Mat
24
24
  import { a as scaleMathStretchConstruction, i as assembleStretchyGlyph, n as MathStretchOptions, r as MathStretchPlacement, t as MathStretchConstruction } from "./math-stretch-BbC2o4Br.js";
25
25
  import { LoadedMathFont, MathFont, MathFontDescriptorMetrics, loadMathFont } from "./math-font.js";
26
26
  import { DEFAULT_VERTICAL_METRIC_POLICY, FontMeasurerOptions, StandardFontMeasurerOptions, VerticalMetricPolicy, createFontMeasurer, createStandardFontMeasurer } from "./measure.js";
27
- import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, groupPdfTextRuns, runGapPt, runsShareBaseline } from "./text-group.js";
27
+ import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfLineWithScripts, PdfScriptPosition, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf } from "./text-group.js";
28
28
  import { PageRasteriser, RasterDrawOp, RasterFillRectOp, RasterFillSpec, RasterImageOp, RasterMatrix, RasterPageGeometry, RasterPathOp, RasterPathSegment, RasterRegionPt, RasterStrokeSpec, RasterSubpath, RenderPdfPageOptions, renderPdfPage } from "./raster.js";
29
29
  import { FontRegistryOptions, FontSubstitution, MathAssembledGlyphs, MathBox, MathColor, MathFontMetrics, MathGlyphMetrics, MathGlyphPlacement, MathGlyphRun, MathLayoutItem, MathRule, MathStretchAxis, MathStretchGlyph, MathStretchResult, MathStroke, Point, PositionedFormula, ProvidedFont, StyledFragment, StyledRun, TextMeasurer, UnderlineMetrics, WrapOptions, WrappedLine } from "document-schema.js";
30
30
  export * from "byte-codec";
31
- export { type CcittFaxImage, type CcittFaxOptions, DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, type EmbeddedFace, type EmbeddedFaceMetrics, type EmbeddedFaceSubstitution, type FontFace, FontFaceParseError, type FontMeasurerOptions, type FontMetrics, type FontRegistry, type FontRegistryOptions, type FontSubstitution, type GlyphInkBounds, type Jbig2DecodeOptions, type Jbig2Image, Jbig2ParseError, Jbig2UnsupportedError, type Jp2ChannelDefinition, type Jp2ColourSpace, type Jp2Container, type Jp2ImageHeader, type Jpeg2000ComponentMetadata, type Jpeg2000DecodeOptions, type Jpeg2000Image, type Jpeg2000Metadata, Jpeg2000ParseError, type Jpeg2000ProgressionOrder, type Jpeg2000QuantizationStyle, type Jpeg2000Transform, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutDocumentSurface, LayoutDocumentSurfaceSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema, type LoadedMathFont, type MathAssembledGlyphs, type MathBox, type MathColor, type MathFont, type MathFontDescriptorMetrics, type MathFontMetrics, type MathGlyphAssembly, type MathGlyphConstruction, type MathGlyphMetrics, type MathGlyphPart, type MathGlyphPlacement, type MathGlyphRun, type MathGlyphVariant, type MathLayoutItem, type MathRule, type MathStretchAxis, type MathStretchConstruction, type MathStretchGlyph, type MathStretchOptions, type MathStretchPlacement, type MathStretchResult, type MathStroke, type MathVariants, NOOP_DIAGNOSTIC_SINK, type PageRasteriser, PdfBytesSchema, type PdfDiagnostic, type PdfDiagnosticSeverity, type PdfDiagnosticSink, PdfEncryptedError, PdfEncryptionError, type PdfEncryptionOptions, type PdfEncryptionPermissions, type PdfEncryptionScheme, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, PdfSkippedSurfacesError, type PdfTextBox, type PdfTextGroupingOptions, type PdfTextLine, type PdfTextRunGeometry, type PdfTextWord, type PdfWordSeparator, type Point, type PositionedFormula, type ProvidedFont, type RasterDrawOp, type RasterFillRectOp, type RasterFillSpec, type RasterImageOp, type RasterMatrix, type RasterPageGeometry, type RasterPathOp, type RasterPathSegment, type RasterRegionPt, type RasterStrokeSpec, type RasterSubpath, type ReadPdfOptions, type ReadPdfSurfaces, type RenderPdfPageOptions, type ResolvedFace, type ResolvedFont, STANDARD_METRICS, type StandardFontMeasurerOptions, type StandardFontName, type StyledFragment, type StyledRun, type TextMeasurer, type UnderlineMetrics, type VerticalMetricPolicy, type WinAnsiSubstitution, type WrapOptions, type WrappedLine, type WritePdfOptions, assembleStretchyGlyph, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, sourcePageIndex, unfilterScanlines, writePdf };
31
+ export { type CcittFaxImage, type CcittFaxOptions, DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, type EmbeddedFace, type EmbeddedFaceMetrics, type EmbeddedFaceSubstitution, type FontFace, FontFaceParseError, type FontMeasurerOptions, type FontMetrics, type FontRegistry, type FontRegistryOptions, type FontSubstitution, type GlyphInkBounds, type Jbig2DecodeOptions, type Jbig2Image, Jbig2ParseError, Jbig2UnsupportedError, type Jp2ChannelDefinition, type Jp2ColourSpace, type Jp2Container, type Jp2ImageHeader, type Jpeg2000ComponentMetadata, type Jpeg2000DecodeOptions, type Jpeg2000Image, type Jpeg2000Metadata, Jpeg2000ParseError, type Jpeg2000ProgressionOrder, type Jpeg2000QuantizationStyle, type Jpeg2000Transform, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutDocumentSurface, LayoutDocumentSurfaceSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema, type LoadedMathFont, type MathAssembledGlyphs, type MathBox, type MathColor, type MathFont, type MathFontDescriptorMetrics, type MathFontMetrics, type MathGlyphAssembly, type MathGlyphConstruction, type MathGlyphMetrics, type MathGlyphPart, type MathGlyphPlacement, type MathGlyphRun, type MathGlyphVariant, type MathLayoutItem, type MathRule, type MathStretchAxis, type MathStretchConstruction, type MathStretchGlyph, type MathStretchOptions, type MathStretchPlacement, type MathStretchResult, type MathStroke, type MathVariants, NOOP_DIAGNOSTIC_SINK, type PageRasteriser, PdfBytesSchema, type PdfDiagnostic, type PdfDiagnosticSeverity, type PdfDiagnosticSink, PdfEncryptedError, PdfEncryptionError, type PdfEncryptionOptions, type PdfEncryptionPermissions, type PdfEncryptionScheme, type PdfLineWithScripts, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, type PdfScriptPosition, PdfSkippedSurfacesError, type PdfTextBox, type PdfTextGroupingOptions, type PdfTextLine, type PdfTextRunGeometry, type PdfTextWord, type PdfWordSeparator, type Point, type PositionedFormula, type ProvidedFont, type RasterDrawOp, type RasterFillRectOp, type RasterFillSpec, type RasterImageOp, type RasterMatrix, type RasterPageGeometry, type RasterPathOp, type RasterPathSegment, type RasterRegionPt, type RasterStrokeSpec, type RasterSubpath, type ReadPdfOptions, type ReadPdfSurfaces, type RenderPdfPageOptions, type ResolvedFace, type ResolvedFont, STANDARD_METRICS, type StandardFontMeasurerOptions, type StandardFontName, type StyledFragment, type StyledRun, type TextMeasurer, type UnderlineMetrics, type VerticalMetricPolicy, type WinAnsiSubstitution, type WrapOptions, type WrappedLine, type WritePdfOptions, assembleStretchyGlyph, attachScriptRuns, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, scriptPositionOf, sourcePageIndex, unfilterScanlines, writePdf };
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { decodeJpeg2000, readJpeg2000Metadata } from "./image/jpeg2000.js";
9
9
  import { LAYOUT_FORMAT_VERSION, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachmentSchema, LayoutDestinationSchema, LayoutDestinationTargetSchema, LayoutDocumentSchema, LayoutDocumentSurfaceSchema, LayoutEllipseSchema, LayoutFormFieldSchema, LayoutFormWidgetSchema, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLinkSchema, LayoutItemSchema, LayoutLayerSchema, LayoutLineSchema, LayoutLinkSchema, LayoutOutlineItemSchema, LayoutPageSchema, LayoutPathSchema, LayoutPathSegmentSchema, LayoutRectSchema, LayoutStructureElementSchema, LayoutSubpathSchema, LayoutTextSchema, sourcePageIndex } from "./layout.js";
10
10
  import { NOOP_DIAGNOSTIC_SINK, PdfEncryptedError, PdfPageSelectionError, PdfParseError, PdfPasswordRequiredError } from "./diagnostics.js";
11
11
  import { resolveStandardFont } from "./fonts.js";
12
- import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, groupPdfTextRuns, runGapPt, runsShareBaseline } from "./text-group.js";
12
+ import { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf } from "./text-group.js";
13
13
  import { readPdf } from "./read.js";
14
14
  import { PdfEncryptionError } from "./encrypt-write.js";
15
15
  import { assembleStretchyGlyph, scaleMathStretchConstruction } from "./math-stretch.js";
@@ -20,6 +20,6 @@ import { PdfSkippedSurfacesError } from "./write-skipped-surfaces.js";
20
20
  import { PdfPartialDocumentError, writePdf } from "./write.js";
21
21
  import { PdfBytesSchema, pdfCodec } from "./codec.js";
22
22
  import { FontFaceParseError, readFontFace } from "./font-face.js";
23
- import { r as renderPdfPage } from "./raster-CIc6ot36.js";
23
+ import { r as renderPdfPage } from "./raster-DCDcD7no.js";
24
24
  export * from "byte-codec";
25
- export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, FontFaceParseError, Jbig2ParseError, Jbig2UnsupportedError, Jpeg2000ParseError, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachmentSchema, LayoutDestinationSchema, LayoutDestinationTargetSchema, LayoutDocumentSchema, LayoutDocumentSurfaceSchema, LayoutEllipseSchema, LayoutFormFieldSchema, LayoutFormWidgetSchema, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLinkSchema, LayoutItemSchema, LayoutLayerSchema, LayoutLineSchema, LayoutLinkSchema, LayoutOutlineItemSchema, LayoutPageSchema, LayoutPathSchema, LayoutPathSegmentSchema, LayoutRectSchema, LayoutStructureElementSchema, LayoutSubpathSchema, LayoutTextSchema, NOOP_DIAGNOSTIC_SINK, PdfBytesSchema, PdfEncryptedError, PdfEncryptionError, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, PdfSkippedSurfacesError, STANDARD_METRICS, assembleStretchyGlyph, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, sourcePageIndex, unfilterScanlines, writePdf };
25
+ export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_VERTICAL_METRIC_POLICY, DEFAULT_WORD_GAP_EM, FontFaceParseError, Jbig2ParseError, Jbig2UnsupportedError, Jpeg2000ParseError, Jpeg2000UnsupportedError, LAYOUT_FORMAT_VERSION, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachmentSchema, LayoutDestinationSchema, LayoutDestinationTargetSchema, LayoutDocumentSchema, LayoutDocumentSurfaceSchema, LayoutEllipseSchema, LayoutFormFieldSchema, LayoutFormWidgetSchema, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLinkSchema, LayoutItemSchema, LayoutLayerSchema, LayoutLineSchema, LayoutLinkSchema, LayoutOutlineItemSchema, LayoutPageSchema, LayoutPathSchema, LayoutPathSegmentSchema, LayoutRectSchema, LayoutStructureElementSchema, LayoutSubpathSchema, LayoutTextSchema, NOOP_DIAGNOSTIC_SINK, PdfBytesSchema, PdfEncryptedError, PdfEncryptionError, PdfPageSelectionError, PdfParseError, PdfPartialDocumentError, PdfPasswordRequiredError, PdfSkippedSurfacesError, STANDARD_METRICS, assembleStretchyGlyph, attachScriptRuns, createFontMeasurer, createFontRegistry, createStandardFontMeasurer, decodeCcittFax, decodeJbig2Embedded, decodeJpeg2000, filterScanlines, groupPdfTextRuns, loadMathFont, looksLikeBareCodestream, parseJp2Container, pdfCodec, readFontFace, readJpeg2000Metadata, readPdf, renderPdfPage, resolveFaceWithRegistry, resolveStandardFont, runGapPt, runsShareBaseline, scaleMathStretchConstruction, scriptPositionOf, sourcePageIndex, unfilterScanlines, writePdf };
@@ -2,7 +2,8 @@ const require_objects = require("./objects.cjs");
2
2
  const require_matrix = require("./matrix.cjs");
3
3
  const require_filters = require("./filters.cjs");
4
4
  const require_content_read = require("./content-read.cjs");
5
- require("./text-group.cjs");
5
+ const require_text_group = require("./text-group.cjs");
6
+ const require_interpret_state = require("./interpret-state.cjs");
6
7
  const require_interpret_shapes = require("./interpret-shapes.cjs");
7
8
  const require_interpret_marked = require("./interpret-marked.cjs");
8
9
  let document_schema_js = require("document-schema.js");
@@ -44,38 +45,7 @@ function genericColor(operands) {
44
45
  //#endregion
45
46
  //#region src/interpret.ts
46
47
  const MAX_FORM_XOBJECT_DEPTH = 12;
47
- const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
48
- const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1e3;
49
- const ASCII_SPACE = 32;
50
48
  const DEFAULT_LINE_WIDTH_PT = 1;
51
- function initialTextParameters() {
52
- return {
53
- fontResourceName: void 0,
54
- fontSizePt: 0,
55
- charSpace: 0,
56
- wordSpace: 0,
57
- horizScale: 1,
58
- leading: 0,
59
- rise: 0
60
- };
61
- }
62
- function defaultTextObjectState() {
63
- return {
64
- tm: require_matrix.IDENTITY_MATRIX,
65
- tlm: require_matrix.IDENTITY_MATRIX
66
- };
67
- }
68
- function computeTrm(gs, text) {
69
- const fontMatrix = [
70
- gs.fontSizePt * gs.horizScale,
71
- 0,
72
- 0,
73
- gs.fontSizePt,
74
- 0,
75
- gs.rise
76
- ];
77
- return require_matrix.multiplyMatrices(require_matrix.multiplyMatrices(fontMatrix, text.tm), gs.ctm);
78
- }
79
49
  function numAt(operands, index) {
80
50
  return require_objects.asNumber(operands[index]) ?? 0;
81
51
  }
@@ -104,7 +74,7 @@ function interpretContentStream(bytes, resources, context) {
104
74
  strokeColor: document_schema_js.COLOR_BLACK,
105
75
  lineWidth: DEFAULT_LINE_WIDTH_PT,
106
76
  dashArray: [],
107
- ...initialTextParameters()
77
+ ...require_interpret_state.initialTextParameters()
108
78
  }, context, 0, new require_interpret_shapes.MarkedContentStack());
109
79
  }
110
80
  function lastPointOf(subpath) {
@@ -122,7 +92,7 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
122
92
  const operations = require_content_read.readContentStream(bytes, context.sink);
123
93
  const gsStack = [];
124
94
  let gs = initialState;
125
- let text = defaultTextObjectState();
95
+ let text = require_interpret_state.defaultTextObjectState();
126
96
  let pathSubpaths = [];
127
97
  let currentSubpath;
128
98
  const inScope = (key) => markedContent.inScope(key);
@@ -236,25 +206,16 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
236
206
  }
237
207
  resetPath();
238
208
  };
239
- const advanceThroughString = (codes) => {
240
- const fontResourceName = gs.fontResourceName;
241
- if (fontResourceName === void 0) return;
242
- let offset = 0;
243
- while (offset < codes.length) {
244
- const glyph = context.fontMetrics.glyphAdvance(fontResourceName, resources, codes, offset);
245
- if (glyph === void 0) context.sink({
246
- code: "pdf/font-not-resolved",
247
- severity: "warning",
248
- message: `could not resolve font resource /${fontResourceName} to compute a glyph advance; assuming a fallback width`
249
- });
250
- const widthPer1000 = glyph?.widthPer1000 ?? FALLBACK_GLYPH_WIDTH_PER_1000;
251
- const byteLength = glyph?.byteLengthConsumed ?? 1;
252
- const isSingleByteSpace = byteLength === 1 && codes[offset] === ASCII_SPACE;
253
- const spacing = gs.charSpace + (isSingleByteSpace ? gs.wordSpace : 0);
254
- const vertical = glyph?.vertical;
255
- text.tm = vertical === void 0 ? require_matrix.multiplyMatrices(require_matrix.translationMatrix((widthPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt + spacing) * gs.horizScale, 0), text.tm) : require_matrix.multiplyMatrices(require_matrix.translationMatrix(0, vertical.displacementPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt + spacing), text.tm);
256
- offset += byteLength;
257
- }
209
+ const advanceThroughGlyph = (fontResourceName, codes, offset) => {
210
+ const glyph = context.fontMetrics.glyphAdvance(fontResourceName, resources, codes, offset);
211
+ if (glyph === void 0) context.sink({
212
+ code: "pdf/font-not-resolved",
213
+ severity: "warning",
214
+ message: `could not resolve font resource /${fontResourceName} to compute a glyph advance; assuming a fallback width`
215
+ });
216
+ const step = require_interpret_state.stepPastGlyph(gs, text.tm, glyph, codes, offset);
217
+ text.tm = step.tm;
218
+ return step;
258
219
  };
259
220
  const positionVectorOf = (codes) => {
260
221
  const fontResourceName = gs.fontResourceName;
@@ -262,18 +223,22 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
262
223
  const vertical = context.fontMetrics.glyphAdvance(fontResourceName, resources, codes, 0)?.vertical;
263
224
  if (vertical === void 0) return;
264
225
  return {
265
- x: vertical.positionXPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT,
266
- y: vertical.positionYPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT
226
+ x: vertical.positionXPer1000 / require_interpret_state.GLYPH_SPACE_PER_TEXT_SPACE_UNIT,
227
+ y: vertical.positionYPer1000 / require_interpret_state.GLYPH_SPACE_PER_TEXT_SPACE_UNIT
267
228
  };
268
229
  };
269
230
  const showTextArray = (elements) => {
270
231
  const fontResourceName = gs.fontResourceName;
271
232
  if (fontResourceName === void 0) return;
272
233
  const vertical = context.fontMetrics.isVertical(fontResourceName, resources);
273
- let startMatrix = computeTrm(gs, text);
234
+ const wordGap = require_text_group.DEFAULT_WORD_GAP_EM * gs.fontSizePt;
235
+ let startMatrix = require_interpret_state.computeTrm(gs, text);
274
236
  let chunks = [];
275
237
  let totalLength = 0;
276
238
  let firstGlyphPosition;
239
+ let lastWidthEndTm;
240
+ let gapSinceLastGlyph = 0;
241
+ const gapIsWordWide = () => !vertical && totalLength > 0 && gapSinceLastGlyph >= wordGap;
277
242
  const flushRun = () => {
278
243
  if (totalLength === 0) return;
279
244
  const combined = new Uint8Array(totalLength);
@@ -282,7 +247,10 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
282
247
  combined.set(chunk, at);
283
248
  at += chunk.length;
284
249
  }
285
- const endMatrix = computeTrm(gs, text);
250
+ const endMatrix = require_interpret_state.computeTrm(gs, gapIsWordWide() && lastWidthEndTm !== void 0 ? {
251
+ ...text,
252
+ tm: lastWidthEndTm
253
+ } : text);
286
254
  const shift = firstGlyphPosition === void 0 ? void 0 : require_matrix.translationMatrix(-firstGlyphPosition.x, -firstGlyphPosition.y);
287
255
  pushItem({
288
256
  kind: "text",
@@ -300,15 +268,22 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
300
268
  firstGlyphPosition = void 0;
301
269
  };
302
270
  for (const el of elements) if (el.kind === "string") {
303
- firstGlyphPosition ??= vertical ? positionVectorOf(el.bytes) : void 0;
304
- chunks.push(el.bytes);
305
- totalLength += el.bytes.length;
306
- advanceThroughString(el.bytes);
271
+ let offset = 0;
272
+ while (offset < el.bytes.length) {
273
+ if (gapIsWordWide()) flushRun();
274
+ if (totalLength === 0) startMatrix = require_interpret_state.computeTrm(gs, text);
275
+ firstGlyphPosition ??= vertical ? positionVectorOf(el.bytes.subarray(offset)) : void 0;
276
+ const glyph = advanceThroughGlyph(fontResourceName, el.bytes, offset);
277
+ chunks.push(el.bytes.subarray(offset, offset + glyph.byteLength));
278
+ totalLength += glyph.byteLength;
279
+ offset += glyph.byteLength;
280
+ lastWidthEndTm = glyph.widthEndTm;
281
+ gapSinceLastGlyph = glyph.charSpaceAfter;
282
+ }
307
283
  } else if (el.kind === "number") {
308
- const adjustment = -(el.value / GLYPH_SPACE_PER_TEXT_SPACE_UNIT) * gs.fontSizePt;
309
- if (!vertical && totalLength > 0 && adjustment >= .125 * gs.fontSizePt) flushRun();
284
+ const adjustment = -(el.value / require_interpret_state.GLYPH_SPACE_PER_TEXT_SPACE_UNIT) * gs.fontSizePt;
285
+ gapSinceLastGlyph += adjustment;
310
286
  text.tm = require_matrix.multiplyMatrices(vertical ? require_matrix.translationMatrix(0, adjustment) : require_matrix.translationMatrix(adjustment * gs.horizScale, 0), text.tm);
311
- if (totalLength === 0) startMatrix = computeTrm(gs, text);
312
287
  }
313
288
  flushRun();
314
289
  };
@@ -573,7 +548,7 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
573
548
  emitPaint(operator);
574
549
  break;
575
550
  case "BT":
576
- text = defaultTextObjectState();
551
+ text = require_interpret_state.defaultTextObjectState();
577
552
  break;
578
553
  case "Tf":
579
554
  gs = {
@@ -2,7 +2,8 @@ import { asArray, asName, asNumber, dictGet } from "./objects.js";
2
2
  import { IDENTITY_MATRIX, applyMatrix, multiplyMatrices, translationMatrix } from "./matrix.js";
3
3
  import { decodeStream } from "./filters.js";
4
4
  import { readContentStream } from "./content-read.js";
5
- import "./text-group.js";
5
+ import { DEFAULT_WORD_GAP_EM } from "./text-group.js";
6
+ import { GLYPH_SPACE_PER_TEXT_SPACE_UNIT, computeTrm, defaultTextObjectState, initialTextParameters, stepPastGlyph } from "./interpret-state.js";
6
7
  import { MarkedContentStack, classifyShape, strokeStyleFromDashArray } from "./interpret-shapes.js";
7
8
  import { markedContentProperties, markedContentScopeProps } from "./interpret-marked.js";
8
9
  import { COLOR_BLACK } from "document-schema.js";
@@ -44,38 +45,7 @@ function genericColor(operands) {
44
45
  //#endregion
45
46
  //#region src/interpret.ts
46
47
  const MAX_FORM_XOBJECT_DEPTH = 12;
47
- const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
48
- const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1e3;
49
- const ASCII_SPACE = 32;
50
48
  const DEFAULT_LINE_WIDTH_PT = 1;
51
- function initialTextParameters() {
52
- return {
53
- fontResourceName: void 0,
54
- fontSizePt: 0,
55
- charSpace: 0,
56
- wordSpace: 0,
57
- horizScale: 1,
58
- leading: 0,
59
- rise: 0
60
- };
61
- }
62
- function defaultTextObjectState() {
63
- return {
64
- tm: IDENTITY_MATRIX,
65
- tlm: IDENTITY_MATRIX
66
- };
67
- }
68
- function computeTrm(gs, text) {
69
- const fontMatrix = [
70
- gs.fontSizePt * gs.horizScale,
71
- 0,
72
- 0,
73
- gs.fontSizePt,
74
- 0,
75
- gs.rise
76
- ];
77
- return multiplyMatrices(multiplyMatrices(fontMatrix, text.tm), gs.ctm);
78
- }
79
49
  function numAt(operands, index) {
80
50
  return asNumber(operands[index]) ?? 0;
81
51
  }
@@ -236,25 +206,16 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
236
206
  }
237
207
  resetPath();
238
208
  };
239
- const advanceThroughString = (codes) => {
240
- const fontResourceName = gs.fontResourceName;
241
- if (fontResourceName === void 0) return;
242
- let offset = 0;
243
- while (offset < codes.length) {
244
- const glyph = context.fontMetrics.glyphAdvance(fontResourceName, resources, codes, offset);
245
- if (glyph === void 0) context.sink({
246
- code: "pdf/font-not-resolved",
247
- severity: "warning",
248
- message: `could not resolve font resource /${fontResourceName} to compute a glyph advance; assuming a fallback width`
249
- });
250
- const widthPer1000 = glyph?.widthPer1000 ?? FALLBACK_GLYPH_WIDTH_PER_1000;
251
- const byteLength = glyph?.byteLengthConsumed ?? 1;
252
- const isSingleByteSpace = byteLength === 1 && codes[offset] === ASCII_SPACE;
253
- const spacing = gs.charSpace + (isSingleByteSpace ? gs.wordSpace : 0);
254
- const vertical = glyph?.vertical;
255
- text.tm = vertical === void 0 ? multiplyMatrices(translationMatrix((widthPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt + spacing) * gs.horizScale, 0), text.tm) : multiplyMatrices(translationMatrix(0, vertical.displacementPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt + spacing), text.tm);
256
- offset += byteLength;
257
- }
209
+ const advanceThroughGlyph = (fontResourceName, codes, offset) => {
210
+ const glyph = context.fontMetrics.glyphAdvance(fontResourceName, resources, codes, offset);
211
+ if (glyph === void 0) context.sink({
212
+ code: "pdf/font-not-resolved",
213
+ severity: "warning",
214
+ message: `could not resolve font resource /${fontResourceName} to compute a glyph advance; assuming a fallback width`
215
+ });
216
+ const step = stepPastGlyph(gs, text.tm, glyph, codes, offset);
217
+ text.tm = step.tm;
218
+ return step;
258
219
  };
259
220
  const positionVectorOf = (codes) => {
260
221
  const fontResourceName = gs.fontResourceName;
@@ -270,10 +231,14 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
270
231
  const fontResourceName = gs.fontResourceName;
271
232
  if (fontResourceName === void 0) return;
272
233
  const vertical = context.fontMetrics.isVertical(fontResourceName, resources);
234
+ const wordGap = DEFAULT_WORD_GAP_EM * gs.fontSizePt;
273
235
  let startMatrix = computeTrm(gs, text);
274
236
  let chunks = [];
275
237
  let totalLength = 0;
276
238
  let firstGlyphPosition;
239
+ let lastWidthEndTm;
240
+ let gapSinceLastGlyph = 0;
241
+ const gapIsWordWide = () => !vertical && totalLength > 0 && gapSinceLastGlyph >= wordGap;
277
242
  const flushRun = () => {
278
243
  if (totalLength === 0) return;
279
244
  const combined = new Uint8Array(totalLength);
@@ -282,7 +247,10 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
282
247
  combined.set(chunk, at);
283
248
  at += chunk.length;
284
249
  }
285
- const endMatrix = computeTrm(gs, text);
250
+ const endMatrix = computeTrm(gs, gapIsWordWide() && lastWidthEndTm !== void 0 ? {
251
+ ...text,
252
+ tm: lastWidthEndTm
253
+ } : text);
286
254
  const shift = firstGlyphPosition === void 0 ? void 0 : translationMatrix(-firstGlyphPosition.x, -firstGlyphPosition.y);
287
255
  pushItem({
288
256
  kind: "text",
@@ -300,15 +268,22 @@ function runContentStream(bytes, resources, initialState, context, depth, marked
300
268
  firstGlyphPosition = void 0;
301
269
  };
302
270
  for (const el of elements) if (el.kind === "string") {
303
- firstGlyphPosition ??= vertical ? positionVectorOf(el.bytes) : void 0;
304
- chunks.push(el.bytes);
305
- totalLength += el.bytes.length;
306
- advanceThroughString(el.bytes);
271
+ let offset = 0;
272
+ while (offset < el.bytes.length) {
273
+ if (gapIsWordWide()) flushRun();
274
+ if (totalLength === 0) startMatrix = computeTrm(gs, text);
275
+ firstGlyphPosition ??= vertical ? positionVectorOf(el.bytes.subarray(offset)) : void 0;
276
+ const glyph = advanceThroughGlyph(fontResourceName, el.bytes, offset);
277
+ chunks.push(el.bytes.subarray(offset, offset + glyph.byteLength));
278
+ totalLength += glyph.byteLength;
279
+ offset += glyph.byteLength;
280
+ lastWidthEndTm = glyph.widthEndTm;
281
+ gapSinceLastGlyph = glyph.charSpaceAfter;
282
+ }
307
283
  } else if (el.kind === "number") {
308
284
  const adjustment = -(el.value / GLYPH_SPACE_PER_TEXT_SPACE_UNIT) * gs.fontSizePt;
309
- if (!vertical && totalLength > 0 && adjustment >= .125 * gs.fontSizePt) flushRun();
285
+ gapSinceLastGlyph += adjustment;
310
286
  text.tm = multiplyMatrices(vertical ? translationMatrix(0, adjustment) : translationMatrix(adjustment * gs.horizScale, 0), text.tm);
311
- if (totalLength === 0) startMatrix = computeTrm(gs, text);
312
287
  }
313
288
  flushRun();
314
289
  };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_interpret = require("./interpret-2_N_lh0h.cjs");
2
+ const require_interpret = require("./interpret-BkDxq0QA.cjs");
3
3
  exports.cmykColor = require_interpret.cmykColor;
4
4
  exports.genericColor = require_interpret.genericColor;
5
5
  exports.grayColor = require_interpret.grayColor;
@@ -1,2 +1,2 @@
1
- import { a as grayColor, i as genericColor, o as rgbColor, r as cmykColor } from "./interpret-BeMKZGBm.js";
1
+ import { a as grayColor, i as genericColor, o as rgbColor, r as cmykColor } from "./interpret-BtzX-LWm.js";
2
2
  export { cmykColor, genericColor, grayColor, rgbColor };
@@ -0,0 +1,66 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_matrix = require("./matrix.cjs");
3
+ //#region src/interpret-state.ts
4
+ const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
5
+ const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1e3;
6
+ const ASCII_SPACE = 32;
7
+ function initialTextParameters() {
8
+ return {
9
+ fontResourceName: void 0,
10
+ fontSizePt: 0,
11
+ charSpace: 0,
12
+ wordSpace: 0,
13
+ horizScale: 1,
14
+ leading: 0,
15
+ rise: 0
16
+ };
17
+ }
18
+ function defaultTextObjectState() {
19
+ return {
20
+ tm: require_matrix.IDENTITY_MATRIX,
21
+ tlm: require_matrix.IDENTITY_MATRIX
22
+ };
23
+ }
24
+ function computeTrm(gs, text) {
25
+ const fontMatrix = [
26
+ gs.fontSizePt * gs.horizScale,
27
+ 0,
28
+ 0,
29
+ gs.fontSizePt,
30
+ 0,
31
+ gs.rise
32
+ ];
33
+ return require_matrix.multiplyMatrices(require_matrix.multiplyMatrices(fontMatrix, text.tm), gs.ctm);
34
+ }
35
+ /**
36
+ * Moves a text matrix past one glyph.
37
+ * @param gs - the graphics state whose font size, spacing and horizontal scaling apply
38
+ * @param tm - the text matrix where the glyph starts
39
+ * @param glyph - the font's advance for the glyph's code, or undefined when the font could not be resolved, in which case a fallback width is assumed and the code is taken as one byte
40
+ * @param codes - the shown string
41
+ * @param offset - where the glyph's code starts in `codes`
42
+ * @returns the step, with the pen's new position and the end of the glyph's own width reported apart so a caller can tell the glyph's extent from the gap after it
43
+ */
44
+ function stepPastGlyph(gs, tm, glyph, codes, offset) {
45
+ const widthPer1000 = glyph?.widthPer1000 ?? 500;
46
+ const byteLength = glyph?.byteLengthConsumed ?? 1;
47
+ const isSingleByteSpace = byteLength === 1 && codes[offset] === 32;
48
+ const spacing = gs.charSpace + (isSingleByteSpace ? gs.wordSpace : 0);
49
+ const vertical = glyph?.vertical;
50
+ const widthAdvance = vertical === void 0 ? widthPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt : vertical.displacementPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt;
51
+ const along = (distance) => vertical === void 0 ? require_matrix.translationMatrix(distance * gs.horizScale, 0) : require_matrix.translationMatrix(0, distance);
52
+ return {
53
+ byteLength,
54
+ tm: require_matrix.multiplyMatrices(along(widthAdvance + spacing), tm),
55
+ widthEndTm: require_matrix.multiplyMatrices(along(widthAdvance), tm),
56
+ charSpaceAfter: gs.charSpace
57
+ };
58
+ }
59
+ //#endregion
60
+ exports.ASCII_SPACE = ASCII_SPACE;
61
+ exports.FALLBACK_GLYPH_WIDTH_PER_1000 = FALLBACK_GLYPH_WIDTH_PER_1000;
62
+ exports.GLYPH_SPACE_PER_TEXT_SPACE_UNIT = GLYPH_SPACE_PER_TEXT_SPACE_UNIT;
63
+ exports.computeTrm = computeTrm;
64
+ exports.defaultTextObjectState = defaultTextObjectState;
65
+ exports.initialTextParameters = initialTextParameters;
66
+ exports.stepPastGlyph = stepPastGlyph;
@@ -0,0 +1,51 @@
1
+ import { r as Matrix } from "./matrix-B7_SBKQ5.cjs";
2
+ import { GlyphAdvance } from "./interpret-types.cjs";
3
+ import { Color } from "document-schema.js";
4
+ //#region src/interpret-state.d.ts
5
+ declare const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
6
+ declare const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1000;
7
+ declare const ASCII_SPACE = 32;
8
+ interface GraphicsState {
9
+ readonly ctm: Matrix;
10
+ readonly fillColor: Color;
11
+ readonly strokeColor: Color;
12
+ readonly lineWidth: number;
13
+ readonly dashArray: readonly number[];
14
+ readonly fontResourceName: string | undefined;
15
+ readonly fontSizePt: number;
16
+ readonly charSpace: number;
17
+ readonly wordSpace: number;
18
+ readonly horizScale: number;
19
+ readonly leading: number;
20
+ readonly rise: number;
21
+ }
22
+ interface TextObjectState {
23
+ tm: Matrix;
24
+ tlm: Matrix;
25
+ }
26
+ declare function initialTextParameters(): Omit<GraphicsState, "ctm" | "fillColor" | "strokeColor" | "lineWidth" | "dashArray">;
27
+ declare function defaultTextObjectState(): TextObjectState;
28
+ declare function computeTrm(gs: GraphicsState, text: TextObjectState): Matrix;
29
+ /** Where one glyph leaves the text matrix, and what lies between the glyph and the next one. */
30
+ interface GlyphStep {
31
+ /** How many bytes of the shown string the glyph's code took. */
32
+ readonly byteLength: number;
33
+ /** The text matrix after the glyph's width and its spacing, where the next glyph starts. */
34
+ readonly tm: Matrix;
35
+ /** The text matrix at the end of the glyph's own advance width, before any spacing. */
36
+ readonly widthEndTm: Matrix;
37
+ /** The character spacing added after the glyph's width, in unscaled text space units. The word spacing a single-byte space takes is not counted: the space character already records that word boundary in the run's text. */
38
+ readonly charSpaceAfter: number;
39
+ }
40
+ /**
41
+ * Moves a text matrix past one glyph.
42
+ * @param gs - the graphics state whose font size, spacing and horizontal scaling apply
43
+ * @param tm - the text matrix where the glyph starts
44
+ * @param glyph - the font's advance for the glyph's code, or undefined when the font could not be resolved, in which case a fallback width is assumed and the code is taken as one byte
45
+ * @param codes - the shown string
46
+ * @param offset - where the glyph's code starts in `codes`
47
+ * @returns the step, with the pen's new position and the end of the glyph's own width reported apart so a caller can tell the glyph's extent from the gap after it
48
+ */
49
+ declare function stepPastGlyph(gs: GraphicsState, tm: Matrix, glyph: GlyphAdvance | undefined, codes: Uint8Array<ArrayBuffer>, offset: number): GlyphStep;
50
+ //#endregion
51
+ export { ASCII_SPACE, FALLBACK_GLYPH_WIDTH_PER_1000, GLYPH_SPACE_PER_TEXT_SPACE_UNIT, GlyphStep, GraphicsState, TextObjectState, computeTrm, defaultTextObjectState, initialTextParameters, stepPastGlyph };
@@ -0,0 +1,51 @@
1
+ import { r as Matrix } from "./matrix-B7_SBKQ5.js";
2
+ import { GlyphAdvance } from "./interpret-types.js";
3
+ import { Color } from "document-schema.js";
4
+ //#region src/interpret-state.d.ts
5
+ declare const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
6
+ declare const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1000;
7
+ declare const ASCII_SPACE = 32;
8
+ interface GraphicsState {
9
+ readonly ctm: Matrix;
10
+ readonly fillColor: Color;
11
+ readonly strokeColor: Color;
12
+ readonly lineWidth: number;
13
+ readonly dashArray: readonly number[];
14
+ readonly fontResourceName: string | undefined;
15
+ readonly fontSizePt: number;
16
+ readonly charSpace: number;
17
+ readonly wordSpace: number;
18
+ readonly horizScale: number;
19
+ readonly leading: number;
20
+ readonly rise: number;
21
+ }
22
+ interface TextObjectState {
23
+ tm: Matrix;
24
+ tlm: Matrix;
25
+ }
26
+ declare function initialTextParameters(): Omit<GraphicsState, "ctm" | "fillColor" | "strokeColor" | "lineWidth" | "dashArray">;
27
+ declare function defaultTextObjectState(): TextObjectState;
28
+ declare function computeTrm(gs: GraphicsState, text: TextObjectState): Matrix;
29
+ /** Where one glyph leaves the text matrix, and what lies between the glyph and the next one. */
30
+ interface GlyphStep {
31
+ /** How many bytes of the shown string the glyph's code took. */
32
+ readonly byteLength: number;
33
+ /** The text matrix after the glyph's width and its spacing, where the next glyph starts. */
34
+ readonly tm: Matrix;
35
+ /** The text matrix at the end of the glyph's own advance width, before any spacing. */
36
+ readonly widthEndTm: Matrix;
37
+ /** The character spacing added after the glyph's width, in unscaled text space units. The word spacing a single-byte space takes is not counted: the space character already records that word boundary in the run's text. */
38
+ readonly charSpaceAfter: number;
39
+ }
40
+ /**
41
+ * Moves a text matrix past one glyph.
42
+ * @param gs - the graphics state whose font size, spacing and horizontal scaling apply
43
+ * @param tm - the text matrix where the glyph starts
44
+ * @param glyph - the font's advance for the glyph's code, or undefined when the font could not be resolved, in which case a fallback width is assumed and the code is taken as one byte
45
+ * @param codes - the shown string
46
+ * @param offset - where the glyph's code starts in `codes`
47
+ * @returns the step, with the pen's new position and the end of the glyph's own width reported apart so a caller can tell the glyph's extent from the gap after it
48
+ */
49
+ declare function stepPastGlyph(gs: GraphicsState, tm: Matrix, glyph: GlyphAdvance | undefined, codes: Uint8Array<ArrayBuffer>, offset: number): GlyphStep;
50
+ //#endregion
51
+ export { ASCII_SPACE, FALLBACK_GLYPH_WIDTH_PER_1000, GLYPH_SPACE_PER_TEXT_SPACE_UNIT, GlyphStep, GraphicsState, TextObjectState, computeTrm, defaultTextObjectState, initialTextParameters, stepPastGlyph };
@@ -0,0 +1,59 @@
1
+ import { IDENTITY_MATRIX, multiplyMatrices, translationMatrix } from "./matrix.js";
2
+ //#region src/interpret-state.ts
3
+ const FALLBACK_GLYPH_WIDTH_PER_1000 = 500;
4
+ const GLYPH_SPACE_PER_TEXT_SPACE_UNIT = 1e3;
5
+ const ASCII_SPACE = 32;
6
+ function initialTextParameters() {
7
+ return {
8
+ fontResourceName: void 0,
9
+ fontSizePt: 0,
10
+ charSpace: 0,
11
+ wordSpace: 0,
12
+ horizScale: 1,
13
+ leading: 0,
14
+ rise: 0
15
+ };
16
+ }
17
+ function defaultTextObjectState() {
18
+ return {
19
+ tm: IDENTITY_MATRIX,
20
+ tlm: IDENTITY_MATRIX
21
+ };
22
+ }
23
+ function computeTrm(gs, text) {
24
+ const fontMatrix = [
25
+ gs.fontSizePt * gs.horizScale,
26
+ 0,
27
+ 0,
28
+ gs.fontSizePt,
29
+ 0,
30
+ gs.rise
31
+ ];
32
+ return multiplyMatrices(multiplyMatrices(fontMatrix, text.tm), gs.ctm);
33
+ }
34
+ /**
35
+ * Moves a text matrix past one glyph.
36
+ * @param gs - the graphics state whose font size, spacing and horizontal scaling apply
37
+ * @param tm - the text matrix where the glyph starts
38
+ * @param glyph - the font's advance for the glyph's code, or undefined when the font could not be resolved, in which case a fallback width is assumed and the code is taken as one byte
39
+ * @param codes - the shown string
40
+ * @param offset - where the glyph's code starts in `codes`
41
+ * @returns the step, with the pen's new position and the end of the glyph's own width reported apart so a caller can tell the glyph's extent from the gap after it
42
+ */
43
+ function stepPastGlyph(gs, tm, glyph, codes, offset) {
44
+ const widthPer1000 = glyph?.widthPer1000 ?? 500;
45
+ const byteLength = glyph?.byteLengthConsumed ?? 1;
46
+ const isSingleByteSpace = byteLength === 1 && codes[offset] === 32;
47
+ const spacing = gs.charSpace + (isSingleByteSpace ? gs.wordSpace : 0);
48
+ const vertical = glyph?.vertical;
49
+ const widthAdvance = vertical === void 0 ? widthPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt : vertical.displacementPer1000 / GLYPH_SPACE_PER_TEXT_SPACE_UNIT * gs.fontSizePt;
50
+ const along = (distance) => vertical === void 0 ? translationMatrix(distance * gs.horizScale, 0) : translationMatrix(0, distance);
51
+ return {
52
+ byteLength,
53
+ tm: multiplyMatrices(along(widthAdvance + spacing), tm),
54
+ widthEndTm: multiplyMatrices(along(widthAdvance), tm),
55
+ charSpaceAfter: gs.charSpace
56
+ };
57
+ }
58
+ //#endregion
59
+ export { ASCII_SPACE, FALLBACK_GLYPH_WIDTH_PER_1000, GLYPH_SPACE_PER_TEXT_SPACE_UNIT, computeTrm, defaultTextObjectState, initialTextParameters, stepPastGlyph };
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_interpret = require("./interpret-2_N_lh0h.cjs");
2
+ const require_interpret = require("./interpret-BkDxq0QA.cjs");
3
3
  exports.interpretContentStream = require_interpret.interpretContentStream;
4
4
  exports.numAt = require_interpret.numAt;
package/dist/interpret.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as numAt, t as interpretContentStream } from "./interpret-BeMKZGBm.js";
1
+ import { n as numAt, t as interpretContentStream } from "./interpret-BtzX-LWm.js";
2
2
  export { interpretContentStream, numAt };
@@ -11,7 +11,7 @@ import { NOOP_DIAGNOSTIC_SINK, PdfParseError } from "./diagnostics.js";
11
11
  import { openPdfDocument } from "./document.js";
12
12
  import { throwIfAborted } from "./util/abort.js";
13
13
  import { createFontResolver } from "./font-read.js";
14
- import { t as interpretContentStream } from "./interpret-BeMKZGBm.js";
14
+ import { t as interpretContentStream } from "./interpret-BtzX-LWm.js";
15
15
  import { normalizeRotation, pageRotationTransform } from "./read.js";
16
16
  import { parseGlyf } from "./glyf.js";
17
17
  import { decodeGlyphOutline } from "./glyf-contours.js";
@@ -11,7 +11,7 @@ const require_diagnostics = require("./diagnostics.cjs");
11
11
  const require_document = require("./document.cjs");
12
12
  const require_util_abort = require("./util/abort.cjs");
13
13
  const require_font_read = require("./font-read.cjs");
14
- const require_interpret = require("./interpret-2_N_lh0h.cjs");
14
+ const require_interpret = require("./interpret-BkDxq0QA.cjs");
15
15
  const require_read = require("./read.cjs");
16
16
  const require_glyf = require("./glyf.cjs");
17
17
  const require_glyf_contours = require("./glyf-contours.cjs");
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_raster = require("./raster-B8i3Tgjj.cjs");
2
+ const require_raster = require("./raster-De_zVqCu.cjs");
3
3
  exports.drawGlyphOutline = require_raster.drawGlyphOutline;
4
4
  exports.drawTextRun = require_raster.drawTextRun;
5
5
  exports.glyphOutlineSubpaths = require_raster.glyphOutlineSubpaths;
@@ -1,2 +1,2 @@
1
- import { a as drawTextRun, i as drawGlyphOutline, o as glyphOutlineSubpaths } from "./raster-CIc6ot36.js";
1
+ import { a as drawTextRun, i as drawGlyphOutline, o as glyphOutlineSubpaths } from "./raster-DCDcD7no.js";
2
2
  export { drawGlyphOutline, drawTextRun, glyphOutlineSubpaths };
package/dist/raster.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_raster = require("./raster-B8i3Tgjj.cjs");
2
+ const require_raster = require("./raster-De_zVqCu.cjs");
3
3
  exports.EXTENT_ZERO_EPSILON = require_raster.EXTENT_ZERO_EPSILON;
4
4
  exports.flattenCubic = require_raster.flattenCubic;
5
5
  exports.renderPdfPage = require_raster.renderPdfPage;
package/dist/raster.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as flattenCubic, r as renderPdfPage, t as EXTENT_ZERO_EPSILON } from "./raster-CIc6ot36.js";
1
+ import { n as flattenCubic, r as renderPdfPage, t as EXTENT_ZERO_EPSILON } from "./raster-DCDcD7no.js";
2
2
  export { EXTENT_ZERO_EPSILON, flattenCubic, renderPdfPage };
package/dist/read.cjs CHANGED
@@ -17,7 +17,7 @@ const require_diagnostics = require("./diagnostics.cjs");
17
17
  const require_document = require("./document.cjs");
18
18
  const require_util_abort = require("./util/abort.cjs");
19
19
  const require_font_read = require("./font-read.cjs");
20
- const require_interpret = require("./interpret-2_N_lh0h.cjs");
20
+ const require_interpret = require("./interpret-BkDxq0QA.cjs");
21
21
  const require_navigation = require("./navigation.cjs");
22
22
  //#region src/read.ts
23
23
  function skippedSurfacesOf(surfaces) {
package/dist/read.js CHANGED
@@ -16,7 +16,7 @@ import { NOOP_DIAGNOSTIC_SINK, PdfPageSelectionError, PdfParseError } from "./di
16
16
  import { openPdfDocument } from "./document.js";
17
17
  import { throwIfAborted } from "./util/abort.js";
18
18
  import { createFontResolver } from "./font-read.js";
19
- import { t as interpretContentStream } from "./interpret-BeMKZGBm.js";
19
+ import { t as interpretContentStream } from "./interpret-BtzX-LWm.js";
20
20
  import { createDestinationRegistry, readOutline } from "./navigation.js";
21
21
  //#region src/read.ts
22
22
  function skippedSurfacesOf(surfaces) {
@@ -63,6 +63,70 @@ function runGapPt(previous, next) {
63
63
  if (previousWidthPt === void 0) return;
64
64
  return project(next, rotationDeg).alongPt - (project(previous, rotationDeg).alongPt + previousWidthPt);
65
65
  }
66
+ const SCRIPT_MAX_SIZE_RATIO = .75;
67
+ const SCRIPT_MIN_SHIFT_EM = .05;
68
+ const SCRIPT_MAX_SHIFT_EM = .5;
69
+ /**
70
+ * Whether `candidate` is a superscript or subscript of `base`: a run set smaller than `base`, with its baseline moved off `base`'s by more than positioning noise and by less than half `base`'s em, and touching it along the baseline (no wider than a word space apart, on either side).
71
+ *
72
+ * Runs at different angles or in different writing modes are never scripts of one another, and neither is a pair whose gap cannot be derived because the earlier of the two states no advance width.
73
+ * @param base - the run the candidate might decorate
74
+ * @param candidate - the run that might be its superscript or subscript
75
+ * @returns which side of `base`'s baseline `candidate` is set on, or undefined when it is not a script of `base`
76
+ */
77
+ function scriptPositionOf(base, candidate) {
78
+ if (candidate.sizePt > SCRIPT_MAX_SIZE_RATIO * base.sizePt) return;
79
+ const rotationDeg = advanceAngleDeg(base);
80
+ if (rotationDeg !== advanceAngleDeg(candidate) || base.writingMode !== candidate.writingMode) return;
81
+ const baseProjected = project(base, rotationDeg);
82
+ const candidateProjected = project(candidate, rotationDeg);
83
+ const shiftPt = candidateProjected.acrossPt - baseProjected.acrossPt;
84
+ const magnitude = Math.abs(shiftPt);
85
+ if (magnitude < SCRIPT_MIN_SHIFT_EM * base.sizePt || magnitude > SCRIPT_MAX_SHIFT_EM * base.sizePt) return;
86
+ const gapPt = candidateProjected.alongPt >= baseProjected.alongPt ? runGapPt(base, candidate) : runGapPt(candidate, base);
87
+ if (gapPt === void 0 || gapPt <= -candidate.sizePt || gapPt >= .125 * candidate.sizePt) return;
88
+ return shiftPt > 0 ? "superscript" : "subscript";
89
+ }
90
+ /**
91
+ * Moves every run that is a superscript or subscript of a run on another line onto that run's line.
92
+ *
93
+ * A same-baseline tolerance that keeps two lines of type apart cannot also hold every script on its base's line: a script may be raised or lowered by up to half its base's em ({@link scriptPositionOf}), which is more than two thirds of the script's own em once it is set small, so baseline clustering can put a unit's raised exponent on a line of its own. This returns each such run to the line its base is on. Every move is decided against the lines as given, before any is made, so a run that moves is never taken for another run's base and the outcome does not depend on the order of the lines.
94
+ * @param lines - lines already grouped by baseline, of whatever shape the caller keeps them in
95
+ * @param runsOf - the runs on one of those lines, in any order
96
+ * @returns one entry per line, in the order given: the line itself and its runs, less every run that moved off it and followed by every run that moved onto it; a line every run moved off has no runs left, and is kept so that the caller decides what an empty line means
97
+ */
98
+ function attachScriptRuns(lines, runsOf) {
99
+ const runsByLine = lines.map((line) => runsOf(line));
100
+ const maxSizePt = Math.max(0, ...runsByLine.flatMap((runs) => runs.map((run) => run.sizePt)));
101
+ const reachPt = 1.5 * maxSizePt;
102
+ const placed = runsByLine.flatMap((runs, index) => runs.slice(0, 1).map((first) => {
103
+ const rotationDeg = advanceAngleDeg(first);
104
+ return {
105
+ index,
106
+ runs,
107
+ rotationDeg,
108
+ acrossPt: project(first, rotationDeg).acrossPt
109
+ };
110
+ }));
111
+ const moves = [];
112
+ runsByLine.forEach((runs, lineIndex) => {
113
+ for (const candidate of runs) {
114
+ if (candidate.sizePt > SCRIPT_MAX_SIZE_RATIO * maxSizePt) continue;
115
+ const rotationDeg = advanceAngleDeg(candidate);
116
+ const acrossPt = project(candidate, rotationDeg).acrossPt;
117
+ const target = placed.find((other) => other.index !== lineIndex && other.rotationDeg === rotationDeg && Math.abs(other.acrossPt - acrossPt) <= reachPt && other.runs.some((base) => scriptPositionOf(base, candidate) !== void 0));
118
+ if (target !== void 0) moves.push({
119
+ run: candidate,
120
+ to: target.index
121
+ });
122
+ }
123
+ });
124
+ const moved = new Set(moves.map((move) => move.run));
125
+ return lines.map((line, index) => ({
126
+ line,
127
+ runs: [...runsOf(line).filter((run) => !moved.has(run)), ...moves.filter((move) => move.to === index).map((move) => move.run)]
128
+ }));
129
+ }
66
130
  function clusterIntoLines(entries, toleranceEm) {
67
131
  const lines = [];
68
132
  for (const entry of entries) {
@@ -209,8 +273,15 @@ function groupPdfTextRuns(runs, options = {}) {
209
273
  const result = [];
210
274
  const ordered = [...buckets.values()].sort((a, b) => a.rotationDeg - b.rotationDeg || Number(a.vertical) - Number(b.vertical));
211
275
  for (const bucket of ordered) {
212
- const entries = bucket.runs.map((run) => project(run, bucket.rotationDeg)).sort((a, b) => b.acrossPt - a.acrossPt || a.alongPt - b.alongPt);
213
- for (const line of clusterIntoLines(entries, toleranceEm)) {
276
+ const attached = attachScriptRuns(clusterIntoLines(bucket.runs.map((run) => project(run, bucket.rotationDeg)).sort((a, b) => b.acrossPt - a.acrossPt || a.alongPt - b.alongPt), toleranceEm), (line) => line.entries.map((entry) => entry.run));
277
+ for (const { line: clusteredLine, runs: runsOnLine } of attached) {
278
+ const lineEntries = runsOnLine.map((run) => project(run, bucket.rotationDeg));
279
+ const anchor = runsOnLine.includes(clusteredLine.anchor.run) ? clusteredLine.anchor : lineEntries[0];
280
+ if (anchor === void 0) continue;
281
+ const line = {
282
+ anchor,
283
+ entries: lineEntries
284
+ };
214
285
  line.entries.sort((a, b) => a.alongPt - b.alongPt);
215
286
  result.push(buildLine(line, bucket.rotationDeg, bucket.vertical, wordGapEm, columnGapEm));
216
287
  }
@@ -221,6 +292,8 @@ function groupPdfTextRuns(runs, options = {}) {
221
292
  exports.DEFAULT_BASELINE_TOLERANCE_EM = DEFAULT_BASELINE_TOLERANCE_EM;
222
293
  exports.DEFAULT_COLUMN_GAP_EM = DEFAULT_COLUMN_GAP_EM;
223
294
  exports.DEFAULT_WORD_GAP_EM = DEFAULT_WORD_GAP_EM;
295
+ exports.attachScriptRuns = attachScriptRuns;
224
296
  exports.groupPdfTextRuns = groupPdfTextRuns;
225
297
  exports.runGapPt = runGapPt;
226
298
  exports.runsShareBaseline = runsShareBaseline;
299
+ exports.scriptPositionOf = scriptPositionOf;
@@ -56,6 +56,29 @@ declare function runsShareBaseline(a: PdfTextRunGeometry, b: PdfTextRunGeometry,
56
56
  * @returns the gap in points, or undefined when it cannot be derived
57
57
  */
58
58
  declare function runGapPt(previous: PdfTextRunGeometry, next: PdfTextRunGeometry): number | undefined;
59
+ type PdfScriptPosition = "superscript" | "subscript";
60
+ /**
61
+ * Whether `candidate` is a superscript or subscript of `base`: a run set smaller than `base`, with its baseline moved off `base`'s by more than positioning noise and by less than half `base`'s em, and touching it along the baseline (no wider than a word space apart, on either side).
62
+ *
63
+ * Runs at different angles or in different writing modes are never scripts of one another, and neither is a pair whose gap cannot be derived because the earlier of the two states no advance width.
64
+ * @param base - the run the candidate might decorate
65
+ * @param candidate - the run that might be its superscript or subscript
66
+ * @returns which side of `base`'s baseline `candidate` is set on, or undefined when it is not a script of `base`
67
+ */
68
+ declare function scriptPositionOf(base: PdfTextRunGeometry, candidate: PdfTextRunGeometry): PdfScriptPosition | undefined;
69
+ interface PdfLineWithScripts<TLine, TRun extends PdfTextRunGeometry> {
70
+ readonly line: TLine;
71
+ readonly runs: TRun[];
72
+ }
73
+ /**
74
+ * Moves every run that is a superscript or subscript of a run on another line onto that run's line.
75
+ *
76
+ * A same-baseline tolerance that keeps two lines of type apart cannot also hold every script on its base's line: a script may be raised or lowered by up to half its base's em ({@link scriptPositionOf}), which is more than two thirds of the script's own em once it is set small, so baseline clustering can put a unit's raised exponent on a line of its own. This returns each such run to the line its base is on. Every move is decided against the lines as given, before any is made, so a run that moves is never taken for another run's base and the outcome does not depend on the order of the lines.
77
+ * @param lines - lines already grouped by baseline, of whatever shape the caller keeps them in
78
+ * @param runsOf - the runs on one of those lines, in any order
79
+ * @returns one entry per line, in the order given: the line itself and its runs, less every run that moved off it and followed by every run that moved onto it; a line every run moved off has no runs left, and is kept so that the caller decides what an empty line means
80
+ */
81
+ declare function attachScriptRuns<TLine, TRun extends PdfTextRunGeometry>(lines: readonly TLine[], runsOf: (line: TLine) => readonly TRun[]): PdfLineWithScripts<TLine, TRun>[];
59
82
  /**
60
83
  * Groups positioned PDF text runs into lines of words.
61
84
  *
@@ -72,4 +95,4 @@ declare function runGapPt(previous: PdfTextRunGeometry, next: PdfTextRunGeometry
72
95
  */
73
96
  declare function groupPdfTextRuns<TRun extends PdfTextRunGeometry>(runs: readonly TRun[], options?: PdfTextGroupingOptions): PdfTextLine<TRun>[];
74
97
  //#endregion
75
- export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, groupPdfTextRuns, runGapPt, runsShareBaseline };
98
+ export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfLineWithScripts, PdfScriptPosition, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf };
@@ -56,6 +56,29 @@ declare function runsShareBaseline(a: PdfTextRunGeometry, b: PdfTextRunGeometry,
56
56
  * @returns the gap in points, or undefined when it cannot be derived
57
57
  */
58
58
  declare function runGapPt(previous: PdfTextRunGeometry, next: PdfTextRunGeometry): number | undefined;
59
+ type PdfScriptPosition = "superscript" | "subscript";
60
+ /**
61
+ * Whether `candidate` is a superscript or subscript of `base`: a run set smaller than `base`, with its baseline moved off `base`'s by more than positioning noise and by less than half `base`'s em, and touching it along the baseline (no wider than a word space apart, on either side).
62
+ *
63
+ * Runs at different angles or in different writing modes are never scripts of one another, and neither is a pair whose gap cannot be derived because the earlier of the two states no advance width.
64
+ * @param base - the run the candidate might decorate
65
+ * @param candidate - the run that might be its superscript or subscript
66
+ * @returns which side of `base`'s baseline `candidate` is set on, or undefined when it is not a script of `base`
67
+ */
68
+ declare function scriptPositionOf(base: PdfTextRunGeometry, candidate: PdfTextRunGeometry): PdfScriptPosition | undefined;
69
+ interface PdfLineWithScripts<TLine, TRun extends PdfTextRunGeometry> {
70
+ readonly line: TLine;
71
+ readonly runs: TRun[];
72
+ }
73
+ /**
74
+ * Moves every run that is a superscript or subscript of a run on another line onto that run's line.
75
+ *
76
+ * A same-baseline tolerance that keeps two lines of type apart cannot also hold every script on its base's line: a script may be raised or lowered by up to half its base's em ({@link scriptPositionOf}), which is more than two thirds of the script's own em once it is set small, so baseline clustering can put a unit's raised exponent on a line of its own. This returns each such run to the line its base is on. Every move is decided against the lines as given, before any is made, so a run that moves is never taken for another run's base and the outcome does not depend on the order of the lines.
77
+ * @param lines - lines already grouped by baseline, of whatever shape the caller keeps them in
78
+ * @param runsOf - the runs on one of those lines, in any order
79
+ * @returns one entry per line, in the order given: the line itself and its runs, less every run that moved off it and followed by every run that moved onto it; a line every run moved off has no runs left, and is kept so that the caller decides what an empty line means
80
+ */
81
+ declare function attachScriptRuns<TLine, TRun extends PdfTextRunGeometry>(lines: readonly TLine[], runsOf: (line: TLine) => readonly TRun[]): PdfLineWithScripts<TLine, TRun>[];
59
82
  /**
60
83
  * Groups positioned PDF text runs into lines of words.
61
84
  *
@@ -72,4 +95,4 @@ declare function runGapPt(previous: PdfTextRunGeometry, next: PdfTextRunGeometry
72
95
  */
73
96
  declare function groupPdfTextRuns<TRun extends PdfTextRunGeometry>(runs: readonly TRun[], options?: PdfTextGroupingOptions): PdfTextLine<TRun>[];
74
97
  //#endregion
75
- export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, groupPdfTextRuns, runGapPt, runsShareBaseline };
98
+ export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, PdfLineWithScripts, PdfScriptPosition, PdfTextBox, PdfTextGroupingOptions, PdfTextLine, PdfTextRunGeometry, PdfTextWord, PdfWordSeparator, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf };
@@ -62,6 +62,70 @@ function runGapPt(previous, next) {
62
62
  if (previousWidthPt === void 0) return;
63
63
  return project(next, rotationDeg).alongPt - (project(previous, rotationDeg).alongPt + previousWidthPt);
64
64
  }
65
+ const SCRIPT_MAX_SIZE_RATIO = .75;
66
+ const SCRIPT_MIN_SHIFT_EM = .05;
67
+ const SCRIPT_MAX_SHIFT_EM = .5;
68
+ /**
69
+ * Whether `candidate` is a superscript or subscript of `base`: a run set smaller than `base`, with its baseline moved off `base`'s by more than positioning noise and by less than half `base`'s em, and touching it along the baseline (no wider than a word space apart, on either side).
70
+ *
71
+ * Runs at different angles or in different writing modes are never scripts of one another, and neither is a pair whose gap cannot be derived because the earlier of the two states no advance width.
72
+ * @param base - the run the candidate might decorate
73
+ * @param candidate - the run that might be its superscript or subscript
74
+ * @returns which side of `base`'s baseline `candidate` is set on, or undefined when it is not a script of `base`
75
+ */
76
+ function scriptPositionOf(base, candidate) {
77
+ if (candidate.sizePt > SCRIPT_MAX_SIZE_RATIO * base.sizePt) return;
78
+ const rotationDeg = advanceAngleDeg(base);
79
+ if (rotationDeg !== advanceAngleDeg(candidate) || base.writingMode !== candidate.writingMode) return;
80
+ const baseProjected = project(base, rotationDeg);
81
+ const candidateProjected = project(candidate, rotationDeg);
82
+ const shiftPt = candidateProjected.acrossPt - baseProjected.acrossPt;
83
+ const magnitude = Math.abs(shiftPt);
84
+ if (magnitude < SCRIPT_MIN_SHIFT_EM * base.sizePt || magnitude > SCRIPT_MAX_SHIFT_EM * base.sizePt) return;
85
+ const gapPt = candidateProjected.alongPt >= baseProjected.alongPt ? runGapPt(base, candidate) : runGapPt(candidate, base);
86
+ if (gapPt === void 0 || gapPt <= -candidate.sizePt || gapPt >= .125 * candidate.sizePt) return;
87
+ return shiftPt > 0 ? "superscript" : "subscript";
88
+ }
89
+ /**
90
+ * Moves every run that is a superscript or subscript of a run on another line onto that run's line.
91
+ *
92
+ * A same-baseline tolerance that keeps two lines of type apart cannot also hold every script on its base's line: a script may be raised or lowered by up to half its base's em ({@link scriptPositionOf}), which is more than two thirds of the script's own em once it is set small, so baseline clustering can put a unit's raised exponent on a line of its own. This returns each such run to the line its base is on. Every move is decided against the lines as given, before any is made, so a run that moves is never taken for another run's base and the outcome does not depend on the order of the lines.
93
+ * @param lines - lines already grouped by baseline, of whatever shape the caller keeps them in
94
+ * @param runsOf - the runs on one of those lines, in any order
95
+ * @returns one entry per line, in the order given: the line itself and its runs, less every run that moved off it and followed by every run that moved onto it; a line every run moved off has no runs left, and is kept so that the caller decides what an empty line means
96
+ */
97
+ function attachScriptRuns(lines, runsOf) {
98
+ const runsByLine = lines.map((line) => runsOf(line));
99
+ const maxSizePt = Math.max(0, ...runsByLine.flatMap((runs) => runs.map((run) => run.sizePt)));
100
+ const reachPt = 1.5 * maxSizePt;
101
+ const placed = runsByLine.flatMap((runs, index) => runs.slice(0, 1).map((first) => {
102
+ const rotationDeg = advanceAngleDeg(first);
103
+ return {
104
+ index,
105
+ runs,
106
+ rotationDeg,
107
+ acrossPt: project(first, rotationDeg).acrossPt
108
+ };
109
+ }));
110
+ const moves = [];
111
+ runsByLine.forEach((runs, lineIndex) => {
112
+ for (const candidate of runs) {
113
+ if (candidate.sizePt > SCRIPT_MAX_SIZE_RATIO * maxSizePt) continue;
114
+ const rotationDeg = advanceAngleDeg(candidate);
115
+ const acrossPt = project(candidate, rotationDeg).acrossPt;
116
+ const target = placed.find((other) => other.index !== lineIndex && other.rotationDeg === rotationDeg && Math.abs(other.acrossPt - acrossPt) <= reachPt && other.runs.some((base) => scriptPositionOf(base, candidate) !== void 0));
117
+ if (target !== void 0) moves.push({
118
+ run: candidate,
119
+ to: target.index
120
+ });
121
+ }
122
+ });
123
+ const moved = new Set(moves.map((move) => move.run));
124
+ return lines.map((line, index) => ({
125
+ line,
126
+ runs: [...runsOf(line).filter((run) => !moved.has(run)), ...moves.filter((move) => move.to === index).map((move) => move.run)]
127
+ }));
128
+ }
65
129
  function clusterIntoLines(entries, toleranceEm) {
66
130
  const lines = [];
67
131
  for (const entry of entries) {
@@ -208,8 +272,15 @@ function groupPdfTextRuns(runs, options = {}) {
208
272
  const result = [];
209
273
  const ordered = [...buckets.values()].sort((a, b) => a.rotationDeg - b.rotationDeg || Number(a.vertical) - Number(b.vertical));
210
274
  for (const bucket of ordered) {
211
- const entries = bucket.runs.map((run) => project(run, bucket.rotationDeg)).sort((a, b) => b.acrossPt - a.acrossPt || a.alongPt - b.alongPt);
212
- for (const line of clusterIntoLines(entries, toleranceEm)) {
275
+ const attached = attachScriptRuns(clusterIntoLines(bucket.runs.map((run) => project(run, bucket.rotationDeg)).sort((a, b) => b.acrossPt - a.acrossPt || a.alongPt - b.alongPt), toleranceEm), (line) => line.entries.map((entry) => entry.run));
276
+ for (const { line: clusteredLine, runs: runsOnLine } of attached) {
277
+ const lineEntries = runsOnLine.map((run) => project(run, bucket.rotationDeg));
278
+ const anchor = runsOnLine.includes(clusteredLine.anchor.run) ? clusteredLine.anchor : lineEntries[0];
279
+ if (anchor === void 0) continue;
280
+ const line = {
281
+ anchor,
282
+ entries: lineEntries
283
+ };
213
284
  line.entries.sort((a, b) => a.alongPt - b.alongPt);
214
285
  result.push(buildLine(line, bucket.rotationDeg, bucket.vertical, wordGapEm, columnGapEm));
215
286
  }
@@ -217,4 +288,4 @@ function groupPdfTextRuns(runs, options = {}) {
217
288
  return result;
218
289
  }
219
290
  //#endregion
220
- export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, groupPdfTextRuns, runGapPt, runsShareBaseline };
291
+ export { DEFAULT_BASELINE_TOLERANCE_EM, DEFAULT_COLUMN_GAP_EM, DEFAULT_WORD_GAP_EM, attachScriptRuns, groupPdfTextRuns, runGapPt, runsShareBaseline, scriptPositionOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-codec",
3
- "version": "5.6.1",
3
+ "version": "5.7.1",
4
4
  "description": "Hand-written, dependency-minimal PDF codec: parses arbitrary real-world PDFs and generates new ones, built on its own codec-owned LayoutDocument item model and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {