pdf-codec 4.0.10 → 4.0.12

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
@@ -269,7 +269,7 @@ Dependency direction is strictly downward and checkable: `layout` imports only `
269
269
  - **The OpenType `MATH` table's `MathVariants` is parsed, stretchy-glyph assembly implemented, and the result genuinely drawable** via `MathAssembledGlyphs` (glyph-ID-addressed placements, since most construction glyphs have no Unicode code point — they draw directly because CID == GID here). An unencoded glyph gets no ToUnicode entry; the construction is wrapped in an `/ActualText` span carrying the operator's own text so it still extracts as `(`. `MathConstants` and `MathGlyphInfo` (italics correction, top-accent attachment) are parsed in full.
270
270
  - **What this package draws for a stretchy glyph is decided entirely by its caller.** Which operators a document actually stretches is a layout-engine decision — `documents.js`'s own `src/mathml/layout.ts` currently stretches vertical fences in an `mrow` and nothing else.
271
271
  - **Real per-glyph ink bounds are measured from the outline (`inkAscentPt`/`inkDescentPt`), computed by walking each glyph's Type 2 charstring.** `ascentPerEm`/`descentPerEm` remain alongside them as the uniform face-wide figure, still the right measure for anything sized against the font rather than particular characters, and the fallback for a glyph with no outline to measure. An ink box is genuinely tight, which for a math font is often _larger_ than the nominal metrics (over a tenth of STIX Two Math's repertoire draws above its nominal ascent). `inkDescentPt` is negative where the glyph's lowest ink sits above the baseline.
272
- - **A `LayoutLine`/`LayoutPath` `style` of `dashed`/`dotted` becomes a real dash-array (`d`) operator scaled to the stroke's own width; `double` becomes two genuinely separate offset strokes.** Dash lengths are stroke-width multiples so a hairline and a thick rule both read as recognisably dashed: `dashed` emits `[3w 3w] 0 d`, `dotted` emits `[0 2w] 0 d` with a `1 J` round cap (the zero on-length under a round cap paints a filled circle — exactly a dot; under PDF's default butt cap it paints nothing). Both are reset immediately after the paint operator (`[] 0 d`, `0 J`) since the graphics state persists for the whole content stream. `double` has no PDF operator and is drawn as geometry: width `w` splits into three equal bands, each rule `w/3` wide with centreline `w/3` offset, outer edges matching the single stroke's. Nothing on the read side recovers a stroke style — a dashed line read back comes back solid.
272
+ - **A `LayoutLine`/`LayoutPath` `style` of `dashed`/`dotted` becomes a real dash-array (`d`) operator scaled to the stroke's own width; `double` becomes two genuinely separate offset strokes.** Dash lengths are stroke-width multiples so a hairline and a thick rule both read as recognisably dashed: `dashed` emits `[3w 3w] 0 d`, `dotted` emits `[0 2w] 0 d` with a `1 J` round cap (the zero on-length under a round cap paints a filled circle — exactly a dot; under PDF's default butt cap it paints nothing). Both are reset immediately after the paint operator (`[] 0 d`, `0 J`) since the graphics state persists for the whole content stream. `double` has no PDF operator and is drawn as geometry: width `w` splits into three equal bands, each rule `w/3` wide with centreline `w/3` offset, outer edges matching the single stroke's. The read side tracks the `d` operator's own dash array as graphics state and recovers `dashed`/`dotted` back onto a stroked `LayoutLine`/`LayoutPath` from it — a zero on-length reads as `dotted`, any other non-empty array as `dashed`, an empty array (or none at all) as `solid`. `double` has no operator of its own to recover from, so it reads back as whatever geometry its two offset strokes actually are (typically two plain solid lines or paths), not as `double`.
273
273
  - **An embedded `CIDFontType2` program needs no `cmap` table of its own**, and `sfnt-subset.ts`'s output doesn't carry one — character code → CID goes through the `Type0` font's `/Encoding` (Identity-H, so CID == character code), and CID → GID through `/CIDToGIDMap /Identity` (matching the GID-preserving design). Both happen inside the PDF's object graph, before the embedded font program is consulted (ISO 32000-1 9.7.4.2).
274
274
  - **`GPOS` pair kerning is read and applied for an embedded face; no other OpenType layout feature is.** `GSUB` (ligatures, contextual alternates, small caps) is never applied — a face's `fi` ligature is drawn as two separate glyphs. The legacy `kern` table is not read either (neither Carlito nor Caladea ships one). A kerned run is shown with `TJ`, and the sign of a `TJ` number is the opposite of the adjustment it expresses (ISO 32000-1 9.4.3: a positive number moves the next glyph closer). A run with no kerning pairs stays as one unsplit hex string with `Tj`.
275
275
  - **Kerning applies to whole shown strings, so a wrap decision does not see a pair straddling the boundary between two separately-measured words.** The width a line reports is the width the page draws; making the wrap decision itself exact would mean widening the `TextMeasurer` port for a sub-point difference that only ever errs towards breaking a line early.
package/dist/codec.d.cts CHANGED
@@ -238,6 +238,7 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
238
238
  pdf: "pdf";
239
239
  epub: "epub";
240
240
  rtf: "rtf";
241
+ wpd: "wpd";
241
242
  }>;
242
243
  xml: z.ZodString;
243
244
  }, z.core.$strict>>;
@@ -303,6 +304,7 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
303
304
  pdf: "pdf";
304
305
  epub: "epub";
305
306
  rtf: "rtf";
307
+ wpd: "wpd";
306
308
  }>;
307
309
  xml: z.ZodString;
308
310
  }, z.core.$strict>>>;
package/dist/codec.d.ts CHANGED
@@ -238,6 +238,7 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
238
238
  pdf: "pdf";
239
239
  epub: "epub";
240
240
  rtf: "rtf";
241
+ wpd: "wpd";
241
242
  }>;
242
243
  xml: z.ZodString;
243
244
  }, z.core.$strict>>;
@@ -303,6 +304,7 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
303
304
  pdf: "pdf";
304
305
  epub: "epub";
305
306
  rtf: "rtf";
307
+ wpd: "wpd";
306
308
  }>;
307
309
  xml: z.ZodString;
308
310
  }, z.core.$strict>>>;
@@ -250,7 +250,11 @@ function detectEllipse(subpath, paint) {
250
250
  stroke: paint.stroke
251
251
  };
252
252
  }
253
- function detectLine(subpath, paint) {
253
+ function strokeStyleFromDashArray(dashArray) {
254
+ if (dashArray.length === 0) return;
255
+ return dashArray.length === 2 && dashArray[0] === 0 ? "dotted" : "dashed";
256
+ }
257
+ function detectLine(subpath, paint, style) {
254
258
  const segment = subpath.segments[0];
255
259
  if (subpath.closed || subpath.segments.length !== 1 || segment?.kind !== "line") return;
256
260
  if (paint.fill !== void 0 || paint.stroke === void 0) return;
@@ -261,13 +265,14 @@ function detectLine(subpath, paint) {
261
265
  x2Pt: segment.xPt,
262
266
  y2Pt: segment.yPt,
263
267
  color: paint.stroke.color,
264
- widthPt: paint.stroke.widthPt
268
+ widthPt: paint.stroke.widthPt,
269
+ ...style !== void 0 ? { style } : {}
265
270
  };
266
271
  }
267
- function classifyShape(subpaths, paint) {
272
+ function classifyShape(subpaths, paint, style) {
268
273
  const subpath = subpaths[0];
269
274
  if (subpaths.length !== 1 || subpath === void 0) return;
270
- return detectRect(subpath, paint) ?? detectEllipse(subpath, paint) ?? detectLine(subpath, paint);
275
+ return detectRect(subpath, paint) ?? detectEllipse(subpath, paint) ?? detectLine(subpath, paint, style);
271
276
  }
272
277
  function interpretContentStream(bytes, resources, context) {
273
278
  const items = [];
@@ -276,6 +281,7 @@ function interpretContentStream(bytes, resources, context) {
276
281
  fillColor: document_schema_js.COLOR_BLACK,
277
282
  strokeColor: document_schema_js.COLOR_BLACK,
278
283
  lineWidth: DEFAULT_LINE_WIDTH_PT,
284
+ dashArray: [],
279
285
  ...initialTextParameters()
280
286
  }, context, items, 0, []);
281
287
  return items;
@@ -402,11 +408,13 @@ function runContentStream(bytes, resources, initialState, context, items, depth,
402
408
  widthPt: gs.lineWidth
403
409
  } : void 0
404
410
  };
405
- pushItem(classifyShape(pathSubpaths, paint) ?? {
411
+ const strokeStyle = isStrokeOp ? strokeStyleFromDashArray(gs.dashArray) : void 0;
412
+ pushItem(classifyShape(pathSubpaths, paint, strokeStyle) ?? {
406
413
  kind: "path",
407
414
  subpaths: pathSubpaths,
408
415
  fillRule: paintFillRuleFor(operator),
409
- ...paint
416
+ ...paint,
417
+ ...strokeStyle !== void 0 ? { style: strokeStyle } : {}
410
418
  });
411
419
  }
412
420
  resetPath();
@@ -705,6 +713,12 @@ function runContentStream(bytes, resources, initialState, context, items, depth,
705
713
  lineWidth: numAt(operands, 0)
706
714
  };
707
715
  break;
716
+ case "d":
717
+ gs = {
718
+ ...gs,
719
+ dashArray: (require_objects.asArray(operands[0]) ?? []).map((entry) => require_objects.asNumber(entry) ?? 0)
720
+ };
721
+ break;
708
722
  case "f":
709
723
  case "F":
710
724
  case "f*":
@@ -1,7 +1,7 @@
1
1
  import { PdfDiagnosticSink } from "./diagnostics.cjs";
2
2
  import { r as Matrix } from "./matrix-B7_SBKQ5.cjs";
3
3
  import { PdfDict, PdfObject } from "./objects.cjs";
4
- import { Color } from "document-schema.js";
4
+ import { Color, ContentStrokeStyle } from "document-schema.js";
5
5
  //#region src/interpret.d.ts
6
6
  interface ExtractedTextRun {
7
7
  readonly kind: "text";
@@ -48,6 +48,7 @@ interface ExtractedLine {
48
48
  readonly y2Pt: number;
49
49
  readonly color: Color;
50
50
  readonly widthPt: number;
51
+ readonly style?: ContentStrokeStyle;
51
52
  readonly layerName?: string;
52
53
  readonly mcid?: number;
53
54
  }
@@ -74,6 +75,7 @@ interface ExtractedPath extends ExtractedPaint {
74
75
  readonly kind: "path";
75
76
  readonly subpaths: readonly ExtractedSubpath[];
76
77
  readonly fillRule: "nonzero" | "evenodd";
78
+ readonly style?: ContentStrokeStyle;
77
79
  }
78
80
  interface ExtractedImage {
79
81
  readonly kind: "image";
@@ -1,7 +1,7 @@
1
1
  import { PdfDiagnosticSink } from "./diagnostics.js";
2
2
  import { r as Matrix } from "./matrix-B7_SBKQ5.js";
3
3
  import { PdfDict, PdfObject } from "./objects.js";
4
- import { Color } from "document-schema.js";
4
+ import { Color, ContentStrokeStyle } from "document-schema.js";
5
5
  //#region src/interpret.d.ts
6
6
  interface ExtractedTextRun {
7
7
  readonly kind: "text";
@@ -48,6 +48,7 @@ interface ExtractedLine {
48
48
  readonly y2Pt: number;
49
49
  readonly color: Color;
50
50
  readonly widthPt: number;
51
+ readonly style?: ContentStrokeStyle;
51
52
  readonly layerName?: string;
52
53
  readonly mcid?: number;
53
54
  }
@@ -74,6 +75,7 @@ interface ExtractedPath extends ExtractedPaint {
74
75
  readonly kind: "path";
75
76
  readonly subpaths: readonly ExtractedSubpath[];
76
77
  readonly fillRule: "nonzero" | "evenodd";
78
+ readonly style?: ContentStrokeStyle;
77
79
  }
78
80
  interface ExtractedImage {
79
81
  readonly kind: "image";
package/dist/interpret.js CHANGED
@@ -249,7 +249,11 @@ function detectEllipse(subpath, paint) {
249
249
  stroke: paint.stroke
250
250
  };
251
251
  }
252
- function detectLine(subpath, paint) {
252
+ function strokeStyleFromDashArray(dashArray) {
253
+ if (dashArray.length === 0) return;
254
+ return dashArray.length === 2 && dashArray[0] === 0 ? "dotted" : "dashed";
255
+ }
256
+ function detectLine(subpath, paint, style) {
253
257
  const segment = subpath.segments[0];
254
258
  if (subpath.closed || subpath.segments.length !== 1 || segment?.kind !== "line") return;
255
259
  if (paint.fill !== void 0 || paint.stroke === void 0) return;
@@ -260,13 +264,14 @@ function detectLine(subpath, paint) {
260
264
  x2Pt: segment.xPt,
261
265
  y2Pt: segment.yPt,
262
266
  color: paint.stroke.color,
263
- widthPt: paint.stroke.widthPt
267
+ widthPt: paint.stroke.widthPt,
268
+ ...style !== void 0 ? { style } : {}
264
269
  };
265
270
  }
266
- function classifyShape(subpaths, paint) {
271
+ function classifyShape(subpaths, paint, style) {
267
272
  const subpath = subpaths[0];
268
273
  if (subpaths.length !== 1 || subpath === void 0) return;
269
- return detectRect(subpath, paint) ?? detectEllipse(subpath, paint) ?? detectLine(subpath, paint);
274
+ return detectRect(subpath, paint) ?? detectEllipse(subpath, paint) ?? detectLine(subpath, paint, style);
270
275
  }
271
276
  function interpretContentStream(bytes, resources, context) {
272
277
  const items = [];
@@ -275,6 +280,7 @@ function interpretContentStream(bytes, resources, context) {
275
280
  fillColor: COLOR_BLACK,
276
281
  strokeColor: COLOR_BLACK,
277
282
  lineWidth: DEFAULT_LINE_WIDTH_PT,
283
+ dashArray: [],
278
284
  ...initialTextParameters()
279
285
  }, context, items, 0, []);
280
286
  return items;
@@ -401,11 +407,13 @@ function runContentStream(bytes, resources, initialState, context, items, depth,
401
407
  widthPt: gs.lineWidth
402
408
  } : void 0
403
409
  };
404
- pushItem(classifyShape(pathSubpaths, paint) ?? {
410
+ const strokeStyle = isStrokeOp ? strokeStyleFromDashArray(gs.dashArray) : void 0;
411
+ pushItem(classifyShape(pathSubpaths, paint, strokeStyle) ?? {
405
412
  kind: "path",
406
413
  subpaths: pathSubpaths,
407
414
  fillRule: paintFillRuleFor(operator),
408
- ...paint
415
+ ...paint,
416
+ ...strokeStyle !== void 0 ? { style: strokeStyle } : {}
409
417
  });
410
418
  }
411
419
  resetPath();
@@ -704,6 +712,12 @@ function runContentStream(bytes, resources, initialState, context, items, depth,
704
712
  lineWidth: numAt(operands, 0)
705
713
  };
706
714
  break;
715
+ case "d":
716
+ gs = {
717
+ ...gs,
718
+ dashArray: (asArray(operands[0]) ?? []).map((entry) => asNumber(entry) ?? 0)
719
+ };
720
+ break;
707
721
  case "f":
708
722
  case "F":
709
723
  case "f*":
package/dist/layout.d.cts CHANGED
@@ -442,6 +442,7 @@ declare const LayoutAnnotationSchema: z.ZodObject<{
442
442
  pdf: "pdf";
443
443
  epub: "epub";
444
444
  rtf: "rtf";
445
+ wpd: "wpd";
445
446
  }>;
446
447
  xml: z.ZodString;
447
448
  }, z.core.$strict>>;
@@ -658,6 +659,7 @@ declare const LayoutPageSchema: z.ZodObject<{
658
659
  pdf: "pdf";
659
660
  epub: "epub";
660
661
  rtf: "rtf";
662
+ wpd: "wpd";
661
663
  }>;
662
664
  xml: z.ZodString;
663
665
  }, z.core.$strict>>;
@@ -999,6 +1001,7 @@ declare const LayoutDocumentSchema: z.ZodObject<{
999
1001
  pdf: "pdf";
1000
1002
  epub: "epub";
1001
1003
  rtf: "rtf";
1004
+ wpd: "wpd";
1002
1005
  }>;
1003
1006
  xml: z.ZodString;
1004
1007
  }, z.core.$strict>>;
@@ -1064,6 +1067,7 @@ declare const LayoutDocumentSchema: z.ZodObject<{
1064
1067
  pdf: "pdf";
1065
1068
  epub: "epub";
1066
1069
  rtf: "rtf";
1070
+ wpd: "wpd";
1067
1071
  }>;
1068
1072
  xml: z.ZodString;
1069
1073
  }, z.core.$strict>>>;
package/dist/layout.d.ts CHANGED
@@ -442,6 +442,7 @@ declare const LayoutAnnotationSchema: z.ZodObject<{
442
442
  pdf: "pdf";
443
443
  epub: "epub";
444
444
  rtf: "rtf";
445
+ wpd: "wpd";
445
446
  }>;
446
447
  xml: z.ZodString;
447
448
  }, z.core.$strict>>;
@@ -658,6 +659,7 @@ declare const LayoutPageSchema: z.ZodObject<{
658
659
  pdf: "pdf";
659
660
  epub: "epub";
660
661
  rtf: "rtf";
662
+ wpd: "wpd";
661
663
  }>;
662
664
  xml: z.ZodString;
663
665
  }, z.core.$strict>>;
@@ -999,6 +1001,7 @@ declare const LayoutDocumentSchema: z.ZodObject<{
999
1001
  pdf: "pdf";
1000
1002
  epub: "epub";
1001
1003
  rtf: "rtf";
1004
+ wpd: "wpd";
1002
1005
  }>;
1003
1006
  xml: z.ZodString;
1004
1007
  }, z.core.$strict>>;
@@ -1064,6 +1067,7 @@ declare const LayoutDocumentSchema: z.ZodObject<{
1064
1067
  pdf: "pdf";
1065
1068
  epub: "epub";
1066
1069
  rtf: "rtf";
1070
+ wpd: "wpd";
1067
1071
  }>;
1068
1072
  xml: z.ZodString;
1069
1073
  }, z.core.$strict>>>;
package/dist/read.cjs CHANGED
@@ -426,6 +426,7 @@ function convertLine(item, pageMatrix) {
426
426
  y2Pt: p2.y,
427
427
  color: item.color,
428
428
  widthPt: item.widthPt,
429
+ ...item.style !== void 0 ? { style: item.style } : {},
429
430
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
430
431
  };
431
432
  }
@@ -482,6 +483,7 @@ function convertPath(item, pageMatrix) {
482
483
  ...item.fill !== void 0 ? { fill: item.fill } : {},
483
484
  ...item.fill !== void 0 && item.fillRule === "evenodd" ? { fillRule: "evenodd" } : {},
484
485
  ...item.stroke !== void 0 ? { stroke: item.stroke } : {},
486
+ ...item.style !== void 0 ? { style: item.style } : {},
485
487
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
486
488
  };
487
489
  }
package/dist/read.js CHANGED
@@ -425,6 +425,7 @@ function convertLine(item, pageMatrix) {
425
425
  y2Pt: p2.y,
426
426
  color: item.color,
427
427
  widthPt: item.widthPt,
428
+ ...item.style !== void 0 ? { style: item.style } : {},
428
429
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
429
430
  };
430
431
  }
@@ -481,6 +482,7 @@ function convertPath(item, pageMatrix) {
481
482
  ...item.fill !== void 0 ? { fill: item.fill } : {},
482
483
  ...item.fill !== void 0 && item.fillRule === "evenodd" ? { fillRule: "evenodd" } : {},
483
484
  ...item.stroke !== void 0 ? { stroke: item.stroke } : {},
485
+ ...item.style !== void 0 ? { style: item.style } : {},
484
486
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
485
487
  };
486
488
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-codec",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
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": {
@@ -100,7 +100,7 @@
100
100
  "packageManager": "pnpm@11.6.0",
101
101
  "dependencies": {
102
102
  "byte-codec": "1.4.1",
103
- "document-schema.js": "7.4.0",
103
+ "document-schema.js": "7.5.0",
104
104
  "fflate": "0.8.3",
105
105
  "zod": "4.4.3"
106
106
  },