reamkit 1.26.0 → 1.28.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 (85) hide show
  1. package/README.md +14 -8
  2. package/dist/esm/core/converter/ream.d.ts +0 -9
  3. package/dist/esm/core/converter/ream.js +0 -1
  4. package/dist/esm/core/document-model/types.d.ts +5 -3
  5. package/dist/esm/core/font/index.d.ts +1 -0
  6. package/dist/esm/core/font/ligatures.d.ts +17 -0
  7. package/dist/esm/core/font/ligatures.js +49 -0
  8. package/dist/esm/core/font/ttf-parser.d.ts +2 -1
  9. package/dist/esm/core/font/ttf-parser.js +11 -2
  10. package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
  11. package/dist/esm/core/fonts/remote-fonts.js +47 -2
  12. package/dist/esm/core/fonts/scripts.js +10 -5
  13. package/dist/esm/excel/header-footer.js +53 -8
  14. package/dist/esm/layout/styled-layout.js +53 -3
  15. package/dist/esm/pdf/cid-font.js +35 -8
  16. package/dist/esm/pdf-reader/annot-draw.d.ts +65 -0
  17. package/dist/esm/pdf-reader/annot-draw.js +487 -0
  18. package/dist/esm/pdf-reader/annots.d.ts +0 -12
  19. package/dist/esm/pdf-reader/annots.js +30 -4
  20. package/dist/esm/pdf-reader/ccitt.d.ts +20 -3
  21. package/dist/esm/pdf-reader/ccitt.js +102 -6
  22. package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
  23. package/dist/esm/pdf-reader/cff-outline.js +1122 -0
  24. package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
  25. package/dist/esm/pdf-reader/cie-color.js +112 -0
  26. package/dist/esm/pdf-reader/cmap.js +5 -2
  27. package/dist/esm/pdf-reader/content.d.ts +123 -3
  28. package/dist/esm/pdf-reader/content.js +232 -54
  29. package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
  30. package/dist/esm/pdf-reader/dingbats.js +1033 -0
  31. package/dist/esm/pdf-reader/display.d.ts +1 -1
  32. package/dist/esm/pdf-reader/display.js +61 -6
  33. package/dist/esm/pdf-reader/document.d.ts +6 -0
  34. package/dist/esm/pdf-reader/document.js +55 -10
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +23 -3
  36. package/dist/esm/pdf-reader/embedded-fonts.js +36 -4
  37. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  38. package/dist/esm/pdf-reader/encodings.js +110 -0
  39. package/dist/esm/pdf-reader/flow-build.d.ts +19 -5
  40. package/dist/esm/pdf-reader/flow-build.js +111 -11
  41. package/dist/esm/pdf-reader/font.js +403 -21
  42. package/dist/esm/pdf-reader/function.d.ts +16 -0
  43. package/dist/esm/pdf-reader/function.js +414 -0
  44. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  45. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  46. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  47. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  48. package/dist/esm/pdf-reader/icc.js +210 -0
  49. package/dist/esm/pdf-reader/image-decode.d.ts +9 -4
  50. package/dist/esm/pdf-reader/image-decode.js +274 -72
  51. package/dist/esm/pdf-reader/images.d.ts +18 -0
  52. package/dist/esm/pdf-reader/images.js +154 -11
  53. package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
  54. package/dist/esm/pdf-reader/jbig2.js +126 -32
  55. package/dist/esm/pdf-reader/layout.d.ts +32 -0
  56. package/dist/esm/pdf-reader/layout.js +1316 -64
  57. package/dist/esm/pdf-reader/lexer.d.ts +2 -0
  58. package/dist/esm/pdf-reader/lexer.js +4 -0
  59. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  60. package/dist/esm/pdf-reader/math-rows.js +198 -0
  61. package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
  62. package/dist/esm/pdf-reader/optional-content.js +93 -0
  63. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  64. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  65. package/dist/esm/pdf-reader/reader.d.ts +6 -6
  66. package/dist/esm/pdf-reader/reader.js +102 -16
  67. package/dist/esm/pdf-reader/shading.d.ts +139 -8
  68. package/dist/esm/pdf-reader/shading.js +309 -39
  69. package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
  70. package/dist/esm/pdf-reader/standard-metrics.js +18 -0
  71. package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
  72. package/dist/esm/pdf-reader/standard-widths.js +62 -0
  73. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  74. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  75. package/dist/esm/pdf-reader/tagged.js +204 -32
  76. package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
  77. package/dist/esm/pdf-reader/text-rules.js +185 -0
  78. package/dist/esm/pdf-reader/text.js +157 -4
  79. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  80. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  81. package/dist/esm/pdf-reader/vector.d.ts +5 -0
  82. package/dist/esm/pdf-reader/vector.js +79 -27
  83. package/dist/esm/word/document-parser.js +4 -1
  84. package/dist/esm/word/docx-writer.js +74 -11
  85. package/package.json +1 -1
@@ -1,35 +1,66 @@
1
1
  import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
2
2
  import { FEATURES } from "../core/ir/features.js";
3
+ import { buildAlphaMap, buildColorSpaceMap, buildShadingMap, gradientShading, shadingTypeOf } from "./shading.js";
3
4
  import { IDENTITY, interpretContent, multiply } from "./content.js";
4
5
  import { collectPageAppearances } from "./annots.js";
6
+ import { hiddenProperties, hiddenXObject } from "./optional-content.js";
5
7
  import { buildFonts } from "./text.js";
6
- import { buildAlphaMap, buildColorSpaceMap, buildShadingMap } from "./shading.js";
7
8
  //#region src/pdf-reader/vector.ts
8
- /**
9
- * Every vector the page paints, its FORM XOBJECTS included (§8.8).
10
- *
11
- * A `Do` of a form is a call: its content stream draws in the caller's space
12
- * through the form's own `/Matrix`. Interpreting the page stream alone reads
13
- * only what the page drew directly, and a document that puts its artwork in
14
- * forms — as every CAD and drawing producer does — comes back with none of it.
15
- * 22060_A1_01_Plans.pdf holds eleven, and its floor plans were simply absent.
16
- *
17
- * The same walk `collectPageImages` already makes, with the same depth and
18
- * cycle guards, collecting paths instead of pictures.
19
- */
20
- function paintedVectors(file, page, shadings, alphas, spaces) {
9
+ function paintedVectors(file, page) {
21
10
  const out = [];
11
+ let bareShadings = 0;
22
12
  const visiting = /* @__PURE__ */ new Set();
13
+ const stateCache = /* @__PURE__ */ new Map();
14
+ const mapsOf = (resources) => {
15
+ const had = stateCache.get(resources);
16
+ if (had) return had;
17
+ const made = {
18
+ shadings: buildShadingMap(file, resources),
19
+ alphas: buildAlphaMap(file, resources),
20
+ spaces: buildColorSpaceMap(file, resources)
21
+ };
22
+ stateCache.set(resources, made);
23
+ return made;
24
+ };
23
25
  const walk = (resources, content, baseCtm, depth, prefix) => {
24
26
  if (out.length >= MAX_VECTORS) return;
25
27
  const xobjects = resources ? file.get(resources, "XObject") : PDF_NULL;
26
28
  const xobjDict = xobjects instanceof Map ? xobjects : void 0;
27
- const result = interpretContent(content, buildFonts(file, resources), baseCtm, shadings, alphas, spaces);
29
+ const maps = mapsOf(resources);
30
+ const result = interpretContent(content, buildFonts(file, resources), baseCtm, maps.shadings, maps.alphas, maps.spaces, hiddenProperties(file, resources));
31
+ for (const paint of result.shadings) {
32
+ if (out.length >= MAX_VECTORS) break;
33
+ const dict = resources ? file.get(resources, "Shading") : PDF_NULL;
34
+ const found = dict instanceof Map ? file.resolve(dict.get(paint.name) ?? PDF_NULL) : PDF_NULL;
35
+ const sh = found instanceof PdfStream ? found.dict : found instanceof Map ? found : void 0;
36
+ if (sh && shadingTypeOf(file, sh) === 1) continue;
37
+ const gradient = sh ? gradientShading(file, sh) : void 0;
38
+ if (paint.masked) {
39
+ bareShadings++;
40
+ continue;
41
+ }
42
+ if (!sh || !gradient || !paint.clip) {
43
+ bareShadings++;
44
+ continue;
45
+ }
46
+ out.push({
47
+ order: paint.order,
48
+ segs: paint.clip.segs,
49
+ gradient,
50
+ ...paint.alpha !== void 0 ? { alpha: paint.alpha } : {},
51
+ ...paint.darkens ? { darkens: true } : {},
52
+ orderKey: [...prefix, paint.order]
53
+ });
54
+ }
28
55
  const events = [
29
56
  ...result.vectors.map((vector) => ({
30
57
  order: vector.order,
31
58
  vector
32
59
  })),
60
+ ...result.outlines.map((vector) => ({
61
+ order: vector.order,
62
+ vector
63
+ })),
33
64
  ...result.images.map((xobject) => ({
34
65
  order: xobject.order,
35
66
  xobject
@@ -60,6 +91,7 @@ function paintedVectors(file, page, shadings, alphas, spaces) {
60
91
  if (depth >= MAX_FORM_DEPTH) continue;
61
92
  const stream = xobjDict ? file.resolve(xobjDict.get(placement.name) ?? PDF_NULL) : PDF_NULL;
62
93
  if (!(stream instanceof PdfStream) || visiting.has(stream)) continue;
94
+ if (hiddenXObject(file, stream)) continue;
63
95
  const subtype = file.get(stream.dict, "Subtype");
64
96
  if (!(subtype instanceof PdfName) || subtype.value !== "Form") continue;
65
97
  visiting.add(stream);
@@ -72,7 +104,10 @@ function paintedVectors(file, page, shadings, alphas, spaces) {
72
104
  collectPageAppearances(file, page).forEach((appearance, index) => {
73
105
  walk(appearance.resources ?? page.resources, file.streamData(appearance.stream), appearance.ctm, 1, [Number.MAX_SAFE_INTEGER, index]);
74
106
  });
75
- return out;
107
+ return {
108
+ placements: out,
109
+ bareShadings
110
+ };
76
111
  }
77
112
  /** §8.10.2 `/Matrix` — the form's own space, composed onto the placement CTM. */
78
113
  function formMatrix(file, dict) {
@@ -116,14 +151,24 @@ var MAX_FORM_DEPTH = 12;
116
151
  * clutter. Clips and the bare `sh` operator are not captured (a documented loss).
117
152
  */
118
153
  function collectPageVectors(file, page, occupied = []) {
119
- const [px0, py0, px1, py1] = page.mediaBox;
154
+ const [px0, py0, px1, py1] = page.cropBox;
120
155
  const pageArea = Math.max(1, Math.abs((px1 - px0) * (py1 - py0)));
121
- const shadings = buildShadingMap(file, page);
122
- const alphas = buildAlphaMap(file, page);
123
- const spaces = buildColorSpaceMap(file, page);
124
156
  const out = [];
125
157
  const painted = [...occupied];
126
- const raws = paintedVectors(file, page, shadings, alphas, spaces);
158
+ const lifted = paintedVectors(file, page);
159
+ const raws = lifted.placements;
160
+ const losses = [];
161
+ const bareShadings = lifted.bareShadings;
162
+ const asked = /* @__PURE__ */ new Set();
163
+ for (const raw of raws) {
164
+ if (raw.masked === true) asked.add("PDF soft mask (/SMask in the graphics state) is not applied; the shape is drawn at full opacity throughout");
165
+ if (raw.blend !== void 0) asked.add(`PDF blend mode /${raw.blend} is not performed; the shape is drawn over what it was to blend with`);
166
+ }
167
+ for (const detail of asked) losses.push({
168
+ severity: "degraded",
169
+ feature: FEATURES.images,
170
+ detail
171
+ });
127
172
  for (const raw of raws) {
128
173
  if (out.length >= MAX_VECTORS) break;
129
174
  const v = clipped(raw);
@@ -139,7 +184,7 @@ function collectPageVectors(file, page, occupied = []) {
139
184
  const short = Math.min(w, h);
140
185
  const isBox = short >= MIN_SIDE && area >= MIN_AREA;
141
186
  const isRule = long >= MIN_RULE_LEN && short > 0;
142
- const filled = (v.gradient !== void 0 || solidFill) && (isBox || isRule) && area <= .85 * pageArea;
187
+ const filled = (v.gradient !== void 0 || solidFill) && (isBox || isRule || v.glyph === true);
143
188
  const stroked = v.strokeHex !== void 0 && (v.strokeHex !== "FFFFFF" || painted.some((box) => overlaps(box, b))) && Math.max(w, h) >= MIN_STROKE_LEN && area <= .85 * pageArea;
144
189
  if (!filled && !stroked) continue;
145
190
  painted.push(b);
@@ -148,6 +193,7 @@ function collectPageVectors(file, page, occupied = []) {
148
193
  segs: v.segs,
149
194
  ...filled ? v.gradient ? { gradient: v.gradient } : v.fillHex !== void 0 ? { fillHex: v.fillHex } : {} : {},
150
195
  ...filled && v.alpha !== void 0 ? { alpha: v.alpha } : {},
196
+ ...filled && v.darkens === true ? { darkens: true } : {},
151
197
  ...stroked ? {
152
198
  strokeHex: v.strokeHex,
153
199
  ...v.lineWidth !== void 0 ? { lineWidth: v.lineWidth } : {}
@@ -156,13 +202,19 @@ function collectPageVectors(file, page, occupied = []) {
156
202
  ...v.mcid !== void 0 ? { mcid: v.mcid } : {}
157
203
  });
158
204
  }
205
+ if (out.length >= MAX_VECTORS || raws.length >= MAX_VECTORS) losses.push({
206
+ severity: "dropped",
207
+ feature: FEATURES.shapes,
208
+ detail: `page carries more than ${String(MAX_VECTORS)} painted paths; the rest were not read`
209
+ });
210
+ if (bareShadings > 0) losses.push({
211
+ severity: "dropped",
212
+ feature: FEATURES.images,
213
+ detail: `${String(bareShadings)} bare-shading (sh) region${bareShadings === 1 ? "" : "s"} painted the clip rather than a path, and ${bareShadings === 1 ? "was" : "were"} not reconstructed`
214
+ });
159
215
  return {
160
216
  vectors: out,
161
- losses: out.length >= MAX_VECTORS || raws.length >= MAX_VECTORS ? [{
162
- severity: "dropped",
163
- feature: FEATURES.shapes,
164
- detail: `page carries more than ${String(MAX_VECTORS)} painted paths; the rest were not read`
165
- }] : []
217
+ losses
166
218
  };
167
219
  }
168
220
  /** Whether two boxes share any area at all. */
@@ -262,7 +262,10 @@ function parseSectPrNode(sectPr) {
262
262
  const type = poAttr(child, "type") ?? "default";
263
263
  const pitch = poIntAttr(child, "linePitch");
264
264
  if ((type === "lines" || type === "linesAndChars") && pitch !== void 0 && pitch > 0) gridLinePitchPt = twipsToPt(pitch);
265
- } else if (poIs(child, "w:type")) sectionStart = poAttr(child, "val") === "continuous" ? "continuous" : "nextPage";
265
+ } else if (poIs(child, "w:type")) {
266
+ const val = poAttr(child, "val");
267
+ sectionStart = val === "continuous" || val === "oddPage" || val === "evenPage" ? val : "nextPage";
268
+ }
266
269
  return {
267
270
  pageSize,
268
271
  ...margins ? { margins } : {},
@@ -616,8 +616,8 @@ function geomXml(g) {
616
616
  case "close": return "<a:close/>";
617
617
  }
618
618
  }).join("");
619
- const w = c1(g.custom.pathWidth);
620
- const h = c1(g.custom.pathHeight);
619
+ const w = Math.max(1, c1(g.custom.pathWidth));
620
+ const h = Math.max(1, c1(g.custom.pathHeight));
621
621
  return `<a:custGeom><a:avLst/><a:gdLst/><a:rect l="0" t="0" r="${w}" b="${h}"/><a:pathLst><a:path w="${w}" h="${h}">${cmds}</a:path></a:pathLst></a:custGeom>`;
622
622
  }
623
623
  const gds = g.adjust && g.adjust.size > 0 ? [...g.adjust].map(([n, v]) => `<a:gd name="${escapeAttr(n)}" fmla="val ${v}"/>`).join("") : "";
@@ -826,19 +826,27 @@ function runXml(run, state, scope) {
826
826
  }
827
827
  function rPrXml(r) {
828
828
  const out = [];
829
+ const fonts = rFontsXml(r.fontFamily);
830
+ if (fonts) out.push(fonts);
829
831
  if (r.bold !== DEFAULT_RUN.bold) out.push(toggle("w:b", r.bold));
830
832
  if (r.italic !== DEFAULT_RUN.italic) out.push(toggle("w:i", r.italic));
831
833
  if (r.strike !== DEFAULT_RUN.strike) out.push(toggle("w:strike", r.strike));
832
- if (r.underline !== DEFAULT_RUN.underline) out.push(`<w:u w:val="${r.underline}"/>`);
833
- const fonts = rFontsXml(r.fontFamily);
834
- if (fonts) out.push(fonts);
835
- if (r.fontSizePt !== DEFAULT_RUN.fontSizePt) out.push(`<w:sz w:val="${Math.round(r.fontSizePt * 2)}"/>`);
836
834
  if (r.colorHex !== DEFAULT_RUN.colorHex) out.push(`<w:color w:val="${r.colorHex}"/>`);
835
+ if (r.fontSizePt !== DEFAULT_RUN.fontSizePt) out.push(`<w:sz w:val="${Math.round(r.fontSizePt * 2)}"/>`);
836
+ if (r.underline !== DEFAULT_RUN.underline) out.push(underlineXml(r));
837
+ if (r.shadingColorHex !== void 0) out.push(runShdXml(r.shadingColorHex));
837
838
  if (r.verticalAlign !== DEFAULT_RUN.verticalAlign) out.push(`<w:vertAlign w:val="${r.verticalAlign}"/>`);
838
839
  if (r.rtl !== DEFAULT_RUN.rtl) out.push(toggle("w:rtl", r.rtl));
839
840
  if (r.lang !== void 0) out.push(`<w:lang w:val="${escapeAttr(r.lang)}"/>`);
840
841
  return out.length > 0 ? `<w:rPr>${out.join("")}</w:rPr>` : "";
841
842
  }
843
+ function underlineXml(r) {
844
+ const color = r.underlineColorHex !== void 0 ? ` w:color="${r.underlineColorHex}"` : "";
845
+ return `<w:u w:val="${r.underline}"${color}/>`;
846
+ }
847
+ function runShdXml(fill) {
848
+ return `<w:shd w:val="clear" w:color="auto" w:fill="${fill}"/>`;
849
+ }
842
850
  function pPrBody(p) {
843
851
  const out = [];
844
852
  if (p.numbering) out.push(`<w:numPr><w:ilvl w:val="${p.numbering.ilvl}"/><w:numId w:val="${escapeAttr(p.numbering.numId)}"/></w:numPr>`);
@@ -942,14 +950,18 @@ function rawParaPrXml(p) {
942
950
  }
943
951
  function rawRunPrXml(r) {
944
952
  const out = [];
953
+ const fonts = r.fontFamily ? rawRFontsXml(r.fontFamily) : "";
954
+ if (fonts) out.push(fonts);
945
955
  if (r.bold !== void 0) out.push(toggle("w:b", r.bold));
946
956
  if (r.italic !== void 0) out.push(toggle("w:i", r.italic));
947
957
  if (r.strike !== void 0) out.push(toggle("w:strike", r.strike));
948
- if (r.underline !== void 0) out.push(`<w:u w:val="${r.underline}"/>`);
949
- const fonts = r.fontFamily ? rawRFontsXml(r.fontFamily) : "";
950
- if (fonts) out.push(fonts);
951
- if (r.fontSizePt !== void 0) out.push(`<w:sz w:val="${Math.round(r.fontSizePt * 2)}"/>`);
952
958
  if (r.colorHex !== void 0) out.push(`<w:color w:val="${r.colorHex}"/>`);
959
+ if (r.fontSizePt !== void 0) out.push(`<w:sz w:val="${Math.round(r.fontSizePt * 2)}"/>`);
960
+ if (r.underline !== void 0) out.push(underlineXml({
961
+ ...r,
962
+ underline: r.underline
963
+ }));
964
+ if (r.shadingColorHex !== void 0) out.push(runShdXml(r.shadingColorHex));
953
965
  if (r.verticalAlign !== void 0) out.push(`<w:vertAlign w:val="${r.verticalAlign}"/>`);
954
966
  return out.length > 0 ? `<w:rPr>${out.join("")}</w:rPr>` : "";
955
967
  }
@@ -964,6 +976,7 @@ function sectPrXml(s, hf) {
964
976
  const parts = [];
965
977
  for (const h of hf.headers) parts.push(`<w:headerReference w:type="${h.type}" r:id="${h.relId}"/>`);
966
978
  for (const f of hf.footers) parts.push(`<w:footerReference w:type="${f.type}" r:id="${f.relId}"/>`);
979
+ if (s.sectionStart !== void 0 && s.sectionStart !== "nextPage") parts.push(`<w:type w:val="${s.sectionStart}"/>`);
967
980
  if (s.pageSize) {
968
981
  const orient = s.pageSize.orientation === "landscape" ? " w:orient=\"landscape\"" : "";
969
982
  parts.push(`<w:pgSz w:w="${twips(s.pageSize.width)}" w:h="${twips(s.pageSize.height)}"${orient}/>`);
@@ -987,8 +1000,58 @@ function colsXml(cols) {
987
1000
  return `<w:cols w:num="${cols.count}" w:space="${twips(cols.spacePt)}"/>`;
988
1001
  }
989
1002
  var twips = (pt) => Math.round(pt * 20);
1003
+ /**
1004
+ * XML 1.0 §2.2 — the characters a document may contain at all.
1005
+ *
1006
+ * Everything below U+0020 except tab, newline and return is FORBIDDEN, and no
1007
+ * escape exists for them: `&#2;` is as ill-formed as the byte. A package with
1008
+ * one in it is not a document — LibreOffice says "source file could not be
1009
+ * loaded" and Word says nothing useful either.
1010
+ *
1011
+ * They reach here from the PDF reader. A subset font that states neither a
1012
+ * `/ToUnicode` nor an `/Encoding /Differences` says nothing about what its
1013
+ * codes mean, and the codes of a subset start at 1, 2, 3 — so the last-resort
1014
+ * Latin-1 reading turns a page of text into control characters. Reading it
1015
+ * better is `font.ts`'s business; what this must guarantee is that nothing the
1016
+ * reader believes can produce a package that will not open.
1017
+ *
1018
+ * A lone surrogate is the same case: a half of a pair is not a character.
1019
+ */
1020
+ /**
1021
+ * XML 1.0 §2.2 — whether a code point may appear in a document at all.
1022
+ *
1023
+ * Everything below U+0020 except tab, newline and return is forbidden, and no
1024
+ * escape exists for them: `&#2;` is as ill-formed as the byte itself. So is a
1025
+ * lone surrogate — half a pair is not a character — and so are U+FFFE/U+FFFF.
1026
+ */
1027
+ function xmlAllows(cp) {
1028
+ if (cp === 9 || cp === 10 || cp === 13) return true;
1029
+ if (cp < 32) return false;
1030
+ if (cp >= 55296 && cp <= 57343) return false;
1031
+ if (cp === 65534 || cp === 65535) return false;
1032
+ return cp <= 1114111;
1033
+ }
1034
+ /**
1035
+ * Escape text for XML, and drop what XML cannot carry.
1036
+ *
1037
+ * The dropping is not fussiness: a package with one control character in it is
1038
+ * not a document. LibreOffice answers "source file could not be loaded" and
1039
+ * Word refuses it too, so the whole conversion is lost over one byte.
1040
+ *
1041
+ * They reach here from the PDF reader. A subset font that states neither a
1042
+ * `/ToUnicode` nor an `/Encoding /Differences` says nothing about what its
1043
+ * codes mean, and a subset's codes start at 1, 2, 3 — so the last-resort
1044
+ * Latin-1 reading turns a page of prose into control characters. Reading such
1045
+ * a font better is `font.ts`'s business; what this guarantees is that nothing
1046
+ * the reader believes can produce a package that will not open.
1047
+ */
990
1048
  function escapeXml(s) {
991
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1049
+ let out = "";
1050
+ for (const ch of s) {
1051
+ if (!xmlAllows(ch.codePointAt(0) ?? 0)) continue;
1052
+ out += ch === "&" ? "&amp;" : ch === "<" ? "&lt;" : ch === ">" ? "&gt;" : ch;
1053
+ }
1054
+ return out;
992
1055
  }
993
1056
  function escapeAttr(s) {
994
1057
  return escapeXml(s).replace(/"/g, "&quot;");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.26.0",
3
+ "version": "1.28.0",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",