reamkit 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +65 -0
  6. package/dist/esm/core/drawingml/shape-render.js +13 -1
  7. package/dist/esm/core/font/index.d.ts +2 -0
  8. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  9. package/dist/esm/core/font/ttf-build.js +1224 -0
  10. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  11. package/dist/esm/core/font/ttf-subset.js +14 -1
  12. package/dist/esm/core/fonts/index.d.ts +1 -1
  13. package/dist/esm/core/fonts/provider.d.ts +17 -0
  14. package/dist/esm/core/fonts/provider.js +27 -2
  15. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  16. package/dist/esm/core/fonts/remote-fonts.js +108 -18
  17. package/dist/esm/core/ir/flow.d.ts +88 -1
  18. package/dist/esm/core/numbering/index.d.ts +1 -1
  19. package/dist/esm/core/numbering/state.d.ts +11 -1
  20. package/dist/esm/core/numbering/state.js +10 -1
  21. package/dist/esm/core/style-cascade/resolver.js +35 -4
  22. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  23. package/dist/esm/core/style-cascade/types.js +3 -0
  24. package/dist/esm/index.d.ts +2 -2
  25. package/dist/esm/layout/page-doc.d.ts +10 -3
  26. package/dist/esm/layout/styled-layout.d.ts +48 -7
  27. package/dist/esm/layout/styled-layout.js +881 -119
  28. package/dist/esm/layout/turned-section.d.ts +38 -0
  29. package/dist/esm/layout/turned-section.js +193 -0
  30. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  31. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  32. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  33. package/dist/esm/pdf-reader/annots.js +86 -9
  34. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  35. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  36. package/dist/esm/pdf-reader/cmap.js +5 -2
  37. package/dist/esm/pdf-reader/content.d.ts +70 -4
  38. package/dist/esm/pdf-reader/content.js +172 -12
  39. package/dist/esm/pdf-reader/display.d.ts +36 -0
  40. package/dist/esm/pdf-reader/display.js +82 -1
  41. package/dist/esm/pdf-reader/document.js +5 -1
  42. package/dist/esm/pdf-reader/embedded-fonts.d.ts +25 -0
  43. package/dist/esm/pdf-reader/embedded-fonts.js +78 -9
  44. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  45. package/dist/esm/pdf-reader/encodings.js +25 -3
  46. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  47. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  48. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  49. package/dist/esm/pdf-reader/figures.js +433 -0
  50. package/dist/esm/pdf-reader/flow-build.d.ts +224 -7
  51. package/dist/esm/pdf-reader/flow-build.js +545 -39
  52. package/dist/esm/pdf-reader/font.d.ts +27 -1
  53. package/dist/esm/pdf-reader/font.js +632 -63
  54. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  55. package/dist/esm/pdf-reader/glyf-outline.js +148 -3
  56. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  57. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  58. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  59. package/dist/esm/pdf-reader/image-decode.js +70 -4
  60. package/dist/esm/pdf-reader/images.d.ts +5 -0
  61. package/dist/esm/pdf-reader/images.js +4 -2
  62. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  63. package/dist/esm/pdf-reader/jbig2.js +78 -16
  64. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  65. package/dist/esm/pdf-reader/jpeg.js +21 -1
  66. package/dist/esm/pdf-reader/layout.d.ts +119 -2
  67. package/dist/esm/pdf-reader/layout.js +2219 -184
  68. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  69. package/dist/esm/pdf-reader/lexer.js +17 -0
  70. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  71. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  72. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  73. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  74. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  75. package/dist/esm/pdf-reader/regions.js +167 -0
  76. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  77. package/dist/esm/pdf-reader/shading.js +181 -11
  78. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  79. package/dist/esm/pdf-reader/tagged.js +207 -28
  80. package/dist/esm/pdf-reader/text-rules.js +1 -1
  81. package/dist/esm/pdf-reader/text.d.ts +6 -3
  82. package/dist/esm/pdf-reader/text.js +106 -22
  83. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  84. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  85. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  86. package/dist/esm/pdf-reader/vector.js +105 -6
  87. package/dist/esm/word/doc/doc-reader.js +6 -2
  88. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  89. package/dist/esm/word/doc/doc-text.js +19 -1
  90. package/dist/esm/word/document-parser.d.ts +2 -2
  91. package/dist/esm/word/document-parser.js +11 -1
  92. package/dist/esm/word/docx-reader.js +5 -3
  93. package/dist/esm/word/docx-writer.js +341 -54
  94. package/dist/esm/word/drawing-parser.d.ts +5 -3
  95. package/dist/esm/word/drawing-parser.js +56 -10
  96. package/dist/esm/word/font-embed.d.ts +30 -0
  97. package/dist/esm/word/font-embed.js +173 -0
  98. package/dist/esm/word/font-table.d.ts +10 -0
  99. package/dist/esm/word/font-table.js +13 -1
  100. package/dist/esm/word/index.js +1 -1
  101. package/dist/esm/word/numbering-parser.d.ts +3 -1
  102. package/dist/esm/word/numbering-parser.js +2 -1
  103. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  104. package/dist/esm/word/paragraph-properties.js +16 -2
  105. package/dist/esm/word/run-properties.js +26 -0
  106. package/package.json +12 -5
@@ -1,6 +1,9 @@
1
1
  import { PDF_NULL, PdfHexString, PdfName } from "../pdf/objects.js";
2
+ import { bidiClass } from "../core/bidi/char-types.js";
3
+ import { reorderVisual } from "../core/bidi/algorithm.js";
4
+ import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
2
5
  import { Lexer } from "./lexer.js";
3
- import { cmykHex, grayHex, rgbHex, spaceColor } from "./shading.js";
6
+ import { cmykHex, dashLengths, grayHex, rgbHex, spaceColor } from "./shading.js";
4
7
  //#region src/pdf-reader/content.ts
5
8
  /**
6
9
  * A painted path emitted by the path-painting operators (§8.5.3), captured in
@@ -101,9 +104,13 @@ function initialState() {
101
104
  fillColor: "000000",
102
105
  strokeColor: "000000",
103
106
  lineWidth: 1,
107
+ dash: [],
108
+ lineCap: 0,
104
109
  fillGradient: void 0,
105
110
  fillPattern: void 0,
111
+ fillPatternPaint: void 0,
106
112
  fillAlpha: 1,
113
+ strokeAlpha: 1,
107
114
  fillDarkens: false,
108
115
  blendMode: void 0,
109
116
  softMask: false,
@@ -184,6 +191,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
184
191
  const glyphs = [];
185
192
  const painted = [];
186
193
  const outlines = [];
194
+ const codesShown = /* @__PURE__ */ new Map();
187
195
  const lexer = new Lexer(bytes);
188
196
  const stack = [];
189
197
  let state = initialState();
@@ -229,6 +237,25 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
229
237
  y
230
238
  });
231
239
  };
240
+ const currentPoint = () => {
241
+ for (let i = path.length - 1; i >= 0; i--) {
242
+ const seg = path[i];
243
+ if (seg.op === "close") {
244
+ for (let k = i - 1; k >= 0; k--) {
245
+ const start = path[k];
246
+ if (start.op === "move") return {
247
+ x: start.x,
248
+ y: start.y
249
+ };
250
+ }
251
+ return;
252
+ }
253
+ return {
254
+ x: seg.x,
255
+ y: seg.y
256
+ };
257
+ }
258
+ };
232
259
  const rectTo = (x, y, w, h) => {
233
260
  moveTo(x, y);
234
261
  lineTo(x + w, y);
@@ -241,6 +268,11 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
241
268
  const scale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1;
242
269
  return state.lineWidth * scale;
243
270
  };
271
+ const ctmDash = () => {
272
+ const m = state.ctm;
273
+ const scale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1;
274
+ return state.dash.map((n) => n * scale);
275
+ };
244
276
  const paintPath = (fill, stroke) => {
245
277
  if (path.length >= 2 && (fill || stroke) && visible()) {
246
278
  const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
@@ -249,6 +281,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
249
281
  segs: path,
250
282
  ...state.clip ? { clip: state.clip } : {},
251
283
  ...fill && state.fillPattern !== void 0 ? { patternName: state.fillPattern } : {},
284
+ ...fill && state.fillPattern !== void 0 && state.fillPatternPaint !== void 0 ? { patternPaint: state.fillPatternPaint } : {},
252
285
  ...fill ? { fillHex: state.fillColor } : {},
253
286
  ...fill && state.fillAlpha < 1 ? { alpha: state.fillAlpha } : {},
254
287
  ...fill && state.fillDarkens ? { darkens: true } : {},
@@ -259,6 +292,10 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
259
292
  strokeHex: state.strokeColor,
260
293
  lineWidth: ctmLineWidth()
261
294
  } : {},
295
+ ...stroke && state.dash.length > 0 ? { dash: ctmDash() } : {},
296
+ ...stroke && state.lineCap === 1 ? { cap: "round" } : {},
297
+ ...stroke && state.lineCap === 2 ? { cap: "square" } : {},
298
+ ...stroke && state.strokeAlpha < 1 ? { strokeAlpha: state.strokeAlpha } : {},
262
299
  ...mcid !== void 0 ? { mcid } : {}
263
300
  });
264
301
  }
@@ -337,6 +374,11 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
337
374
  const bytes = toBytes(operand);
338
375
  const codes = state.font.splitCodes ? state.font.splitCodes(bytes) : splitCodes(bytes, state.font.bytesPerCode);
339
376
  for (const code of codes) advanceGlyph(code);
377
+ if (state.font.program && visible() && state.renderMode !== 3 && state.renderMode !== 7) {
378
+ let set = codesShown.get(state.font);
379
+ if (!set) codesShown.set(state.font, set = /* @__PURE__ */ new Set());
380
+ for (const code of codes) set.add(code);
381
+ }
340
382
  return state.font.decode(codes);
341
383
  };
342
384
  const emitAt = (origin, shown, end) => {
@@ -357,6 +399,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
357
399
  fontSizePt: Math.abs(state.fontSize) * scaleY,
358
400
  spaceWidthPt: (state.font.width(32) / 1e3 * state.fontSize + state.charSpacing + state.wordSpacing) * scaleX,
359
401
  fontKey: state.fontKey,
402
+ ...state.font.fixedPitch === true ? { fixedPitch: true } : {},
360
403
  ...state.font.name !== void 0 ? { fontName: state.font.name } : {},
361
404
  ...state.font.type3 ? { type3: true } : {},
362
405
  ...state.renderMode === 3 || state.renderMode === 7 ? { invisible: true } : {},
@@ -519,6 +562,8 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
519
562
  if (named !== void 0) {
520
563
  state.fillGradient = shadings.get(named);
521
564
  state.fillPattern = state.fillGradient ? void 0 : named;
565
+ const components = operands.filter((o) => typeof o === "number");
566
+ state.fillPatternPaint = components.length > 0 ? spaceColor(components, void 0) : void 0;
522
567
  break;
523
568
  }
524
569
  const hex = colorOfOperands(operands, state.fillSpace);
@@ -531,7 +576,12 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
531
576
  }
532
577
  case "SCN":
533
578
  case "SC": {
534
- if (operands[operands.length - 1] instanceof PdfName) break;
579
+ const last = operands[operands.length - 1];
580
+ if (last instanceof PdfName) {
581
+ const sweep = shadings.get(last.value);
582
+ if (sweep) state.strokeColor = midGradient(sweep);
583
+ break;
584
+ }
535
585
  const hex = colorOfOperands(operands, state.strokeSpace);
536
586
  if (hex !== void 0) state.strokeColor = hex;
537
587
  break;
@@ -548,6 +598,14 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
548
598
  case "w":
549
599
  state.lineWidth = num(0);
550
600
  break;
601
+ case "d":
602
+ state.dash = dashLengths(operands[0]);
603
+ break;
604
+ case "J": {
605
+ const cap = num(0);
606
+ if (cap === 0 || cap === 1 || cap === 2) state.lineCap = cap;
607
+ break;
608
+ }
551
609
  case "m":
552
610
  moveTo(num(0), num(1));
553
611
  break;
@@ -557,6 +615,26 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
557
615
  case "c":
558
616
  curveTo(num(0), num(1), num(2), num(3), num(4), num(5));
559
617
  break;
618
+ case "v": {
619
+ const at = currentPoint();
620
+ if (at) {
621
+ const [x2, y2] = toPage(num(0), num(1));
622
+ const [x, y] = toPage(num(2), num(3));
623
+ path.push({
624
+ op: "cubic",
625
+ x1: at.x,
626
+ y1: at.y,
627
+ x2,
628
+ y2,
629
+ x,
630
+ y
631
+ });
632
+ }
633
+ break;
634
+ }
635
+ case "y":
636
+ curveTo(num(0), num(1), num(2), num(3), num(2), num(3));
637
+ break;
560
638
  case "re":
561
639
  rectTo(num(0), num(1), num(2), num(3));
562
640
  break;
@@ -610,6 +688,10 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
610
688
  state.blendMode = paint.blend;
611
689
  }
612
690
  if (paint.masked !== void 0) state.softMask = paint.masked;
691
+ if (paint.lineWidth !== void 0) state.lineWidth = paint.lineWidth;
692
+ if (paint.dash !== void 0) state.dash = paint.dash;
693
+ if (paint.lineCap !== void 0) state.lineCap = paint.lineCap;
694
+ if (paint.strokeAlpha !== void 0) state.strokeAlpha = paint.strokeAlpha;
613
695
  }
614
696
  break;
615
697
  }
@@ -670,7 +752,8 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
670
752
  vectors,
671
753
  glyphs,
672
754
  shadings: painted,
673
- outlines
755
+ outlines,
756
+ shown: codesShown
674
757
  };
675
758
  }
676
759
  /** One path segment through a matrix — the glyph's own space onto the page. */
@@ -713,12 +796,27 @@ function strokesText(mode) {
713
796
  * ArabicCIDTrueType.pdf came out mirrored for exactly that reason: the reader
714
797
  * passed visual order through and the layout reversed it a second time.
715
798
  *
716
- * A run is reversed only when it is wholly right-to-left. Anything mixed — a
717
- * number inside an Arabic sentence runs left to right — needs the full bidi
718
- * algorithm, and guessing at it would be worse than leaving it alone.
799
+ * A run wholly right-to-left is simply reversed. A MIXED one — a full stop or
800
+ * a number inside an Arabic sentence — is put through the bidi algorithm's
801
+ * reordering (UAX #9 L2), which for the runs a line holds undoes itself: the
802
+ * painted order reordered is the reading order. Left alone, a note of
803
+ * freetext_no_appearance.pdf came back with every line that held a full stop
804
+ * reading back to front. The direction it is reordered in is the one most of
805
+ * its letters run in — the painted order says nothing about which comes first.
719
806
  */
720
807
  function logicalOrder(text) {
721
- return isRightToLeft(text) ? [...text].reverse().join("") : text;
808
+ if (isRightToLeft(text)) return [...text].reverse().join("");
809
+ if (!hasBidiCharacters(text)) return text;
810
+ const chars = [...text];
811
+ let rtl = 0;
812
+ let strong = 0;
813
+ for (const ch of chars) {
814
+ const type = bidiClass(ch.codePointAt(0) ?? 0);
815
+ if (type === "R" || type === "AL") rtl++;
816
+ if (type === "R" || type === "AL" || type === "L") strong++;
817
+ }
818
+ const { levels } = analyzeString(text, rtl * 2 >= strong ? "rtl" : "ltr");
819
+ return reorderVisual(levels).map((i) => chars[i] ?? "").join("");
722
820
  }
723
821
  /**
724
822
  * Whether a string is wholly right-to-left: at least one letter of an RTL
@@ -837,8 +935,8 @@ function readValue(lexer) {
837
935
  * The bytes are binary and may hold `EI` themselves, so the end is found by
838
936
  * MEASURING where the dictionary says how much there is — an unfiltered image
839
937
  * is exactly `ceil(W · BPC · components / 8) · H` bytes — and only searched for
840
- * where a filter makes the length unknowable. images_1bit_grayscale.pdf draws
841
- * two of them and both were skipped over.
938
+ * where a filter makes the length unknowable (see {@link filteredEnd}).
939
+ * images_1bit_grayscale.pdf draws two of them and both were skipped over.
842
940
  */
843
941
  function readInlineImage(lexer) {
844
942
  const dict = /* @__PURE__ */ new Map();
@@ -848,24 +946,86 @@ function readInlineImage(lexer) {
848
946
  if (tok.kind === "keyword" && tok.value === "ID") break;
849
947
  if (tok.kind === "name") dict.set(tok.value, readValue(lexer));
850
948
  }
851
- const start = lexer.pos + 1;
852
949
  const measured = inlineLength(dict);
950
+ const crlf = lexer.byteAt(lexer.pos) === 13 && lexer.byteAt(lexer.pos + 1) === 10;
951
+ const start = lexer.pos + (crlf && measured === void 0 ? 2 : 1);
853
952
  let end = measured !== void 0 ? start + measured : -1;
854
953
  if (end < 0 || end > lexer.length) {
855
- end = lexer.indexOfAscii("EI", start);
954
+ end = filteredEnd(lexer, start, dict);
856
955
  if (end < 0) {
857
956
  lexer.pos = lexer.length;
858
957
  return;
859
958
  }
860
959
  }
861
960
  const data = lexer.slice(start, Math.min(end, lexer.length));
862
- const ei = lexer.indexOfAscii("EI", end);
961
+ const ei = lexer.indexOfKeyword("EI", end);
863
962
  lexer.pos = ei < 0 ? lexer.length : ei + 2;
864
963
  return {
865
964
  dict,
866
965
  data
867
966
  };
868
967
  }
968
+ /**
969
+ * §8.9.7 — where a FILTERED inline image's bytes end, which the dictionary
970
+ * cannot say.
971
+ *
972
+ * Searched for as the first two bytes `EI`, the end fell inside the picture
973
+ * wherever its own bytes spelled them: a JPEG is dense with them, and
974
+ * bug1065245.pdf's three banners were each cut off a few hundred bytes in,
975
+ * would not decode, and were dropped — a blank sheet. So the data is read to
976
+ * where its OWN encoding ends: a JPEG at its end-of-image marker, hex text at
977
+ * `>`, base-85 at `~>`. Anything else ends at the first `EI` standing as a
978
+ * word of its own.
979
+ */
980
+ function filteredEnd(lexer, start, dict) {
981
+ const f = dict.get("F") ?? dict.get("Filter");
982
+ const first = Array.isArray(f) ? f[0] : f;
983
+ const filter = first instanceof PdfName ? first.value : "";
984
+ if (filter === "DCT" || filter === "DCTDecode") {
985
+ const eoi = jpegEnd(lexer, start);
986
+ if (eoi !== void 0) return eoi;
987
+ } else if (filter === "AHx" || filter === "ASCIIHexDecode") {
988
+ const gt = lexer.indexOfAscii(">", start);
989
+ if (gt >= 0) return gt + 1;
990
+ } else if (filter === "A85" || filter === "ASCII85Decode") {
991
+ const tilde = lexer.indexOfAscii("~>", start);
992
+ if (tilde >= 0) return tilde + 2;
993
+ }
994
+ return lexer.indexOfKeyword("EI", start);
995
+ }
996
+ /**
997
+ * Where a JPEG that starts at `start` ends: past its end-of-image marker,
998
+ * found by walking its segments — a length-prefixed marker segment is stepped
999
+ * over whole, and the coded data after a start-of-scan is read to the next
1000
+ * marker, where `FF` stands before anything but a stuffed `00` or a restart.
1001
+ */
1002
+ function jpegEnd(lexer, start) {
1003
+ if (lexer.byteAt(start) !== 255 || lexer.byteAt(start + 1) !== 216) return void 0;
1004
+ let i = start + 2;
1005
+ for (;;) {
1006
+ if (lexer.byteAt(i) !== 255) return void 0;
1007
+ let marker = lexer.byteAt(i + 1);
1008
+ while (marker === 255) marker = lexer.byteAt(++i + 1);
1009
+ if (marker < 0) return void 0;
1010
+ if (marker === 217) return i + 2;
1011
+ if (marker >= 208 && marker <= 215 || marker === 1) {
1012
+ i += 2;
1013
+ continue;
1014
+ }
1015
+ const hi = lexer.byteAt(i + 2);
1016
+ const lo = lexer.byteAt(i + 3);
1017
+ if (hi < 0 || lo < 0 || (hi << 8) + lo < 2) return void 0;
1018
+ i += 2 + (hi << 8) + lo;
1019
+ if (marker !== 218) continue;
1020
+ for (;;) {
1021
+ const b = lexer.byteAt(i);
1022
+ if (b < 0) return void 0;
1023
+ const next = lexer.byteAt(i + 1);
1024
+ if (b === 255 && next !== 0 && !(next >= 208 && next <= 215)) break;
1025
+ i++;
1026
+ }
1027
+ }
1028
+ }
869
1029
  /** How many bytes an UNFILTERED inline image's samples take, if that is known. */
870
1030
  function inlineLength(dict) {
871
1031
  if (dict.has("F") || dict.has("Filter")) return void 0;
@@ -15,6 +15,16 @@ export interface Display {
15
15
  x: number;
16
16
  y: number;
17
17
  };
18
+ /**
19
+ * §17.6.20 — present when the page is READ in a frame turned back from the
20
+ * sheet it is shown on: its words run DOWN the sheet, and they are read in
21
+ * the frame where they stand upright (see {@link textFrameOf}). `width` and
22
+ * `height` above are that frame's; these are the sheet's.
23
+ */
24
+ readonly sheet?: {
25
+ readonly width: number;
26
+ readonly height: number;
27
+ };
18
28
  }
19
29
  /**
20
30
  * The {@link Display} a page's shown box and `/Rotate` describe.
@@ -27,6 +37,32 @@ export interface Display {
27
37
  * @returns Its size, its turn, and the map onto it.
28
38
  */
29
39
  export declare function displayOf(page: PdfPage): Display;
40
+ /**
41
+ * The frame a page whose words run DOWN its sheet is read in: the sheet turned
42
+ * back a quarter, so the words stand upright in it and every line, paragraph
43
+ * and margin is found the way it is on any other page.
44
+ *
45
+ * A viewer turns such a page with its words, and so does the document it is
46
+ * read into (§17.6.20 `tbRl`; see {@link wordsTurnOf} for when a page's words
47
+ * run down it): hello_world_rotated.pdf sets "Hello world"
48
+ * upright in a portrait box and turns the page by `/Rotate 90`, and read on
49
+ * the shown sheet the words were set flat across it where every viewer shows
50
+ * them running down.
51
+ *
52
+ * @param shown The page's shown geometry.
53
+ * @returns The frame, which knows the sheet it was turned back from.
54
+ */
55
+ export declare function textFrameOf(shown: Display): Display;
56
+ /**
57
+ * How a page's words stand on its sheet: the quarter turn, counter-clockwise,
58
+ * whose words outweigh every other on it together, by their letters at their
59
+ * size — or 0 where none does. A label set on its side beside the text does
60
+ * not turn a page; a page of it does.
61
+ *
62
+ * @param runs The page's runs, placed on the shown sheet.
63
+ * @returns 0, 90, 180 or 270; 270 is a page whose words run DOWN it.
64
+ */
65
+ export declare function wordsTurnOf(runs: ReadonlyArray<TextRun>): 0 | 90 | 180 | 270;
30
66
  /**
31
67
  * The same runs, placed on the shown page.
32
68
  *
@@ -46,6 +46,71 @@ function displayOf(page) {
46
46
  };
47
47
  }
48
48
  /**
49
+ * The frame a page whose words run DOWN its sheet is read in: the sheet turned
50
+ * back a quarter, so the words stand upright in it and every line, paragraph
51
+ * and margin is found the way it is on any other page.
52
+ *
53
+ * A viewer turns such a page with its words, and so does the document it is
54
+ * read into (§17.6.20 `tbRl`; see {@link wordsTurnOf} for when a page's words
55
+ * run down it): hello_world_rotated.pdf sets "Hello world"
56
+ * upright in a portrait box and turns the page by `/Rotate 90`, and read on
57
+ * the shown sheet the words were set flat across it where every viewer shows
58
+ * them running down.
59
+ *
60
+ * @param shown The page's shown geometry.
61
+ * @returns The frame, which knows the sheet it was turned back from.
62
+ */
63
+ function textFrameOf(shown) {
64
+ return {
65
+ width: shown.height,
66
+ height: shown.width,
67
+ turnDeg: shown.turnDeg === 270 || shown.turnDeg === -90 ? 0 : shown.turnDeg + 90,
68
+ place: (x, y) => {
69
+ const p = shown.place(x, y);
70
+ return {
71
+ x: shown.height - p.y,
72
+ y: p.x
73
+ };
74
+ },
75
+ sheet: {
76
+ width: shown.width,
77
+ height: shown.height
78
+ }
79
+ };
80
+ }
81
+ /**
82
+ * How a page's words stand on its sheet: the quarter turn, counter-clockwise,
83
+ * whose words outweigh every other on it together, by their letters at their
84
+ * size — or 0 where none does. A label set on its side beside the text does
85
+ * not turn a page; a page of it does.
86
+ *
87
+ * @param runs The page's runs, placed on the shown sheet.
88
+ * @returns 0, 90, 180 or 270; 270 is a page whose words run DOWN it.
89
+ */
90
+ function wordsTurnOf(runs) {
91
+ const by = [
92
+ 0,
93
+ 0,
94
+ 0,
95
+ 0
96
+ ];
97
+ let all = 0;
98
+ for (const r of runs) {
99
+ const letters = r.text.trim().length;
100
+ if (letters === 0) continue;
101
+ const weight = letters * (r.fontSizePt || 10);
102
+ all += weight;
103
+ const angle = (Math.round(r.angleDeg ?? 0) % 360 + 360) % 360;
104
+ if (angle % 90 === 0) by[angle / 90] += weight;
105
+ }
106
+ const most = by.findIndex((w) => w * 2 > all);
107
+ return most > 0 ? [
108
+ 90,
109
+ 180,
110
+ 270
111
+ ][most - 1] : 0;
112
+ }
113
+ /**
49
114
  * The same runs, placed on the shown page.
50
115
  *
51
116
  * @param runs The runs as the content stream drew them.
@@ -199,12 +264,28 @@ function placeVectors(vectors, d) {
199
264
  ys.push(s.y);
200
265
  if (s.op === "cubic") xs.push(s.x1, s.x2), ys.push(s.y1, s.y2);
201
266
  }
267
+ const g = v.gradient;
268
+ const moved = g?.axis !== void 0 ? { gradient: {
269
+ ...g,
270
+ axis: ((a) => {
271
+ const p0 = d.place(a.x0, a.y0);
272
+ const p1 = d.place(a.x1, a.y1);
273
+ return {
274
+ x0: p0.x,
275
+ y0: p0.y,
276
+ x1: p1.x,
277
+ y1: p1.y
278
+ };
279
+ })(g.axis)
280
+ } } : {};
202
281
  if (xs.length === 0) return {
203
282
  ...v,
283
+ ...moved,
204
284
  segs
205
285
  };
206
286
  return {
207
287
  ...v,
288
+ ...moved,
208
289
  segs,
209
290
  minX: Math.min(...xs),
210
291
  minY: Math.min(...ys),
@@ -214,4 +295,4 @@ function placeVectors(vectors, d) {
214
295
  }).filter((v) => shows(d, v.minX, v.minY, v.maxX, v.maxY));
215
296
  }
216
297
  //#endregion
217
- export { displayOf, placeImages, placeRuns, placeVectors };
298
+ export { displayOf, placeImages, placeRuns, placeVectors, textFrameOf, wordsTurnOf };
@@ -183,7 +183,7 @@ var PdfFile = class PdfFile {
183
183
  }
184
184
  return;
185
185
  }
186
- const media = mediaBox ?? DEFAULT_MEDIA_BOX;
186
+ const media = mediaBox !== void 0 && hasArea(mediaBox) ? mediaBox : DEFAULT_MEDIA_BOX;
187
187
  out.push({
188
188
  dict: node,
189
189
  mediaBox: media,
@@ -556,6 +556,10 @@ function lastIndexOfAscii(buf, needle) {
556
556
  * bug1844576.pdf is 612×792 of paper cropped to 181×53.75, and read as its
557
557
  * media box the form on it arrived a fifth of the way down a letter page.
558
558
  */
559
+ /** Whether a rectangle bounds anything: a point or a line is no page. */
560
+ function hasArea([x0, y0, x1, y1]) {
561
+ return Math.abs(x1 - x0) > 1 && Math.abs(y1 - y0) > 1;
562
+ }
559
563
  function shownBox(media, crop) {
560
564
  if (!crop) return media;
561
565
  const [mx0, my0, mx1, my1] = media;
@@ -29,6 +29,16 @@ import { FontRegistry } from '../core/font/index.js';
29
29
  * @returns Name → a one-face registry holding that program.
30
30
  */
31
31
  export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray<PdfPage>, losses?: Array<Loss>): Map<string, FontRegistry>;
32
+ /**
33
+ * Every `/Font` dictionary the pages draw with, each once: the page's own
34
+ * resources and those of every form it paints (§8.8 — a drawing keeps most of
35
+ * its lettering inside them).
36
+ *
37
+ * @param file The owning file.
38
+ * @param pages The pages whose fonts are wanted.
39
+ * @param visit Called once per font dictionary.
40
+ */
41
+ export declare function eachPageFont(file: PdfFile, pages: ReadonlyArray<PdfPage>, visit: (fontDict: PdfDict) => void): void;
32
42
  /**
33
43
  * The name a run set in `fontDict` will ask for: its `/BaseFont` without the
34
44
  * six-capital subset prefix (§9.6.4), lowercased.
@@ -49,3 +59,18 @@ export declare function embeddedFontName(file: PdfFile, fontDict: PdfDict): stri
49
59
  * @param fontDict The font dictionary.
50
60
  */
51
61
  export declare function hasLiftableProgram(file: PdfFile, fontDict: PdfDict): boolean;
62
+ /**
63
+ * The style a font's embedded program states for itself — the `head` table's
64
+ * macStyle, bit 0 bold and bit 1 italic — where the file carries a TrueType
65
+ * program to read it from, and the weight a Type 1 program names (see
66
+ * {@link typeOneStyle}).
67
+ *
68
+ * @param file The document.
69
+ * @param fontDict The font dictionary.
70
+ * @returns The program's own style, or `undefined` where there is no program
71
+ * or it has no header.
72
+ */
73
+ export declare function programStyle(file: PdfFile, fontDict: PdfDict): {
74
+ bold: boolean;
75
+ italic: boolean;
76
+ } | undefined;
@@ -32,11 +32,7 @@ var MAX_FORM_DEPTH = 8;
32
32
  */
33
33
  function collectEmbeddedFonts(file, pages, losses) {
34
34
  const out = /* @__PURE__ */ new Map();
35
- const seen = /* @__PURE__ */ new Set();
36
- const visiting = /* @__PURE__ */ new Set();
37
- const addFont = (fontDict) => {
38
- if (seen.has(fontDict)) return;
39
- seen.add(fontDict);
35
+ eachPageFont(file, pages, (fontDict) => {
40
36
  const name = embeddedFontName(file, fontDict);
41
37
  if (name === void 0 || out.has(name)) return;
42
38
  const program = fontProgram(file, fontDict);
@@ -53,13 +49,29 @@ function collectEmbeddedFonts(file, pages, losses) {
53
49
  }
54
50
  out.set(name, FontRegistry.fromBytes({ regular: program }));
55
51
  } catch {}
56
- };
52
+ });
53
+ return out;
54
+ }
55
+ /**
56
+ * Every `/Font` dictionary the pages draw with, each once: the page's own
57
+ * resources and those of every form it paints (§8.8 — a drawing keeps most of
58
+ * its lettering inside them).
59
+ *
60
+ * @param file The owning file.
61
+ * @param pages The pages whose fonts are wanted.
62
+ * @param visit Called once per font dictionary.
63
+ */
64
+ function eachPageFont(file, pages, visit) {
65
+ const seen = /* @__PURE__ */ new Set();
66
+ const visiting = /* @__PURE__ */ new Set();
57
67
  const walk = (resources, depth) => {
58
68
  if (!resources) return;
59
69
  const fonts = file.get(resources, "Font");
60
70
  if (fonts instanceof Map) for (const value of fonts.values()) {
61
71
  const dict = file.resolve(value);
62
- if (dict instanceof Map) addFont(dict);
72
+ if (!(dict instanceof Map) || seen.has(dict)) continue;
73
+ seen.add(dict);
74
+ visit(dict);
63
75
  }
64
76
  if (depth >= MAX_FORM_DEPTH) return;
65
77
  const xobjects = file.get(resources, "XObject");
@@ -76,7 +88,6 @@ function collectEmbeddedFonts(file, pages, losses) {
76
88
  }
77
89
  };
78
90
  for (const page of pages) walk(page.resources, 0);
79
- return out;
80
91
  }
81
92
  /**
82
93
  * The name a run set in `fontDict` will ask for: its `/BaseFont` without the
@@ -105,6 +116,64 @@ function embeddedFontName(file, fontDict) {
105
116
  function hasLiftableProgram(file, fontDict) {
106
117
  return fontProgram(file, fontDict) !== void 0;
107
118
  }
119
+ /**
120
+ * The style a font's embedded program states for itself — the `head` table's
121
+ * macStyle, bit 0 bold and bit 1 italic — where the file carries a TrueType
122
+ * program to read it from, and the weight a Type 1 program names (see
123
+ * {@link typeOneStyle}).
124
+ *
125
+ * @param file The document.
126
+ * @param fontDict The font dictionary.
127
+ * @returns The program's own style, or `undefined` where there is no program
128
+ * or it has no header.
129
+ */
130
+ function programStyle(file, fontDict) {
131
+ const program = fontProgram(file, fontDict);
132
+ if (!program) return typeOneStyle(file, fontDict);
133
+ if (program.length < 12) return void 0;
134
+ const view = new DataView(program.buffer, program.byteOffset, program.byteLength);
135
+ const tables = view.getUint16(4);
136
+ for (let i = 0; i < tables; i++) {
137
+ const at = 12 + i * 16;
138
+ if (at + 16 > program.length) return void 0;
139
+ if (String.fromCharCode(...program.subarray(at, at + 4)) !== "head") continue;
140
+ const offset = view.getUint32(at + 8);
141
+ if (offset + 46 > program.length) return void 0;
142
+ const macStyle = view.getUint16(offset + 44);
143
+ return {
144
+ bold: (macStyle & 1) !== 0,
145
+ italic: (macStyle & 2) !== 0
146
+ };
147
+ }
148
+ }
149
+ /**
150
+ * §9.9 `/FontFile` — the weight a Type 1 program names in its `FontInfo`
151
+ * (`/Weight (Bold)`), read off the cleartext that opens it. pdfTeX states no
152
+ * weight in the descriptor, and names URW's bold Times "NimbusRomNo9L-Medi",
153
+ * which no rule of names reads as bold: comments.pdf's headings and captions
154
+ * came back in the body's weight. Its program says "Bold"; Computer Modern's
155
+ * say "Medium", which is their book weight.
156
+ *
157
+ * @param file The document.
158
+ * @param fontDict The font dictionary.
159
+ * @returns The style, or `undefined` where no program names a weight.
160
+ */
161
+ function typeOneStyle(file, fontDict) {
162
+ const owner = descendant(file, fontDict) ?? fontDict;
163
+ const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
164
+ if (!(descriptor instanceof Map)) return void 0;
165
+ const program = file.resolve(descriptor.get("FontFile") ?? PDF_NULL);
166
+ if (!(program instanceof PdfStream)) return void 0;
167
+ const bytes = file.streamData(program);
168
+ const clear = new TextDecoder("latin1").decode(bytes.subarray(0, TYPE_ONE_HEAD));
169
+ const weight = /\/Weight\s*\(([^)]*)\)/u.exec(clear)?.[1];
170
+ return weight === void 0 ? void 0 : {
171
+ bold: /bold|black|heavy|semib|demi/iu.test(weight),
172
+ italic: false
173
+ };
174
+ }
175
+ /** How far into a Type 1 program its `FontInfo` stands, at the most. */
176
+ var TYPE_ONE_HEAD = 8192;
108
177
  /** §9.9 `/FontFile2` — the TrueType program, off the font or its descendant. */
109
178
  function fontProgram(file, fontDict) {
110
179
  const owner = descendant(file, fontDict) ?? fontDict;
@@ -123,4 +192,4 @@ function descendant(file, fontDict) {
123
192
  return first instanceof Map ? first : void 0;
124
193
  }
125
194
  //#endregion
126
- export { collectEmbeddedFonts, embeddedFontName, hasLiftableProgram };
195
+ export { collectEmbeddedFonts, eachPageFont, embeddedFontName, hasLiftableProgram, programStyle };
@@ -1,5 +1,13 @@
1
1
  /** One of the base encodings this reader has a table for (Annex D.2). */
2
2
  export type BaseEncodingName = 'StandardEncoding' | 'WinAnsiEncoding' | 'MacRomanEncoding';
3
+ /**
4
+ * The glyph a code of WinAnsiEncoding's Latin-1 half selects, by name (see
5
+ * `WIN_ANSI_LATIN`).
6
+ *
7
+ * @param code A one-byte code.
8
+ * @returns The Annex D.2 glyph name, or `undefined` outside 0xA0–0xFF.
9
+ */
10
+ export declare function winAnsiLatinName(code: number): string | undefined;
3
11
  /**
4
12
  * The glyph names of a base encoding, or `undefined` for a name this has no
5
13
  * table for — `/MacExpertEncoding`, whose repertory no reader here can set.