@reportwright/pdf 0.1.0-beta.0 → 0.1.0-beta.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 +41 -8
- package/dist/index.js +485 -117
- package/examples/factur-x.mjs +37 -0
- package/examples/harfbuzz-shaper.mjs +27 -0
- package/index.d.ts +6 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ writer to be ready. `pdf.end()` ends a Node stream and closes a `WritableStream`
|
|
|
38
38
|
| option | meaning |
|
|
39
39
|
|---|---|
|
|
40
40
|
| `title`, `author`, `subject`, `keywords` | document info, mirrored in the XMP metadata (every value escaped) |
|
|
41
|
+
| `metadata` | `true` (default): an XMP metadata stream mirrors the info. `false`: the Info dictionary only, no XMP (a one-page invoice goes from 2.5 KB to 1.6 KB). Refused with `pdfa` or `tagged`: PDF/A and PDF/UA require the XMP |
|
|
41
42
|
| `creationDate` | a `Date` (default: now). With the same input and date the output is byte-for-byte identical |
|
|
42
43
|
| `compress` | `true` (default): content, fonts, object streams and the xref stream are Flate-compressed |
|
|
43
44
|
| `pdfa` | `true`: PDF/A-2b (sRGB output intent bundled, XMP identification). Fonts must be embedded. `{ part, conformance, outputIntent }`: a level (see PDF/A levels) and/or your RGB or CMYK output profile instead of sRGB (a CMYK intent allows CMYK colour and refuses RGB) |
|
|
@@ -61,6 +62,9 @@ writer to be ready. `pdf.end()` ends a Node stream and closes a `WritableStream`
|
|
|
61
62
|
CID-keyed CFF gets an identity charset so every reader maps its glyphs alike); `false` embeds the whole font; or a
|
|
62
63
|
function `(font, codePoints, { glyphs }) => Promise<Uint8Array>` (e.g. a HarfBuzz subsetter, which also
|
|
63
64
|
desubroutinises CFF) that must keep glyph IDs. `shaper`: see Shaping.
|
|
65
|
+
A variable font (an `fvar` table) is drawn at its default instance, which may be its thinnest weight (Noto Sans JP's
|
|
66
|
+
is Thin), with a warning naming the axes' values: for another weight, embed a static instance of it (fonttools
|
|
67
|
+
`varLib.instancer`, or `hb-subset --instance`).
|
|
64
68
|
- `font.widthOfText(text, size, { kerning, characterSpacing, wordSpacing, horizontalScaling, direction })` — the width
|
|
65
69
|
text is drawn at (kerned, shaped, spaced); `font.widthOf(text, size)` — plain advances; `font.lineHeight(size)` —
|
|
66
70
|
ascent − descent + line gap (1.2 × size for standard fonts); `font.ascent`, `font.descent` (fractions of the size).
|
|
@@ -123,14 +127,31 @@ Text options (`page.text` and `page.textBox`):
|
|
|
123
127
|
returns the glyphs in visual order, in font units: `[{ g, cl, ax, dx, dy }]` (glyph ID, cluster = UTF-16 index of
|
|
124
128
|
its first character, x advance, offsets: what HarfBuzz's `hb_shape` gives). So Arabic, Indic and other complex
|
|
125
129
|
scripts draw correctly. The glyphs are drawn by ID with their advances and offsets (TJ adjustments, `Ts` for vertical
|
|
126
|
-
offsets);
|
|
127
|
-
of several glyphs (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
offsets); copied and extracted text is the source text, once, in logical order (checked with Acrobat-style
|
|
131
|
+
ActualText readers, poppler and pdf.js): a cluster of several glyphs (a conjunct, a reordered matra, a letter and its
|
|
132
|
+
dots) has its text on one glyph and WJ (U+2060) on the others, which are drawn after the run or inside an
|
|
133
|
+
`/ActualText` span, and a glyph that stands for different text in different places gets a code for each (a
|
|
134
|
+
`CIDToGIDMap`; a CFF font cannot, so there it gets an ActualText span). The shaper's output is checked (glyph IDs in
|
|
135
|
+
the font, finite numbers, clusters inside the text, at most 4 glyphs a character).
|
|
136
|
+
|
|
137
|
+
An adapter for harfbuzzjs v1 (`npm i harfbuzzjs`; not a dependency of this package). The same file ships in the package
|
|
138
|
+
as `examples/harfbuzz-shaper.mjs`:
|
|
131
139
|
|
|
132
140
|
```js
|
|
133
|
-
import
|
|
141
|
+
import * as hb from 'harfbuzzjs';
|
|
142
|
+
function harfbuzzShaper(bytes) {
|
|
143
|
+
const font = new hb.Font(new hb.Face(new hb.Blob(bytes))); // positions in font units
|
|
144
|
+
return (text, _font, { direction, kerning }) => {
|
|
145
|
+
const buf = new hb.Buffer();
|
|
146
|
+
buf.addText(text); // clusters are UTF-16 indices
|
|
147
|
+
buf.guessSegmentProperties(); // script and language from the text
|
|
148
|
+
buf.setDirection(direction === 'rtl' ? hb.Direction.RTL : hb.Direction.LTR);
|
|
149
|
+
hb.shape(font, buf, kerning ? [] : [new hb.Feature('kern', 0)]);
|
|
150
|
+
const pos = buf.getGlyphPositions();
|
|
151
|
+
return buf.getGlyphInfos().map((g, i) => ({ g: g.codepoint, cl: g.cluster, ax: pos[i].xAdvance, dx: pos[i].xOffset, dy: pos[i].yOffset }));
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
const bytes = fs.readFileSync('NotoSansDevanagari-Regular.ttf');
|
|
135
156
|
const hindi = await pdf.embedFont(bytes, { shaper: harfbuzzShaper(bytes) });
|
|
136
157
|
page.text('किताब हिन्दी', { x: 40, y: 40, font: hindi, size: 18 });
|
|
@@ -657,7 +678,11 @@ const signed = await doc.saveIncremental({ sign: { field: { name: 'approval', pa
|
|
|
657
678
|
sign: an empty signature field by name, or a new one (`{ name, page, x, y, width, height }`; no size: invisible), with
|
|
658
679
|
group F's signers, the `/ByteRange` over the whole file including the original bytes. An encrypted file's new objects
|
|
659
680
|
use its own AES key; RC4 files cannot be updated (writing RC4 is refused).
|
|
660
|
-
- `mergePdfs(docs, options)`: every page of each, into a new file.
|
|
681
|
+
- `mergePdfs(docs, options)`: every page of each, into a new file. **A merged PDF/UA (or PDF/A-a) file is no longer
|
|
682
|
+
tagged**: the structure tree is kept only when every page comes from one document (and the form is not flattened),
|
|
683
|
+
so a merge drops it with `/MarkInfo` and `/Lang`, the XMP's PDF/UA identification (`pdfuaid`) goes too, PDF/A-a is
|
|
684
|
+
claimed as PDF/A-b, and `warnings` says "the structure tree (tags) was dropped … the result is not tagged (no
|
|
685
|
+
longer PDF/UA …)". Tag the merged content again with the writer if the result must be PDF/UA.
|
|
661
686
|
- Errors from the input are `PdfReadError` with a `code`: `E_MALFORMED`, `E_BUDGET`, `E_DEPTH`, `E_CYCLE`,
|
|
662
687
|
`E_TIMEOUT`, `E_ABORTED`, `E_PASSWORD`, `E_UNSUPPORTED`, `E_ACTIVE`.
|
|
663
688
|
|
|
@@ -689,7 +714,7 @@ JavaScript, launch and submit actions cannot be written at all (see Forms and Li
|
|
|
689
714
|
|---|---|
|
|
690
715
|
| Decoded stream bytes, the whole document / one stream (every filter and predictor counts as it produces, so a zip bomb stops at the cap: a 302-byte stream that inflates to 10 GB stops at 128 MB) | `maxDecoded` 512 MB / `maxStream` 128 MB |
|
|
691
716
|
| Parsing steps (tokens, xref rows, tree nodes, operators); time | `maxSteps` 200 M; `timeoutMs`, `signal` |
|
|
692
|
-
| Cross-reference entries and scanned objects held; one page's text pieces | `maxObjects` 5 M; `maxTextItems`
|
|
717
|
+
| Cross-reference entries and scanned objects held; one page's text pieces | `maxObjects` 5 M; `maxTextItems` 1 M (and 16 M characters a page, ActualText included) |
|
|
693
718
|
| Nesting of arrays and dictionaries (an explicit stack, never recursion) | 256 |
|
|
694
719
|
| A chain of indirect references | 32 |
|
|
695
720
|
|
|
@@ -738,3 +763,11 @@ widgets may cover at most 40% of a page in all, and a widget with an opaque back
|
|
|
738
763
|
|
|
739
764
|
Image masks (stencils) and colour-managed images (embedded ICC profiles of PNG/JPEG files are ignored); the Unicode
|
|
740
765
|
bidi algorithm, hyphenation, kerning of the standard fonts, WOFF fonts; popup annotations; RC4 encryption (refused; read only), public-key (certificate) encryption (neither written nor read), verifying signatures' cryptography (they are listed with their coverage); PDF/A-1a, PDF/X, attachment annotations in PDF/A-3 (use `pdf.attach`), validating a Factur-X invoice's content; layout-perfect text extraction; page edits in an incremental update (use `save()`); reading encrypted files in browsers (WebCrypto has no synchronous AES; Node only). See NOTES.md for the plan. Extending the library: ARCHITECTURE.md.
|
|
766
|
+
|
|
767
|
+
## Changes
|
|
768
|
+
|
|
769
|
+
0.1.0-beta.1: shaped text (Indic, Arabic) extracts once and in order in pdf.js, poppler and `extractText`;
|
|
770
|
+
`extractText` honours ActualText, reads unmapped glyphs as U+FFFD, falls back to an embedded TrueType cmap, spaces
|
|
771
|
+
separate text objects (spreadsheet cells), orders a line along its baseline (rotated text too), and stops a
|
|
772
|
+
2 M-operator page at 170 MB (`maxTextItems` 1 M); standard-font text writes 40% faster; variable fonts warn; the
|
|
773
|
+
package ships `examples/`. The list with details: NOTES.md ("Changes") in the repository.
|
package/dist/index.js
CHANGED
|
@@ -1663,14 +1663,14 @@ function createDoc(out, o) {
|
|
|
1663
1663
|
}
|
|
1664
1664
|
await writeNodes(level2);
|
|
1665
1665
|
const I = o.info, when = I.creationDate;
|
|
1666
|
-
const xmp = doc.xmp ?? new TextEncoder().encode(xmpPacket({ title: I.title, author: I.author, subject: I.subject, keywords: I.keywords, stamp: when.toISOString().replace(/\.\d+Z$/, "Z"), pdfa: doc.pdfaLevel, ua: o.tagged, extensions: doc.xmpExtensions }));
|
|
1667
|
-
const meta = objs.reserve();
|
|
1668
|
-
await objs.write(meta, "<< /Type /Metadata /Subtype /XML >>", xmp);
|
|
1666
|
+
const xmp = o.metadata === false ? null : doc.xmp ?? new TextEncoder().encode(xmpPacket({ title: I.title, author: I.author, subject: I.subject, keywords: I.keywords, stamp: when.toISOString().replace(/\.\d+Z$/, "Z"), pdfa: doc.pdfaLevel, ua: o.tagged, extensions: doc.xmpExtensions }));
|
|
1667
|
+
const meta = xmp ? objs.reserve() : 0;
|
|
1668
|
+
if (xmp) await objs.write(meta, "<< /Type /Metadata /Subtype /XML >>", xmp);
|
|
1669
1669
|
const info = objs.reserve();
|
|
1670
1670
|
if (doc.info != null) await objs.write(info, doc.info);
|
|
1671
1671
|
else await objs.write(info, `<< /Producer ${text(PRODUCER)} /Creator ${text(PRODUCER)}${I.title != null ? ` /Title ${text(I.title)}` : ""}${I.author != null ? ` /Author ${text(I.author)}` : ""}${I.subject != null ? ` /Subject ${text(I.subject)}` : ""}${I.keywords != null ? ` /Keywords ${text(I.keywords)}` : ""} /CreationDate ${pdfDate(when)} /ModDate ${pdfDate(when)} >>`);
|
|
1672
1672
|
const root = objs.reserve();
|
|
1673
|
-
let cat = `<< /Type /Catalog /Pages ${level2[0].ref} 0 R /Metadata ${meta} 0 R`;
|
|
1673
|
+
let cat = `<< /Type /Catalog /Pages ${level2[0].ref} 0 R${meta ? ` /Metadata ${meta} 0 R` : ""}`;
|
|
1674
1674
|
for (const [k, v] of catalog) cat += ` ${pdfName(k)} ${v}`;
|
|
1675
1675
|
await objs.write(root, cat + " >>");
|
|
1676
1676
|
const r = await objs.finish({ root, info, id: o.id, last: doc.last });
|
|
@@ -2028,13 +2028,12 @@ function segments(str3, fonts, o) {
|
|
|
2028
2028
|
}
|
|
2029
2029
|
function runWidth(run2, size, sp) {
|
|
2030
2030
|
let a = 0, n = 0, s = 0;
|
|
2031
|
-
if (Array.isArray(run2)) {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
} else ({ a, n, sp: s } = run2);
|
|
2031
|
+
if (Array.isArray(run2)) for (const it of run2) {
|
|
2032
|
+
a += it.a;
|
|
2033
|
+
if (it.sp) s++;
|
|
2034
|
+
if (!it.c) n++;
|
|
2035
|
+
}
|
|
2036
|
+
else ({ a, n, sp: s } = run2);
|
|
2038
2037
|
return (a * size / 1e3 + n * sp.tc + s * sp.tw) * sp.tz / 100;
|
|
2039
2038
|
}
|
|
2040
2039
|
function measure(impl, str3, size, o) {
|
|
@@ -2042,11 +2041,11 @@ function measure(impl, str3, size, o) {
|
|
|
2042
2041
|
const sp = spacing(o);
|
|
2043
2042
|
return runWidth(impl.items(str3, { kerning: o?.kerning, rtl: o?.direction === "rtl", ws: sp.tw, measure: true }), size, sp);
|
|
2044
2043
|
}
|
|
2045
|
-
function runOps(run2, size, rise, tw, standard) {
|
|
2044
|
+
function runOps(run2, size, rise, tw, standard, tc = 0) {
|
|
2046
2045
|
if (!Array.isArray(run2)) return run2.body ? `(${run2.body}) Tj
|
|
2047
2046
|
` : "";
|
|
2048
|
-
const wsT = standard ? 0 : tw * 1e3 / size;
|
|
2049
|
-
let s = "", arr = "", lit = "", cur = 0, pos = 0, r = rise, left = 0, adjusted = false;
|
|
2047
|
+
const wsT = standard ? 0 : tw * 1e3 / size, tcT = tc * 1e3 / size;
|
|
2048
|
+
let s = "", arr = "", lit = "", cur = 0, pos = 0, r = rise, left = 0, adjusted = false, out = false;
|
|
2050
2049
|
const endLit = () => {
|
|
2051
2050
|
if (lit) {
|
|
2052
2051
|
arr += `(${lit})`;
|
|
@@ -2063,13 +2062,7 @@ function runOps(run2, size, rise, tw, standard) {
|
|
|
2063
2062
|
adjusted = false;
|
|
2064
2063
|
}
|
|
2065
2064
|
};
|
|
2066
|
-
|
|
2067
|
-
if (it.at !== void 0) {
|
|
2068
|
-
flush();
|
|
2069
|
-
s += `/Span << /ActualText ${text(it.at)} >> BDC
|
|
2070
|
-
`;
|
|
2071
|
-
left = it.n || 1;
|
|
2072
|
-
}
|
|
2065
|
+
const put = (it) => {
|
|
2073
2066
|
const want = rise + it.dy * size / 1e3;
|
|
2074
2067
|
if (Math.abs(want - r) >= 5e-4) {
|
|
2075
2068
|
flush();
|
|
@@ -2087,6 +2080,20 @@ function runOps(run2, size, rise, tw, standard) {
|
|
|
2087
2080
|
}
|
|
2088
2081
|
lit += it.b;
|
|
2089
2082
|
cur += it.w;
|
|
2083
|
+
};
|
|
2084
|
+
for (const it of run2) {
|
|
2085
|
+
if (it.c) {
|
|
2086
|
+
pos += it.a;
|
|
2087
|
+
out = true;
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
if (it.at !== void 0) {
|
|
2091
|
+
flush();
|
|
2092
|
+
s += `/Span << /ActualText ${text(it.at)} >> BDC
|
|
2093
|
+
`;
|
|
2094
|
+
left = it.n || 1;
|
|
2095
|
+
}
|
|
2096
|
+
put(it);
|
|
2090
2097
|
pos += it.a + (it.sp ? wsT : 0);
|
|
2091
2098
|
if (left && --left === 0) {
|
|
2092
2099
|
flush();
|
|
@@ -2094,6 +2101,23 @@ function runOps(run2, size, rise, tw, standard) {
|
|
|
2094
2101
|
}
|
|
2095
2102
|
}
|
|
2096
2103
|
flush();
|
|
2104
|
+
if (out) {
|
|
2105
|
+
s += `/Span << /ActualText ${text("")} >> BDC
|
|
2106
|
+
0 0 Td
|
|
2107
|
+
`;
|
|
2108
|
+
cur = pos = 0;
|
|
2109
|
+
for (const it of run2) {
|
|
2110
|
+
if (!it.c) {
|
|
2111
|
+
pos += it.a + (it.sp ? wsT : 0) + tcT;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
put(it);
|
|
2115
|
+
cur += tcT;
|
|
2116
|
+
pos += it.a;
|
|
2117
|
+
}
|
|
2118
|
+
flush();
|
|
2119
|
+
s += "EMC\n";
|
|
2120
|
+
}
|
|
2097
2121
|
if (r !== rise) s += `${num(rise)} Ts
|
|
2098
2122
|
`;
|
|
2099
2123
|
return s;
|
|
@@ -2148,7 +2172,7 @@ function drawLine(page, fonts, str3, x2, base3, size, ts, o) {
|
|
|
2148
2172
|
}
|
|
2149
2173
|
let at = x2;
|
|
2150
2174
|
for (const s of segs) {
|
|
2151
|
-
const ops = runOps(s.run, size, ts.rise, ts.sp.tw, s.impl.standard);
|
|
2175
|
+
const ops = runOps(s.run, size, ts.rise, ts.sp.tw, s.impl.standard, ts.sp.tc);
|
|
2152
2176
|
if (ops) c.run(s.font.name, size, at, page.height - base3, ops, ts === PLAIN ? PLAIN_STATE : { Tc: ts.sp.tc, Tw: s.impl.standard ? ts.sp.tw : 0, Tz: ts.sp.tz, Ts: ts.rise, Tr: ts.Tr });
|
|
2153
2177
|
if (segs.length > 1) at += runWidth(s.run, size, ts.sp);
|
|
2154
2178
|
}
|
|
@@ -10009,6 +10033,13 @@ function standardFont(doc, key) {
|
|
|
10009
10033
|
/** @type {number[]} */
|
|
10010
10034
|
m.widths
|
|
10011
10035
|
);
|
|
10036
|
+
const W8 = new Float64Array(256), SAME = new Uint8Array(256);
|
|
10037
|
+
for (let c = 0; c < 256; c++) {
|
|
10038
|
+
const code = codeOf.get(c);
|
|
10039
|
+
W8[c] = code === void 0 ? -1 : widths[code] || 0;
|
|
10040
|
+
SAME[c] = code === c && esc(code) === String.fromCharCode(c) ? 1 : 0;
|
|
10041
|
+
}
|
|
10042
|
+
const SP32 = codeOf.get(32) === 32;
|
|
10012
10043
|
const ref = doc.reserve();
|
|
10013
10044
|
const missing = /* @__PURE__ */ new Set();
|
|
10014
10045
|
const memo2 = /* @__PURE__ */ new Map();
|
|
@@ -10068,8 +10099,31 @@ function standardFont(doc, key) {
|
|
|
10068
10099
|
lineHeight: 1.2,
|
|
10069
10100
|
has: (cp) => codeOf.has(cp),
|
|
10070
10101
|
items(text3, o) {
|
|
10071
|
-
let body = "", a = 0, n = 0, sp = 0;
|
|
10072
10102
|
const draw = !o.measure;
|
|
10103
|
+
if (!o.rtl) {
|
|
10104
|
+
let a2 = 0, n2 = 0, sp2 = 0, same = true;
|
|
10105
|
+
for (let i2 = 0; i2 < text3.length; i2++) {
|
|
10106
|
+
const c = text3.charCodeAt(i2);
|
|
10107
|
+
if (c > 255) {
|
|
10108
|
+
same = false;
|
|
10109
|
+
break;
|
|
10110
|
+
}
|
|
10111
|
+
const w = W8[c];
|
|
10112
|
+
if (w < 0) {
|
|
10113
|
+
same = false;
|
|
10114
|
+
break;
|
|
10115
|
+
}
|
|
10116
|
+
a2 += w;
|
|
10117
|
+
n2++;
|
|
10118
|
+
if (c === 32 && SP32) sp2++;
|
|
10119
|
+
if (!SAME[c]) {
|
|
10120
|
+
same = false;
|
|
10121
|
+
break;
|
|
10122
|
+
}
|
|
10123
|
+
}
|
|
10124
|
+
if (same) return { body: draw ? text3 : "", a: a2, n: n2, sp: sp2 };
|
|
10125
|
+
}
|
|
10126
|
+
let body = "", a = 0, n = 0, sp = 0;
|
|
10073
10127
|
for (const ch of o.rtl ? [...text3].reverse() : text3) {
|
|
10074
10128
|
const b = byte(ch);
|
|
10075
10129
|
if (!b) {
|
|
@@ -10938,9 +10992,15 @@ function embedFont(doc, input, opt = {}) {
|
|
|
10938
10992
|
use(0);
|
|
10939
10993
|
const two = (g) => esc(g >> 8) + esc(g & 255);
|
|
10940
10994
|
const kern = kerning(f.tables, f.numGlyphs, (m) => doc.warn(`${f.postscriptName.slice(0, 63)}: ${m}`));
|
|
10995
|
+
const axes = variation(f.tables.get("fvar"));
|
|
10996
|
+
if (axes) doc.warn(`${f.postscriptName.slice(0, 63)} is a variable font: it is drawn at its default instance (${axes}); to draw another weight or width, embed a static instance of it (fonttools varLib.instancer, or HarfBuzz's hb-subset --instance)`);
|
|
10941
10997
|
const k = 1e3 / f.unitsPerEm;
|
|
10942
10998
|
const ref = doc.reserve();
|
|
10943
|
-
const
|
|
10999
|
+
const codes = /* @__PURE__ */ new Map();
|
|
11000
|
+
const codeOf = /* @__PURE__ */ new Map();
|
|
11001
|
+
const copies = !f.cff;
|
|
11002
|
+
const EXACT = [];
|
|
11003
|
+
let next = f.numGlyphs;
|
|
10944
11004
|
const missing = /* @__PURE__ */ new Set();
|
|
10945
11005
|
const memo2 = /* @__PURE__ */ new Map();
|
|
10946
11006
|
const strict = doc.pdfa || doc.tagged;
|
|
@@ -10957,24 +11017,31 @@ function embedFont(doc, input, opt = {}) {
|
|
|
10957
11017
|
);
|
|
10958
11018
|
if (!g) lack(ch);
|
|
10959
11019
|
if (!g && missing.size < 100) missing.add(ch);
|
|
10960
|
-
|
|
10961
|
-
use(g);
|
|
10962
|
-
glyphs2.set(g, g ? ch : "");
|
|
10963
|
-
}
|
|
10964
|
-
memo2.set(ch, b = !g && strict ? "" : two(g));
|
|
11020
|
+
memo2.set(ch, b = !g && strict ? "" : two(code(g, ch)[0]));
|
|
10965
11021
|
}
|
|
10966
11022
|
return b;
|
|
10967
11023
|
};
|
|
10968
|
-
const
|
|
11024
|
+
const code = (g, t) => {
|
|
10969
11025
|
const m = t.length <= 32 ? t : String.fromCodePoint(
|
|
10970
11026
|
/** @type {number} */
|
|
10971
11027
|
t.codePointAt(0)
|
|
10972
11028
|
);
|
|
10973
|
-
if (
|
|
10974
|
-
|
|
10975
|
-
|
|
11029
|
+
if (codes.get(g)?.t === m) return m === t ? EXACT[g] ??= [g, true] : [g, false];
|
|
11030
|
+
let c = codeOf.get(`${g}:${m}`);
|
|
11031
|
+
if (c === void 0) {
|
|
11032
|
+
if (!codes.has(g)) {
|
|
11033
|
+
use(g);
|
|
11034
|
+
c = g;
|
|
11035
|
+
} else if (copies && next <= 65535) c = next++;
|
|
11036
|
+
else return [
|
|
11037
|
+
g,
|
|
11038
|
+
/** @type {any} */
|
|
11039
|
+
codes.get(g).t === t
|
|
11040
|
+
];
|
|
11041
|
+
codes.set(c, { g, t: m });
|
|
11042
|
+
codeOf.set(`${g}:${m}`, c);
|
|
10976
11043
|
}
|
|
10977
|
-
return
|
|
11044
|
+
return [c, m === t];
|
|
10978
11045
|
};
|
|
10979
11046
|
let handle;
|
|
10980
11047
|
const items = (text3, o) => {
|
|
@@ -11025,9 +11092,9 @@ function embedFont(doc, input, opt = {}) {
|
|
|
11025
11092
|
if (missing.size < 100 && !o.measure) missing.add(ch);
|
|
11026
11093
|
if (strict) continue;
|
|
11027
11094
|
}
|
|
11028
|
-
const ok = o.measure
|
|
11095
|
+
const [c, ok] = o.measure ? [g, true] : code(g, ch), w = W(g);
|
|
11029
11096
|
if (kerned && prev >= 0) out[out.length - 1].a += kern(prev, g) * k;
|
|
11030
|
-
out.push({ b: two(
|
|
11097
|
+
out.push({ b: two(c), w, a: w, dx: 0, dy: 0, sp: ch === " ", at: ok ? void 0 : ch, n: 1 });
|
|
11031
11098
|
prev = g;
|
|
11032
11099
|
}
|
|
11033
11100
|
return out;
|
|
@@ -11051,33 +11118,61 @@ function embedFont(doc, input, opt = {}) {
|
|
|
11051
11118
|
});
|
|
11052
11119
|
const starts = [...new Set(gl.map((x2) => x2.cl))].sort((a, b) => a - b);
|
|
11053
11120
|
const end = new Map(starts.map((c, i2) => [c, starts[i2 + 1] ?? text3.length]));
|
|
11054
|
-
const out = [];
|
|
11121
|
+
const out = [], said = /* @__PURE__ */ new Set();
|
|
11122
|
+
const at = (a) => {
|
|
11123
|
+
let b = a;
|
|
11124
|
+
while (b < gl.length && gl[b].cl === gl[a].cl) b++;
|
|
11125
|
+
return [b, said.has(gl[a].cl) ? "" : text3.slice(gl[a].cl, end.get(gl[a].cl))];
|
|
11126
|
+
};
|
|
11127
|
+
let glued = null;
|
|
11055
11128
|
for (let i2 = 0; i2 < gl.length; ) {
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11129
|
+
const [j, t0] = at(i2), cl = gl[i2].cl, first = out.length;
|
|
11130
|
+
if (glued && t0 === " " && j - i2 === 1) {
|
|
11131
|
+
const { g, ax, dx, dy } = gl[i2];
|
|
11132
|
+
out.push({ b: two(o.measure ? g : code(g, "")[0]), w: W(g), a: ax * k, dx: dx * k, dy: dy * k, sp: true });
|
|
11133
|
+
if (glued.n) glued.n++;
|
|
11134
|
+
glued = null;
|
|
11135
|
+
said.add(cl);
|
|
11136
|
+
i2 = j;
|
|
11137
|
+
continue;
|
|
11138
|
+
}
|
|
11139
|
+
glued = null;
|
|
11140
|
+
const glue = !o.rtl && !!t0 && t0 !== " " && j < gl.length && at(j)[1] === " " && at(j)[0] - j === 1 && at(j)[0] < gl.length && MN.test(at(at(j)[0])[1]);
|
|
11141
|
+
const t = glue ? `${t0} ` : t0;
|
|
11142
|
+
let lead = -1, span = glue;
|
|
11143
|
+
if (t0) {
|
|
11144
|
+
for (let q = i2; q < j; q++) if ((gl[q].g || !strict) && (lead < 0 || gl[q].ax > gl[lead].ax)) lead = q;
|
|
11145
|
+
}
|
|
11060
11146
|
for (let q = i2; q < j; q++) {
|
|
11061
11147
|
const { g, ax, dx, dy } = gl[q];
|
|
11062
11148
|
if (!g) {
|
|
11063
11149
|
if (!o.measure) lack(t);
|
|
11064
|
-
if (missing.size < 100 && !o.measure) missing.add(t);
|
|
11150
|
+
if (missing.size < 100 && !o.measure && t) missing.add(t);
|
|
11065
11151
|
if (strict) {
|
|
11066
11152
|
if (out.length > first) out[out.length - 1].a += ax * k;
|
|
11067
11153
|
continue;
|
|
11068
11154
|
}
|
|
11069
11155
|
}
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
out[first].at = t;
|
|
11075
|
-
out[first].n = out.length - first;
|
|
11156
|
+
const [c, exact] = o.measure ? [g, true] : code(g, q !== lead ? "" : o.rtl && RTL3.test(t) ? [...t].reverse().join("") : t);
|
|
11157
|
+
if (!exact) span = true;
|
|
11158
|
+
if (q !== lead && Math.abs(ax * k) >= 1) span ||= !o.rtl;
|
|
11159
|
+
out.push({ b: two(c), w: W(g), a: ax * k, dx: dx * k, dy: dy * k, sp: q === lead && t === " ", c: q !== lead && Math.abs(ax * k) < 1 || void 0 });
|
|
11076
11160
|
}
|
|
11161
|
+
if (out.length > first) said.add(cl);
|
|
11162
|
+
if (span && !o.measure) {
|
|
11163
|
+
const flow = out.slice(first).filter((x2) => !x2.c);
|
|
11164
|
+
if (flow.length) {
|
|
11165
|
+
flow[0].at = t;
|
|
11166
|
+
flow[0].n = flow.length;
|
|
11167
|
+
if (glue) glued = flow[0];
|
|
11168
|
+
}
|
|
11169
|
+
} else if (glue) glued = /** @type {any} */
|
|
11170
|
+
{};
|
|
11077
11171
|
i2 = j;
|
|
11078
11172
|
}
|
|
11079
11173
|
return out;
|
|
11080
11174
|
};
|
|
11175
|
+
const MN = /\p{Mn}/u, RTL3 = /[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
11081
11176
|
const impl = {
|
|
11082
11177
|
standard: false,
|
|
11083
11178
|
items,
|
|
@@ -11114,12 +11209,12 @@ function embedFont(doc, input, opt = {}) {
|
|
|
11114
11209
|
return s;
|
|
11115
11210
|
},
|
|
11116
11211
|
async write() {
|
|
11117
|
-
|
|
11118
|
-
const used = [...
|
|
11119
|
-
const cps = [...
|
|
11212
|
+
const cids = [...codes.keys()].sort((a, b) => a - b);
|
|
11213
|
+
const used = [.../* @__PURE__ */ new Set([0, ...[...codes.values()].map((x2) => x2.g)])].sort((a, b) => a - b);
|
|
11214
|
+
const cps = [...new Set([...codes.values()].flatMap((x2) => [...x2.t].map((c) => (
|
|
11120
11215
|
/** @type {number} */
|
|
11121
11216
|
c.codePointAt(0)
|
|
11122
|
-
)));
|
|
11217
|
+
))))];
|
|
11123
11218
|
let file = bytes;
|
|
11124
11219
|
if (sub && !(f.fsType & 256)) {
|
|
11125
11220
|
if (typeof sub === "function") {
|
|
@@ -11144,8 +11239,8 @@ function embedFont(doc, input, opt = {}) {
|
|
|
11144
11239
|
) : file);
|
|
11145
11240
|
let cidSet = "";
|
|
11146
11241
|
if (subset && doc.pdfaLevel?.part === 1) {
|
|
11147
|
-
const bits2 = new Uint8Array((
|
|
11148
|
-
for (const g of
|
|
11242
|
+
const all = [.../* @__PURE__ */ new Set([...used, ...cids])], bits2 = new Uint8Array((Math.max(...all) >> 3) + 1), n = doc.reserve();
|
|
11243
|
+
for (const g of all) bits2[g >> 3] |= 128 >> (g & 7);
|
|
11149
11244
|
await doc.writeStream(n, "<< >>", bits2);
|
|
11150
11245
|
cidSet = ` /CIDSet ${n} 0 R`;
|
|
11151
11246
|
}
|
|
@@ -11153,16 +11248,27 @@ function embedFont(doc, input, opt = {}) {
|
|
|
11153
11248
|
const flags = (f.fixedPitch ? 1 : 0) | 4 | (f.italicAngle ? 64 : 0);
|
|
11154
11249
|
const [x0, y0, x1, y1] = f.bbox;
|
|
11155
11250
|
await doc.write(desc, `<< /Type /FontDescriptor /FontName ${base3} /Flags ${flags} /FontBBox [${s(x0)} ${s(y0)} ${s(x1)} ${s(y1)}] /ItalicAngle ${num(f.italicAngle)} /Ascent ${s(f.ascent)} /Descent ${s(f.descent)} /CapHeight ${s(f.capHeight)} /XHeight ${s(f.xHeight)} /StemV 0 /${cff ? "FontFile3" : "FontFile2"} ${fileRef} 0 R${cidSet} >>`);
|
|
11156
|
-
const
|
|
11251
|
+
const wc = (cids.length ? cids : [0]).map((c) => [c, codes.get(c)?.g ?? 0]);
|
|
11157
11252
|
let Wa = "";
|
|
11158
|
-
for (let i2 = 0; i2 <
|
|
11253
|
+
for (let i2 = 0; i2 < wc.length; ) {
|
|
11159
11254
|
let j = i2;
|
|
11160
|
-
while (j + 1 <
|
|
11161
|
-
Wa += `${
|
|
11255
|
+
while (j + 1 < wc.length && wc[j + 1][0] === wc[j][0] + 1) j++;
|
|
11256
|
+
Wa += `${wc[i2][0]} [${wc.slice(i2, j + 1).map(([, g]) => s(f.advance(g))).join(" ")}] `;
|
|
11162
11257
|
i2 = j + 1;
|
|
11163
11258
|
}
|
|
11164
|
-
|
|
11165
|
-
|
|
11259
|
+
let c2g = " /CIDToGIDMap /Identity";
|
|
11260
|
+
if (!cff && next > f.numGlyphs) {
|
|
11261
|
+
const m = new Uint8Array(2 * next), r = doc.reserve();
|
|
11262
|
+
for (let c = 0; c < next; c++) {
|
|
11263
|
+
const g = c < f.numGlyphs ? c : codes.get(c)?.g ?? 0;
|
|
11264
|
+
m[2 * c] = g >> 8;
|
|
11265
|
+
m[2 * c + 1] = g & 255;
|
|
11266
|
+
}
|
|
11267
|
+
await doc.writeStream(r, "<< >>", m);
|
|
11268
|
+
c2g = ` /CIDToGIDMap ${r} 0 R`;
|
|
11269
|
+
}
|
|
11270
|
+
await doc.write(cid, `<< /Type /Font /Subtype /${cff ? "CIDFontType0" : "CIDFontType2"} /BaseFont ${base3} /CIDSystemInfo << /Registry ${ascii("Adobe")} /Ordering ${ascii("Identity")} /Supplement 0 >> /FontDescriptor ${desc} 0 R /W [${Wa}]${cff ? "" : c2g} >>`);
|
|
11271
|
+
await doc.writeStream(cmap, "<< >>", latin1(toUnicode(codes)));
|
|
11166
11272
|
await doc.write(ref, `<< /Type /Font /Subtype /Type0 /BaseFont ${base3} /Encoding /Identity-H /DescendantFonts [${cid} 0 R] /ToUnicode ${cmap} 0 R >>`);
|
|
11167
11273
|
}
|
|
11168
11274
|
};
|
|
@@ -11193,8 +11299,11 @@ function subsetOtf(bytes, n, used, seac) {
|
|
|
11193
11299
|
out.set("CFF ", cff);
|
|
11194
11300
|
return buildSfnt(out, [79, 84, 84, 79]);
|
|
11195
11301
|
}
|
|
11196
|
-
function toUnicode(
|
|
11197
|
-
const entries = [...
|
|
11302
|
+
function toUnicode(codes) {
|
|
11303
|
+
const entries = [...codes].map(([c, x2]) => (
|
|
11304
|
+
/** @type {[number, string]} */
|
|
11305
|
+
[c, x2.t]
|
|
11306
|
+
)).sort((a, b) => a[0] - b[0]);
|
|
11198
11307
|
let body = "";
|
|
11199
11308
|
for (let i2 = 0; i2 < entries.length; i2 += 100) {
|
|
11200
11309
|
const chunk = entries.slice(i2, i2 + 100);
|
|
@@ -11218,6 +11327,16 @@ end
|
|
|
11218
11327
|
end
|
|
11219
11328
|
`;
|
|
11220
11329
|
}
|
|
11330
|
+
function variation(t) {
|
|
11331
|
+
if (!t || t.length < 16) return "";
|
|
11332
|
+
const dv = new DataView(t.buffer, t.byteOffset, t.byteLength), off = dv.getUint16(4), n = dv.getUint16(8), size = dv.getUint16(10);
|
|
11333
|
+
const out = [];
|
|
11334
|
+
for (let i2 = 0; i2 < Math.min(n, 16) && size >= 20 && off + i2 * size + 20 <= t.length; i2++) {
|
|
11335
|
+
const p = off + i2 * size;
|
|
11336
|
+
out.push(`${String.fromCharCode(...t.subarray(p, p + 4)).replace(/[^\x20-\x7e]/g, "?")} ${Math.round(dv.getInt32(p + 8) / 655.36) / 100}`);
|
|
11337
|
+
}
|
|
11338
|
+
return out.join(", ") || "no axes read";
|
|
11339
|
+
}
|
|
11221
11340
|
function tag2(bytes) {
|
|
11222
11341
|
let h = 2166136261;
|
|
11223
11342
|
for (let i2 = 0; i2 < bytes.length; i2++) h = Math.imul(h ^ bytes[i2], 16777619);
|
|
@@ -12883,7 +13002,7 @@ var init_budget = __esm({
|
|
|
12883
13002
|
fail = (code, msg) => {
|
|
12884
13003
|
throw new PdfReadError(code, msg);
|
|
12885
13004
|
};
|
|
12886
|
-
DEFAULTS = { maxDecoded: 512 << 20, maxStream: 128 << 20, maxSteps: 2e8, maxObjects: 5e6, maxTextItems:
|
|
13005
|
+
DEFAULTS = { maxDecoded: 512 << 20, maxStream: 128 << 20, maxSteps: 2e8, maxObjects: 5e6, maxTextItems: 1e6 };
|
|
12887
13006
|
}
|
|
12888
13007
|
});
|
|
12889
13008
|
|
|
@@ -13648,9 +13767,45 @@ var init_crypt = __esm({
|
|
|
13648
13767
|
async function openSource(bytes, budget, o = {}) {
|
|
13649
13768
|
const size = bytes.length;
|
|
13650
13769
|
let xref = /* @__PURE__ */ new Map();
|
|
13651
|
-
|
|
13770
|
+
let young = /* @__PURE__ */ new Map(), old = /* @__PURE__ */ new Map();
|
|
13771
|
+
const weak = /* @__PURE__ */ new Map();
|
|
13772
|
+
const cache = {
|
|
13773
|
+
/** @param {number} n @returns {any} undefined: not cached */
|
|
13774
|
+
get(n) {
|
|
13775
|
+
let v = young.get(n);
|
|
13776
|
+
if (v !== void 0) return v;
|
|
13777
|
+
v = old.get(n);
|
|
13778
|
+
if (v === void 0) {
|
|
13779
|
+
v = weak.get(n)?.deref();
|
|
13780
|
+
if (v === void 0) {
|
|
13781
|
+
weak.delete(n);
|
|
13782
|
+
return void 0;
|
|
13783
|
+
}
|
|
13784
|
+
weak.delete(n);
|
|
13785
|
+
}
|
|
13786
|
+
cache.set(n, v);
|
|
13787
|
+
return v;
|
|
13788
|
+
},
|
|
13789
|
+
/**
|
|
13790
|
+
* Held strongly for two turns of CACHE_N (a cheap LRU: no reordering per hit), then weakly
|
|
13791
|
+
* @param {number} n @param {any} v
|
|
13792
|
+
*/
|
|
13793
|
+
set(n, v) {
|
|
13794
|
+
young.set(n, v);
|
|
13795
|
+
if (young.size < CACHE_N) return;
|
|
13796
|
+
for (const [k, o2] of old) if (o2 !== null && typeof o2 === "object" && !young.has(k)) weak.set(k, new WeakRef(o2));
|
|
13797
|
+
old = young;
|
|
13798
|
+
young = /* @__PURE__ */ new Map();
|
|
13799
|
+
},
|
|
13800
|
+
clear() {
|
|
13801
|
+
young.clear();
|
|
13802
|
+
old.clear();
|
|
13803
|
+
weak.clear();
|
|
13804
|
+
}
|
|
13805
|
+
};
|
|
13652
13806
|
const loading = /* @__PURE__ */ new Set();
|
|
13653
13807
|
const objstms = /* @__PURE__ */ new Map();
|
|
13808
|
+
let objstmBytes = 0;
|
|
13654
13809
|
let endstreams = null;
|
|
13655
13810
|
let recovered = false;
|
|
13656
13811
|
let security = null, encryptRef = -1;
|
|
@@ -13768,12 +13923,17 @@ async function openSource(bytes, budget, o = {}) {
|
|
|
13768
13923
|
if (lx2.next() !== T.NUM) break;
|
|
13769
13924
|
offs[k] = lx2.val;
|
|
13770
13925
|
}
|
|
13771
|
-
os = { data, nums: nums.
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13926
|
+
os = { data, nums: nums.slice(0, k), offs: offs.slice(0, k), first };
|
|
13927
|
+
while (objstms.size && (objstms.size >= OBJSTM_CACHE || objstmBytes + data.length > OBJSTM_BYTES)) {
|
|
13928
|
+
const [k0, o0] = (
|
|
13929
|
+
/** @type {[number, any]} */
|
|
13930
|
+
objstms.entries().next().value
|
|
13931
|
+
);
|
|
13932
|
+
objstms.delete(k0);
|
|
13933
|
+
objstmBytes -= o0.data.length;
|
|
13934
|
+
}
|
|
13776
13935
|
objstms.set(s, os);
|
|
13936
|
+
objstmBytes += data.length;
|
|
13777
13937
|
}
|
|
13778
13938
|
let j = os.nums[i2] === n ? i2 : -1;
|
|
13779
13939
|
if (j < 0) {
|
|
@@ -13792,7 +13952,8 @@ async function openSource(bytes, budget, o = {}) {
|
|
|
13792
13952
|
return v === void 0 || v instanceof Op ? null : v;
|
|
13793
13953
|
}
|
|
13794
13954
|
function get(n) {
|
|
13795
|
-
|
|
13955
|
+
const c = cache.get(n);
|
|
13956
|
+
if (c !== void 0) return c;
|
|
13796
13957
|
if (loading.has(n)) return null;
|
|
13797
13958
|
loading.add(n);
|
|
13798
13959
|
try {
|
|
@@ -14046,6 +14207,7 @@ async function openSource(bytes, budget, o = {}) {
|
|
|
14046
14207
|
}
|
|
14047
14208
|
cache.clear();
|
|
14048
14209
|
objstms.clear();
|
|
14210
|
+
objstmBytes = 0;
|
|
14049
14211
|
}
|
|
14050
14212
|
function recoverDeep() {
|
|
14051
14213
|
const found = xref, tr = trailer;
|
|
@@ -14116,6 +14278,7 @@ async function openSource(bytes, budget, o = {}) {
|
|
|
14116
14278
|
const keep = encryptRef >= 0 ? cache.get(encryptRef) : void 0;
|
|
14117
14279
|
cache.clear();
|
|
14118
14280
|
objstms.clear();
|
|
14281
|
+
objstmBytes = 0;
|
|
14119
14282
|
if (keep !== void 0) cache.set(encryptRef, keep);
|
|
14120
14283
|
}
|
|
14121
14284
|
}
|
|
@@ -14124,7 +14287,13 @@ async function openSource(bytes, budget, o = {}) {
|
|
|
14124
14287
|
recover();
|
|
14125
14288
|
recoverDeep();
|
|
14126
14289
|
}
|
|
14127
|
-
|
|
14290
|
+
let root = null;
|
|
14291
|
+
try {
|
|
14292
|
+
root = resolve2(trailer.get("Root"));
|
|
14293
|
+
} catch (e) {
|
|
14294
|
+
if (!(e instanceof PdfReadError) || e.code === "E_TIMEOUT" || e.code === "E_ABORTED" || e.code === "E_BUDGET") throw e;
|
|
14295
|
+
if (e.code === "E_DEPTH") fail("E_DEPTH", `The document catalog cannot be read: ${e.message}`);
|
|
14296
|
+
}
|
|
14128
14297
|
if (!(root instanceof Map)) fail("E_MALFORMED", "The PDF has no document catalog (/Root)");
|
|
14129
14298
|
return {
|
|
14130
14299
|
bytes,
|
|
@@ -14305,7 +14474,7 @@ function inRegion(regions, budget) {
|
|
|
14305
14474
|
return reach > off;
|
|
14306
14475
|
};
|
|
14307
14476
|
}
|
|
14308
|
-
var MAX_SECTIONS, MAX_RESOLVE, MAX_OBJSTM_N, OBJSTM_CACHE, P48, P16, pack, unpack;
|
|
14477
|
+
var MAX_SECTIONS, MAX_RESOLVE, MAX_OBJSTM_N, OBJSTM_CACHE, OBJSTM_BYTES, CACHE_N, P48, P16, pack, unpack;
|
|
14309
14478
|
var init_source = __esm({
|
|
14310
14479
|
"src/reader/source.js"() {
|
|
14311
14480
|
init_lexer();
|
|
@@ -14316,6 +14485,8 @@ var init_source = __esm({
|
|
|
14316
14485
|
MAX_RESOLVE = 32;
|
|
14317
14486
|
MAX_OBJSTM_N = 1e6;
|
|
14318
14487
|
OBJSTM_CACHE = 32;
|
|
14488
|
+
OBJSTM_BYTES = 8 << 20;
|
|
14489
|
+
CACHE_N = 1024;
|
|
14319
14490
|
P48 = 2 ** 48;
|
|
14320
14491
|
P16 = 2 ** 16;
|
|
14321
14492
|
pack = (t, a, b) => t * P48 + a * P16 + b;
|
|
@@ -14432,7 +14603,10 @@ function parseCMap(data, budget, held = { n: 0 }) {
|
|
|
14432
14603
|
}
|
|
14433
14604
|
return { map, spaces };
|
|
14434
14605
|
}
|
|
14435
|
-
function fontOf(src, font, held =
|
|
14606
|
+
function fontOf(src, font, held = (
|
|
14607
|
+
/** @type {Held} */
|
|
14608
|
+
{ n: 0, cmaps: /* @__PURE__ */ new Map() }
|
|
14609
|
+
)) {
|
|
14436
14610
|
const R = src.resolve;
|
|
14437
14611
|
const sub = R(font.get("Subtype"));
|
|
14438
14612
|
let toU = null, spaces = (
|
|
@@ -14489,6 +14663,26 @@ function fontOf(src, font, held = { n: 0 }) {
|
|
|
14489
14663
|
}
|
|
14490
14664
|
}
|
|
14491
14665
|
const lens = spaces.length ? [...new Set(spaces.map((s) => s[0]))].sort() : [2];
|
|
14666
|
+
let back;
|
|
14667
|
+
const fromFont = (c) => {
|
|
14668
|
+
if (back === void 0) back = fontCmap(src, cid, held);
|
|
14669
|
+
if (!back) return void 0;
|
|
14670
|
+
const m = cid instanceof Map ? R(cid.get("CIDToGIDMap")) : null;
|
|
14671
|
+
let g = c;
|
|
14672
|
+
if (m instanceof PdfStream) {
|
|
14673
|
+
if (!gidMap) {
|
|
14674
|
+
try {
|
|
14675
|
+
gidMap = src.decodeStream(m).data.slice(0, 131072);
|
|
14676
|
+
} catch (e) {
|
|
14677
|
+
if (stops(e)) throw e;
|
|
14678
|
+
gidMap = new Uint8Array(0);
|
|
14679
|
+
}
|
|
14680
|
+
}
|
|
14681
|
+
g = 2 * c + 1 < gidMap.length ? gidMap[2 * c] << 8 | gidMap[2 * c + 1] : 0;
|
|
14682
|
+
}
|
|
14683
|
+
return back.get(g);
|
|
14684
|
+
};
|
|
14685
|
+
let gidMap;
|
|
14492
14686
|
return {
|
|
14493
14687
|
/** each glyph to emit, none held (a 16 MB string is not 16 M objects) @param {Uint8Array} b @param {Emit} emit */
|
|
14494
14688
|
decode(b, emit) {
|
|
@@ -14504,7 +14698,7 @@ function fontOf(src, font, held = { n: 0 }) {
|
|
|
14504
14698
|
}
|
|
14505
14699
|
for (let k = 0; k < L && i2 + k < b.length; k++) c = c * 256 + b[i2 + k];
|
|
14506
14700
|
i2 += L;
|
|
14507
|
-
emit(toU?.get(c) ??
|
|
14701
|
+
emit(toU?.get(c) ?? fromFont(c) ?? NONE, W.get(c) ?? dw, false);
|
|
14508
14702
|
}
|
|
14509
14703
|
}
|
|
14510
14704
|
};
|
|
@@ -14527,7 +14721,7 @@ function fontOf(src, font, held = { n: 0 }) {
|
|
|
14527
14721
|
for (const x0 of d.slice(0, 4096)) {
|
|
14528
14722
|
const x2 = R(x0);
|
|
14529
14723
|
if (typeof x2 === "number") c = x2;
|
|
14530
|
-
else if (typeof x2 === "string") diff.set(c++ & 255, glyphText(x2));
|
|
14724
|
+
else if (typeof x2 === "string") diff.set(c++ & 255, x2 === ".notdef" ? "" : glyphText(x2) || NONE);
|
|
14531
14725
|
}
|
|
14532
14726
|
}
|
|
14533
14727
|
const symbolic = base3 === "Symbol" || base3 === "ZapfDingbats";
|
|
@@ -14547,7 +14741,7 @@ function fontOf(src, font, held = { n: 0 }) {
|
|
|
14547
14741
|
if (t === void 0) t = diff.get(c);
|
|
14548
14742
|
if (t === void 0) {
|
|
14549
14743
|
const u = symMap ? symMap.get(c) : baseFn(c);
|
|
14550
|
-
t = u === void 0 ?
|
|
14744
|
+
t = u === void 0 ? NONE : String.fromCodePoint(u);
|
|
14551
14745
|
}
|
|
14552
14746
|
let w = Array.isArray(widths) && typeof fc === "number" && c >= fc && c - fc < widths.length ? R(widths[c - fc]) : std ? std.widths[c] : 500;
|
|
14553
14747
|
if (typeof w !== "number" || !Number.isFinite(w)) w = 0;
|
|
@@ -14556,6 +14750,34 @@ function fontOf(src, font, held = { n: 0 }) {
|
|
|
14556
14750
|
}
|
|
14557
14751
|
};
|
|
14558
14752
|
}
|
|
14753
|
+
function fontCmap(src, cid, held) {
|
|
14754
|
+
const R = src.resolve;
|
|
14755
|
+
if (!(cid instanceof Map) || R(cid.get("Subtype")) !== "CIDFontType2") return null;
|
|
14756
|
+
const fd2 = R(cid.get("FontDescriptor")), ff = fd2 instanceof Map ? R(fd2.get("FontFile2")) : null;
|
|
14757
|
+
if (!(ff instanceof PdfStream)) return null;
|
|
14758
|
+
const was = held.cmaps.get(ff);
|
|
14759
|
+
if (was !== void 0) return was;
|
|
14760
|
+
if (held.cmaps.size >= MAX_CMAP_FONTS) fail("E_BUDGET", `A page's text reads the character maps of more than ${MAX_CMAP_FONTS} embedded fonts (fonts with no ToUnicode)`);
|
|
14761
|
+
held.cmaps.set(ff, null);
|
|
14762
|
+
let f;
|
|
14763
|
+
try {
|
|
14764
|
+
f = parseFont(src.decodeStream(ff).data);
|
|
14765
|
+
} catch (e) {
|
|
14766
|
+
if (stops(e)) throw e;
|
|
14767
|
+
return null;
|
|
14768
|
+
}
|
|
14769
|
+
src.budget.tick(65536);
|
|
14770
|
+
const back = /* @__PURE__ */ new Map();
|
|
14771
|
+
for (let cp = 32; cp <= 65535; cp++) {
|
|
14772
|
+
if (cp === 55296) cp = 63744;
|
|
14773
|
+
const g = f.glyphFor(cp);
|
|
14774
|
+
if (g && !back.has(g)) back.set(g, String.fromCharCode(cp));
|
|
14775
|
+
}
|
|
14776
|
+
held.n += 9 * back.size;
|
|
14777
|
+
if (held.n > MAX_HELD) fail("E_BUDGET", `A page's fonts hold more than ${MAX_HELD.toLocaleString("en")} units of code mappings (an entry 8, a character 1)`);
|
|
14778
|
+
held.cmaps.set(ff, back);
|
|
14779
|
+
return back;
|
|
14780
|
+
}
|
|
14559
14781
|
function contentBytes(src, contents) {
|
|
14560
14782
|
const c = src.resolve(contents);
|
|
14561
14783
|
const list = Array.isArray(c) ? c.slice(0, 1e4).map(src.resolve) : [c];
|
|
@@ -14582,6 +14804,36 @@ function contentBytes(src, contents) {
|
|
|
14582
14804
|
}
|
|
14583
14805
|
return out;
|
|
14584
14806
|
}
|
|
14807
|
+
function logical(parts) {
|
|
14808
|
+
if (!parts.some((t) => RTL2.test(t))) return parts;
|
|
14809
|
+
const kind = (t) => RTL2.test(t) ? "R" : NUM.test(t) ? "N" : STRONG_L.test(t) ? "L" : "N";
|
|
14810
|
+
const k = parts.map(kind), res = parts.slice();
|
|
14811
|
+
for (let i2 = 0; i2 < parts.length; ) {
|
|
14812
|
+
if (k[i2] !== "R") {
|
|
14813
|
+
i2++;
|
|
14814
|
+
continue;
|
|
14815
|
+
}
|
|
14816
|
+
let j = i2, e = i2;
|
|
14817
|
+
while (j < parts.length && k[j] !== "L") {
|
|
14818
|
+
if (k[j] === "R") e = j;
|
|
14819
|
+
j++;
|
|
14820
|
+
}
|
|
14821
|
+
const run2 = parts.slice(i2, e + 1).reverse().map(rev2);
|
|
14822
|
+
for (let a = 0; a < run2.length; ) {
|
|
14823
|
+
if (!NUM.test(run2[a])) {
|
|
14824
|
+
a++;
|
|
14825
|
+
continue;
|
|
14826
|
+
}
|
|
14827
|
+
let b = a;
|
|
14828
|
+
while (b < run2.length && NUM.test(run2[b])) b++;
|
|
14829
|
+
run2.splice(a, b - a, ...run2.slice(a, b).reverse().map(rev2));
|
|
14830
|
+
a = b;
|
|
14831
|
+
}
|
|
14832
|
+
res.splice(i2, run2.length, ...run2);
|
|
14833
|
+
i2 = e + 1;
|
|
14834
|
+
}
|
|
14835
|
+
return res;
|
|
14836
|
+
}
|
|
14585
14837
|
function skipInline(lx) {
|
|
14586
14838
|
const b = lx.buf;
|
|
14587
14839
|
let i2 = lx.pos + 1;
|
|
@@ -14611,9 +14863,31 @@ function textPieces(src, pageDict, resources, o = {}) {
|
|
|
14611
14863
|
}
|
|
14612
14864
|
function pageText(src, pageDict, resources, raw = false) {
|
|
14613
14865
|
const R = src.resolve, budget = src.budget;
|
|
14614
|
-
const
|
|
14866
|
+
const PX = [], PY = [], PE = [], PS = [], PG = [];
|
|
14867
|
+
const PA = [], PAE = [], PC = [], PD = [];
|
|
14868
|
+
const PT = [];
|
|
14869
|
+
let chars2 = 0;
|
|
14870
|
+
let moved = true;
|
|
14615
14871
|
const fonts = /* @__PURE__ */ new Map();
|
|
14616
|
-
const
|
|
14872
|
+
const add2 = (x2, y, end, size, text3, dir) => {
|
|
14873
|
+
if (PT.length >= budget.maxTextItems) throw new PdfReadError("E_BUDGET", `A page has more than ${budget.maxTextItems.toLocaleString("en")} pieces of text (budget.maxTextItems)`);
|
|
14874
|
+
if ((chars2 += text3.length) > MAX_TEXT_CHARS) throw new PdfReadError("E_BUDGET", `A page makes more than ${MAX_TEXT_CHARS.toLocaleString("en")} characters of text`);
|
|
14875
|
+
PX.push(x2);
|
|
14876
|
+
PY.push(y);
|
|
14877
|
+
PE.push(end);
|
|
14878
|
+
PS.push(size);
|
|
14879
|
+
PT.push(text3);
|
|
14880
|
+
PG.push(moved ? 2 : 0);
|
|
14881
|
+
moved = false;
|
|
14882
|
+
PA.push(dir[0]);
|
|
14883
|
+
PAE.push(dir[1]);
|
|
14884
|
+
PC.push(dir[2]);
|
|
14885
|
+
PD.push(dir[3]);
|
|
14886
|
+
};
|
|
14887
|
+
const held = (
|
|
14888
|
+
/** @type {Held} */
|
|
14889
|
+
{ n: 0, cmaps: /* @__PURE__ */ new Map() }
|
|
14890
|
+
);
|
|
14617
14891
|
const fontFor = (res, name) => {
|
|
14618
14892
|
const fd2 = R(res instanceof Map ? R(res.get("Font")) instanceof Map ? R(res.get("Font")).get(name) : null : null);
|
|
14619
14893
|
const key = fd2 instanceof Map ? fd2 : null;
|
|
@@ -14632,6 +14906,11 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14632
14906
|
), size: 0, Tc: 0, Tw: 0, Th: 1, TL: 0, Ts: 0 };
|
|
14633
14907
|
const tsStack = [];
|
|
14634
14908
|
let Tm = ID, Tlm = ID, dos = 0;
|
|
14909
|
+
const marks = [];
|
|
14910
|
+
let deep = 0, actual = (
|
|
14911
|
+
/** @type {any} */
|
|
14912
|
+
null
|
|
14913
|
+
);
|
|
14635
14914
|
let args = [];
|
|
14636
14915
|
const show = (b, ctm) => {
|
|
14637
14916
|
if (!ts.font) return;
|
|
@@ -14642,10 +14921,20 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14642
14921
|
const x2 = M0[4], y = M0[5], size = Math.hypot(M0[2], M0[3]) || ts.size;
|
|
14643
14922
|
const tx = (w / 1e3 * ts.size + ts.Tc + (space ? ts.Tw : 0)) * ts.Th;
|
|
14644
14923
|
Tm = mul2([1, 0, 0, 1, tx, 0], Tm);
|
|
14645
|
-
const
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14924
|
+
const M1 = mul2([ts.size * ts.Th, 0, 0, ts.size, 0, ts.Ts], mul2(Tm, ctm)), end = M1[4];
|
|
14925
|
+
const L = Math.hypot(M0[0], M0[1]), ux = L ? M0[0] / L : 1, uy = L ? M0[1] / L : 0;
|
|
14926
|
+
const dir = [x2 * ux + y * uy, M1[4] * ux + M1[5] * uy, y * ux - x2 * uy, Math.round(Math.atan2(uy, ux) * 180 / Math.PI)];
|
|
14927
|
+
if (actual) {
|
|
14928
|
+
if (!actual.seen) Object.assign(actual, { x: x2, y, size, dir, seen: true });
|
|
14929
|
+
actual.end = end;
|
|
14930
|
+
actual.dir[1] = dir[1];
|
|
14931
|
+
return;
|
|
14932
|
+
}
|
|
14933
|
+
const n = PT.length - 1;
|
|
14934
|
+
if (text3) add2(x2, y, end, size, text3, dir);
|
|
14935
|
+
else if (n >= 0 && PAE[n] < dir[1] && PD[n] === dir[3] && Math.abs(PC[n] - dir[2]) < 0.01) {
|
|
14936
|
+
PE[n] = end;
|
|
14937
|
+
PAE[n] = dir[1];
|
|
14649
14938
|
}
|
|
14650
14939
|
});
|
|
14651
14940
|
budget.tick(1 + (k & 1023));
|
|
@@ -14686,6 +14975,7 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14686
14975
|
}
|
|
14687
14976
|
case "BT":
|
|
14688
14977
|
Tm = Tlm = ID;
|
|
14978
|
+
moved = true;
|
|
14689
14979
|
break;
|
|
14690
14980
|
case "Tf":
|
|
14691
14981
|
ts = { ...ts, font: typeof a[0] === "string" ? fontFor(f.res, a[0]) : null, size: typeof a[1] === "number" ? a[1] : 0 };
|
|
@@ -14710,19 +15000,23 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14710
15000
|
// falls through
|
|
14711
15001
|
case "Td":
|
|
14712
15002
|
if (typeof a[0] === "number" && typeof a[1] === "number") Tm = Tlm = mul2([1, 0, 0, 1, a[0], a[1]], Tlm);
|
|
15003
|
+
moved = true;
|
|
14713
15004
|
break;
|
|
14714
15005
|
case "Tm": {
|
|
14715
15006
|
const m = matrixOf(a);
|
|
14716
15007
|
if (m) Tm = Tlm = m;
|
|
15008
|
+
moved = true;
|
|
14717
15009
|
break;
|
|
14718
15010
|
}
|
|
14719
15011
|
case "T*":
|
|
14720
15012
|
Tm = Tlm = mul2([1, 0, 0, 1, 0, -ts.TL], Tlm);
|
|
15013
|
+
moved = true;
|
|
14721
15014
|
break;
|
|
14722
15015
|
case "Tj":
|
|
14723
15016
|
if (a[0] instanceof PdfString) show(a[0].bytes, f.ctm);
|
|
14724
15017
|
break;
|
|
14725
15018
|
case "'":
|
|
15019
|
+
moved = true;
|
|
14726
15020
|
Tm = Tlm = mul2([1, 0, 0, 1, 0, -ts.TL], Tlm);
|
|
14727
15021
|
if (a[0] instanceof PdfString) show(a[0].bytes, f.ctm);
|
|
14728
15022
|
break;
|
|
@@ -14730,6 +15024,7 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14730
15024
|
if (typeof a[0] === "number") ts = { ...ts, Tw: a[0] };
|
|
14731
15025
|
if (typeof a[1] === "number") ts = { ...ts, Tc: a[1] };
|
|
14732
15026
|
Tm = Tlm = mul2([1, 0, 0, 1, 0, -ts.TL], Tlm);
|
|
15027
|
+
moved = true;
|
|
14733
15028
|
if (a[2] instanceof PdfString) show(a[2].bytes, f.ctm);
|
|
14734
15029
|
break;
|
|
14735
15030
|
case "TJ":
|
|
@@ -14737,10 +15032,38 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14737
15032
|
if (x2 instanceof PdfString) show(x2.bytes, f.ctm);
|
|
14738
15033
|
else if (typeof x2 === "number") {
|
|
14739
15034
|
Tm = mul2([1, 0, 0, 1, -x2 / 1e3 * ts.size * ts.Th, 0], Tm);
|
|
14740
|
-
if (x2 < -180 &&
|
|
15035
|
+
if (x2 < -180 && PT.length) PG[PT.length - 1] |= 1;
|
|
14741
15036
|
}
|
|
14742
15037
|
}
|
|
14743
15038
|
break;
|
|
15039
|
+
case "BMC":
|
|
15040
|
+
case "BDC": {
|
|
15041
|
+
if (marks.length >= MAX_MARKS) {
|
|
15042
|
+
deep++;
|
|
15043
|
+
break;
|
|
15044
|
+
}
|
|
15045
|
+
let at = null;
|
|
15046
|
+
if (op === "BDC" && !actual) {
|
|
15047
|
+
const pr = a[1] instanceof Map ? a[1] : typeof a[1] === "string" && f.res instanceof Map ? R(R(f.res.get("Properties")) instanceof Map ? R(f.res.get("Properties")).get(a[1]) : null) : null;
|
|
15048
|
+
const t = pr instanceof Map ? R(pr.get("ActualText")) : null;
|
|
15049
|
+
if (t instanceof PdfString && t.bytes.length > 2 * MAX_ACTUAL + 2) src.warn(`an /ActualText longer than ${MAX_ACTUAL} characters was ignored`);
|
|
15050
|
+
else if (t instanceof PdfString) at = actual = { at: decodeText(t), x: 0, y: 0, end: 0, size: 0, dir: [0, 0, 0, 0], seen: false };
|
|
15051
|
+
}
|
|
15052
|
+
marks.push(at);
|
|
15053
|
+
break;
|
|
15054
|
+
}
|
|
15055
|
+
case "EMC": {
|
|
15056
|
+
if (deep) {
|
|
15057
|
+
deep--;
|
|
15058
|
+
break;
|
|
15059
|
+
}
|
|
15060
|
+
const m = marks.pop();
|
|
15061
|
+
if (m && m === actual) {
|
|
15062
|
+
actual = null;
|
|
15063
|
+
if (m.seen && m.at) add2(m.x, m.y, m.end, m.size, m.at, m.dir);
|
|
15064
|
+
}
|
|
15065
|
+
break;
|
|
15066
|
+
}
|
|
14744
15067
|
case "BI": {
|
|
14745
15068
|
for (let k = 0; k < 4096; k++) {
|
|
14746
15069
|
const t = parseValue(f.lx);
|
|
@@ -14774,30 +15097,44 @@ function pageText(src, pageDict, resources, raw = false) {
|
|
|
14774
15097
|
default:
|
|
14775
15098
|
}
|
|
14776
15099
|
}
|
|
14777
|
-
if (raw) return
|
|
15100
|
+
if (raw) return PT.map((text3, i2) => ({ x: PX[i2], y: PY[i2], end: PE[i2], size: PS[i2], text: text3, gap: !!(PG[i2] & 1) }));
|
|
14778
15101
|
const out = [];
|
|
14779
|
-
let
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
15102
|
+
for (let i2 = 0; i2 < PT.length; ) {
|
|
15103
|
+
let j = i2 + 1;
|
|
15104
|
+
while (j < PT.length && PD[j] === PD[j - 1] && Math.abs(PC[j] - PC[j - 1]) <= Math.max(PS[j], PS[j - 1], 1) * 0.5) j++;
|
|
15105
|
+
const ix = [];
|
|
15106
|
+
for (let k = i2; k < j; k++) ix.push(k);
|
|
15107
|
+
let sorted = true;
|
|
15108
|
+
for (let k = i2 + 1; k < j && sorted; k++) if (PA[k] < PA[k - 1] - 0.01) sorted = false;
|
|
15109
|
+
if (!sorted) ix.sort((a, b) => PA[a] - PA[b]);
|
|
15110
|
+
const line3 = [];
|
|
15111
|
+
let lend = 0, lgap = false, last = "";
|
|
15112
|
+
for (const k of ix) {
|
|
15113
|
+
const text3 = PT[k], size = Math.max(PS[k], 1);
|
|
15114
|
+
if (line3.length && last !== " " && !text3.startsWith(" ") && (lgap || PA[k] - lend > size * (PG[k] & 2 ? 0.07 : 0.25))) line3.push(" ");
|
|
15115
|
+
line3.push(text3);
|
|
15116
|
+
last = text3[text3.length - 1];
|
|
15117
|
+
lend = Math.max(PAE[k], line3.length > 1 ? lend : -Infinity);
|
|
15118
|
+
lgap = !!(PG[k] & 1);
|
|
15119
|
+
}
|
|
15120
|
+
if (out.length) out.push("\n");
|
|
15121
|
+
out.push(logical(line3).join(""));
|
|
15122
|
+
i2 = j;
|
|
14790
15123
|
}
|
|
14791
15124
|
return out.join("");
|
|
14792
15125
|
}
|
|
14793
|
-
var MAX_FORMS, MAX_CMAP, MAX_DST, MAX_CMAP_CHARS, MAX_HELD, MAX_W, MAX_DO, TEXT_TIMEOUT_MS, DOC, WIN, MAC_HIGH, BASE, STD_HIGH, NAMES2, ACCENT, own, STD_ALIAS, stops, mul2, ID, matrixOf;
|
|
15126
|
+
var MAX_FORMS, MAX_CMAP, MAX_MARKS, MAX_ACTUAL, MAX_TEXT_CHARS, MAX_DST, MAX_CMAP_CHARS, MAX_HELD, MAX_W, MAX_DO, TEXT_TIMEOUT_MS, DOC, WIN, MAC_HIGH, BASE, STD_HIGH, NAMES2, ACCENT, own, STD_ALIAS, NONE, stops, MAX_CMAP_FONTS, mul2, ID, matrixOf, RTL2, STRONG_L, NUM, rev2;
|
|
14794
15127
|
var init_text2 = __esm({
|
|
14795
15128
|
"src/reader/text.js"() {
|
|
14796
15129
|
init_lexer();
|
|
14797
15130
|
init_standard_metrics();
|
|
14798
15131
|
init_budget();
|
|
15132
|
+
init_truetype();
|
|
14799
15133
|
MAX_FORMS = 16;
|
|
14800
15134
|
MAX_CMAP = 1e6;
|
|
15135
|
+
MAX_MARKS = 1e3;
|
|
15136
|
+
MAX_ACTUAL = 4096;
|
|
15137
|
+
MAX_TEXT_CHARS = 16e6;
|
|
14801
15138
|
MAX_DST = 128;
|
|
14802
15139
|
MAX_CMAP_CHARS = 4e6;
|
|
14803
15140
|
MAX_HELD = 16e6;
|
|
@@ -15002,7 +15339,9 @@ var init_text2 = __esm({
|
|
|
15002
15339
|
t[k]
|
|
15003
15340
|
) : void 0;
|
|
15004
15341
|
STD_ALIAS = { Arial: "Helvetica", "Arial-BoldMT": "Helvetica-Bold", ArialMT: "Helvetica", TimesNewRoman: "Times-Roman", TimesNewRomanPSMT: "Times-Roman", CourierNew: "Courier" };
|
|
15342
|
+
NONE = "�";
|
|
15005
15343
|
stops = (e) => e?.code === "E_TIMEOUT" || e?.code === "E_ABORTED" || e?.code === "E_BUDGET";
|
|
15344
|
+
MAX_CMAP_FONTS = 100;
|
|
15006
15345
|
mul2 = (A, B) => (
|
|
15007
15346
|
/** @type {M} */
|
|
15008
15347
|
[A[0] * B[0] + A[1] * B[2], A[0] * B[1] + A[1] * B[3], A[2] * B[0] + A[3] * B[2], A[2] * B[1] + A[3] * B[3], A[4] * B[0] + A[5] * B[2] + B[4], A[4] * B[1] + A[5] * B[3] + B[5]]
|
|
@@ -15013,6 +15352,10 @@ var init_text2 = __esm({
|
|
|
15013
15352
|
/** @type {M} */
|
|
15014
15353
|
a.slice(0, 6)
|
|
15015
15354
|
) : null;
|
|
15355
|
+
RTL2 = /[\u0590-\u05ff\u0600-\u065f\u066a-\u06ef\u06fa-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
|
|
15356
|
+
STRONG_L = /[\p{L}]/u;
|
|
15357
|
+
NUM = /^[\p{Nd}.,٫٬]+$/u;
|
|
15358
|
+
rev2 = (t) => [...t].reverse().join("");
|
|
15016
15359
|
}
|
|
15017
15360
|
});
|
|
15018
15361
|
|
|
@@ -15254,7 +15597,7 @@ function createCopier(t) {
|
|
|
15254
15597
|
let t0 = trees.get(owner);
|
|
15255
15598
|
if (t0) return t0;
|
|
15256
15599
|
const src = owner.r.src, R = src.resolve;
|
|
15257
|
-
t0 = { objs: /* @__PURE__ */ new
|
|
15600
|
+
t0 = { objs: /* @__PURE__ */ new Set(), nums: /* @__PURE__ */ new Set(), types: new Set(STRUCT_TYPES) };
|
|
15258
15601
|
trees.set(owner, t0);
|
|
15259
15602
|
const root = R(owner.r.root.get("StructTreeRoot"));
|
|
15260
15603
|
if (!(root instanceof Map)) return t0;
|
|
@@ -15476,6 +15819,7 @@ function createCopier(t) {
|
|
|
15476
15819
|
continue;
|
|
15477
15820
|
}
|
|
15478
15821
|
const rep = t.replace?.(owner, f.n);
|
|
15822
|
+
f.rep = rep;
|
|
15479
15823
|
f.v = rep !== void 0 ? rep : owner.r.src.get(f.n);
|
|
15480
15824
|
if (!t.keepActive && f.v instanceof Map) {
|
|
15481
15825
|
const r = judge(f.v, owner.r.src.resolve);
|
|
@@ -15500,7 +15844,7 @@ function createCopier(t) {
|
|
|
15500
15844
|
continue;
|
|
15501
15845
|
}
|
|
15502
15846
|
stack.pop();
|
|
15503
|
-
const v = f.v;
|
|
15847
|
+
const v = f.v, rep0 = f.rep;
|
|
15504
15848
|
let open = false;
|
|
15505
15849
|
const refText = (o, r) => {
|
|
15506
15850
|
if (mapOf(o).get(r.n) === GRAY) open = true;
|
|
@@ -15515,7 +15859,7 @@ function createCopier(t) {
|
|
|
15515
15859
|
let key = "";
|
|
15516
15860
|
const raw = v instanceof PdfStream ? owner.r.src.rawStream(v) : null;
|
|
15517
15861
|
if (t.dedupe && early === void 0 && !open && shareable(v)) {
|
|
15518
|
-
key =
|
|
15862
|
+
key = `${dict.length} ${fnv(dict)} ${raw ? `${raw.length} ${fnv(raw)}` : ""}`;
|
|
15519
15863
|
const hit = seen.get(key);
|
|
15520
15864
|
if (hit && hit.dict === dict && same(hit.raw(), raw)) {
|
|
15521
15865
|
m.set(f.n, hit.n);
|
|
@@ -15526,8 +15870,11 @@ function createCopier(t) {
|
|
|
15526
15870
|
const n = early ?? t.reserve();
|
|
15527
15871
|
await t.write(n, dict, data, v instanceof PdfStream ? v.dict : v instanceof Map ? v : null);
|
|
15528
15872
|
if (key) {
|
|
15529
|
-
const o2 = owner,
|
|
15530
|
-
seen.set(key, { n, dict, raw: () =>
|
|
15873
|
+
const o2 = owner, n2 = f.n, mine = rep0 === void 0 ? null : v;
|
|
15874
|
+
seen.set(key, { n, dict, raw: () => {
|
|
15875
|
+
const v2 = mine ?? o2.r.src.get(n2);
|
|
15876
|
+
return v2 instanceof PdfStream ? o2.r.src.rawStream(v2) : null;
|
|
15877
|
+
} });
|
|
15531
15878
|
}
|
|
15532
15879
|
m.set(f.n, n);
|
|
15533
15880
|
}
|
|
@@ -15547,13 +15894,11 @@ function createCopier(t) {
|
|
|
15547
15894
|
}
|
|
15548
15895
|
return typeof v !== "object" || v === null || v instanceof PdfString;
|
|
15549
15896
|
};
|
|
15550
|
-
const
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
o.set(b, a.length + 1);
|
|
15556
|
-
return o;
|
|
15897
|
+
const fnv = (x2) => {
|
|
15898
|
+
let h = 2166136261;
|
|
15899
|
+
if (typeof x2 === "string") for (let i2 = 0; i2 < x2.length; i2++) h = Math.imul(h ^ x2.charCodeAt(i2), 16777619);
|
|
15900
|
+
else for (let i2 = 0; i2 < x2.length; i2++) h = Math.imul(h ^ x2[i2], 16777619);
|
|
15901
|
+
return (h >>> 0).toString(36);
|
|
15557
15902
|
};
|
|
15558
15903
|
function streamDict(owner, s) {
|
|
15559
15904
|
const R = owner.r.src.resolve;
|
|
@@ -15622,16 +15967,14 @@ function createCopier(t) {
|
|
|
15622
15967
|
}
|
|
15623
15968
|
};
|
|
15624
15969
|
}
|
|
15625
|
-
var
|
|
15970
|
+
var SHARE, GRAY, HEXD, Raw, Foreign, OPEN_A, CLOSE_A, OPEN_D, CLOSE_D;
|
|
15626
15971
|
var init_copy = __esm({
|
|
15627
15972
|
"src/reader/copy.js"() {
|
|
15628
15973
|
init_lexer();
|
|
15629
15974
|
init_pdf();
|
|
15630
|
-
init_crypto();
|
|
15631
15975
|
init_policy();
|
|
15632
15976
|
init_deflate();
|
|
15633
15977
|
init_policy();
|
|
15634
|
-
sha256 = (b) => sha("SHA-256", b);
|
|
15635
15978
|
SHARE = /* @__PURE__ */ new Set(["Font", "FontDescriptor", "ExtGState", "Encoding", "Pattern", "Shading", "Halftone", "Function", "XObject", "Mask", "CMap", "OutputIntent", "Filespec", "EmbeddedFile"]);
|
|
15636
15979
|
GRAY = -2;
|
|
15637
15980
|
HEXD = "0123456789abcdef";
|
|
@@ -15926,7 +16269,8 @@ async function run(self, sink, o) {
|
|
|
15926
16269
|
const keepStruct = single && o.keepStructure !== false && !self.edits.flatten;
|
|
15927
16270
|
const info = self.info();
|
|
15928
16271
|
const edited = !!self.edits.info || level?.conformance !== self.pdfa()?.conformance;
|
|
15929
|
-
const
|
|
16272
|
+
const uaLost = !keepStruct && /pdfuaid:part/.test(self.xmp() ?? "");
|
|
16273
|
+
const srcXmp = self.edits.xmp ?? (edited || uaLost || !level && self.pdfa() ? null : self.xmp());
|
|
15930
16274
|
const created = o.creationDate ?? (info.creationDate instanceof Date ? info.creationDate : /* @__PURE__ */ new Date());
|
|
15931
16275
|
let intentOpt = null;
|
|
15932
16276
|
if (level && profile) {
|
|
@@ -16006,7 +16350,8 @@ async function run(self, sink, o) {
|
|
|
16006
16350
|
}
|
|
16007
16351
|
}
|
|
16008
16352
|
for (let k = 0; k < plan.length; k++) {
|
|
16009
|
-
const p = plan[k], owner = p.owner, src = owner.r.src, OR = src.resolve, pg = owner.r
|
|
16353
|
+
const p = plan[k], owner = p.owner, src = owner.r.src, OR = src.resolve, pg = pageAt(owner.r, p.index), d = pg.dict;
|
|
16354
|
+
if (pg.emptied) warn(pg.emptied);
|
|
16010
16355
|
const b = owner.boxes(p.index);
|
|
16011
16356
|
const W = b.mediaBox[2] - b.mediaBox[0], H = b.mediaBox[3] - b.mediaBox[1];
|
|
16012
16357
|
const page = (
|
|
@@ -16130,7 +16475,7 @@ async function run(self, sink, o) {
|
|
|
16130
16475
|
};
|
|
16131
16476
|
for (const k of ["ViewerPreferences", "PageMode", "PageLayout", "OCProperties", "Collection", "URI"]) await cat(k);
|
|
16132
16477
|
if (keepStruct) for (const k of ["StructTreeRoot", "MarkInfo", "Lang"]) await cat(k);
|
|
16133
|
-
else if (root.has("StructTreeRoot")) warn("the structure tree (tags) was dropped: the pages come from several documents,
|
|
16478
|
+
else if (root.has("StructTreeRoot")) warn("the structure tree (tags) was dropped: the pages come from several documents, the form was flattened, or keepStructure is false; the result is not tagged (no longer PDF/UA; PDF/A-a is claimed as PDF/A-b)");
|
|
16134
16479
|
if (level && level.part === 3) await cat("AF");
|
|
16135
16480
|
if (single && plan.length === self.r.pages.length && plan.every((p, i2) => p.index === i2)) await cat("PageLabels");
|
|
16136
16481
|
const oa = R(root.get("OpenAction"));
|
|
@@ -16943,6 +17288,26 @@ function flatTree(rootNode, src, kind = "Names") {
|
|
|
16943
17288
|
}
|
|
16944
17289
|
return out;
|
|
16945
17290
|
}
|
|
17291
|
+
function pageAt(r, i2) {
|
|
17292
|
+
const pg = r.pages[i2];
|
|
17293
|
+
if (pg.checked) return pg;
|
|
17294
|
+
pg.checked = true;
|
|
17295
|
+
const R = r.src.resolve;
|
|
17296
|
+
try {
|
|
17297
|
+
const c = R(pg.dict.get("Contents"));
|
|
17298
|
+
if (Array.isArray(c)) {
|
|
17299
|
+
r.src.budget.tick(Math.min(c.length, 1e4));
|
|
17300
|
+
for (const x2 of c.slice(0, 1e4)) R(x2);
|
|
17301
|
+
}
|
|
17302
|
+
} catch (e) {
|
|
17303
|
+
if (!(e instanceof PdfReadError) || e.code !== "E_CYCLE") throw e;
|
|
17304
|
+
pg.emptied = `page ${i2 + 1}'s /Contents has a reference loop; the page is read as empty`;
|
|
17305
|
+
r.src.warn(pg.emptied);
|
|
17306
|
+
pg.dict = new Map(pg.dict);
|
|
17307
|
+
pg.dict.delete("Contents");
|
|
17308
|
+
}
|
|
17309
|
+
return pg;
|
|
17310
|
+
}
|
|
16946
17311
|
function makeDoc(r, plan, edits) {
|
|
16947
17312
|
const { src, root, pages, pageIndex: pageIndex2 } = r;
|
|
16948
17313
|
const R = src.resolve;
|
|
@@ -17029,7 +17394,7 @@ function makeDoc(r, plan, edits) {
|
|
|
17029
17394
|
* document's timeoutMs when sooner) @param {{ timeoutMs?: number }} [o]
|
|
17030
17395
|
*/
|
|
17031
17396
|
extractText: (o) => {
|
|
17032
|
-
const pg = owner.r
|
|
17397
|
+
const pg = pageAt(owner.r, p.index);
|
|
17033
17398
|
return extractText(owner.r.src, pg.dict, owner.r.src.resolve(pg.inh.Resources), o);
|
|
17034
17399
|
},
|
|
17035
17400
|
/** the annotations: subtype, rect, contents, uri, the page index a link goes to */
|
|
@@ -17215,7 +17580,7 @@ function makeDoc(r, plan, edits) {
|
|
|
17215
17580
|
const contents = R(v.get("Contents"));
|
|
17216
17581
|
const end = range ? range[2] + range[3] : 0;
|
|
17217
17582
|
const ok = !!range && range[0] === 0 && range[1] <= range[2] && end <= src.size;
|
|
17218
|
-
const
|
|
17583
|
+
const rev3 = ok ? src.revisions.findIndex((e) => e >= end) : -1;
|
|
17219
17584
|
out.push({
|
|
17220
17585
|
field: f.name,
|
|
17221
17586
|
subFilter: typeof R(v.get("SubFilter")) === "string" ? R(v.get("SubFilter")) : null,
|
|
@@ -17229,7 +17594,7 @@ function makeDoc(r, plan, edits) {
|
|
|
17229
17594
|
/** the signature covers every byte of the file but its /Contents (no change after it) */
|
|
17230
17595
|
coversWholeFile: ok && end === src.size,
|
|
17231
17596
|
/** the revision (0 first) whose end it covers, -1 when none */
|
|
17232
|
-
revision:
|
|
17597
|
+
revision: rev3,
|
|
17233
17598
|
/** the bytes after it: an incremental update was appended after signing */
|
|
17234
17599
|
bytesAfter: ok ? src.size - end : null,
|
|
17235
17600
|
cms: contents instanceof PdfString ? contents.bytes : null,
|
|
@@ -17551,6 +17916,8 @@ function createPdf(sink, options2 = {}) {
|
|
|
17551
17916
|
/** @type {const} */
|
|
17552
17917
|
["title", "author", "subject", "keywords"]
|
|
17553
17918
|
) if (o[k] != null && (typeof o[k] !== "string" || o[k].length > 65536)) throw new TypeError(`createPdf: ${k} is a string of at most 65,536 characters`);
|
|
17919
|
+
if (o.metadata != null && typeof o.metadata !== "boolean") throw new TypeError("createPdf: metadata is true (the default: an XMP stream) or false (Info only)");
|
|
17920
|
+
if (o.metadata === false && (o.pdfa || o.tagged)) throw new TypeError("createPdf: metadata: false cannot be combined with pdfa or tagged: PDF/A and PDF/UA require the XMP metadata stream");
|
|
17554
17921
|
const level = pdfaLevel(o.pdfa, o.tagged);
|
|
17555
17922
|
o.signal?.throwIfAborted();
|
|
17556
17923
|
const ctl = new AbortController();
|
|
@@ -17575,6 +17942,7 @@ function createPdf(sink, options2 = {}) {
|
|
|
17575
17942
|
pdfa: !!o.pdfa,
|
|
17576
17943
|
pdfaLevel: level,
|
|
17577
17944
|
tagged: !!o.tagged,
|
|
17945
|
+
metadata: o.metadata !== false,
|
|
17578
17946
|
signal: ctl.signal,
|
|
17579
17947
|
encrypt: enc,
|
|
17580
17948
|
hash: signHash ? hasher(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// A Factur-X (ZUGFeRD 2.1+) invoice: a tagged PDF/A-3a page with the CII XML embedded as factur-x.xml.
|
|
2
|
+
//
|
|
3
|
+
// node examples/factur-x.mjs invoice.xml font.ttf out.pdf [profile]
|
|
4
|
+
//
|
|
5
|
+
// The XML must be a UN/CEFACT CrossIndustryInvoice whose GuidelineSpecifiedDocumentContextParameter ID names the
|
|
6
|
+
// profile (EN 16931 by default: urn:cen.eu:en16931:2017). pdf.facturX checks that, refuses any DOCTYPE or entity, and
|
|
7
|
+
// writes the fx XMP schema; it does not validate the invoice itself (run the Factur-X XSD and Schematron for that).
|
|
8
|
+
// The page here only shows a few fields; a real one draws the whole invoice from the same data as the XML.
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { createPdf } from '../dist/index.js';
|
|
11
|
+
|
|
12
|
+
const [xmlFile, fontFile, outFile, profile = 'EN 16931'] = process.argv.slice(2);
|
|
13
|
+
if (!outFile) { console.error('node examples/factur-x.mjs invoice.xml font.ttf out.pdf [profile]'); process.exit(2); }
|
|
14
|
+
const xml = fs.readFileSync(xmlFile);
|
|
15
|
+
const text = xml.toString('utf8');
|
|
16
|
+
/** the first <ram:tag> after anchor (display only: the page shows a few fields of the XML) */
|
|
17
|
+
const field = (tag, anchor = '') => new RegExp(`${anchor}[\\s\\S]*?<ram:${tag}[^>]*>([^<]*)<`).exec(text)?.[1] ?? '';
|
|
18
|
+
const number = field('ID', '<rsm:ExchangedDocument>'), seller = field('Name', '<ram:SellerTradeParty>');
|
|
19
|
+
|
|
20
|
+
const pdf = createPdf(fs.createWriteStream(outFile), {
|
|
21
|
+
title: `Invoice ${number}`, author: seller, subject: 'Factur-X invoice', keywords: `invoice, Factur-X, ${profile}`,
|
|
22
|
+
pdfa: { part: 3, conformance: 'a' }, tagged: { lang: 'en' },
|
|
23
|
+
});
|
|
24
|
+
const font = await pdf.embedFont(fs.readFileSync(fontFile));
|
|
25
|
+
const page = pdf.addPage({ trimBox: [0, 0, 595.28, 841.89] });
|
|
26
|
+
const line = (s, y, size = 11) => page.text(s, { x: 56, y, font, size });
|
|
27
|
+
page.artifact({ type: 'Pagination', subtype: 'Header' }, () => line('Factur-X invoice: the XML inside this PDF is the legal invoice', 24, 8));
|
|
28
|
+
page.tag('H1', () => line(`Invoice ${number}`, 60, 22));
|
|
29
|
+
page.tag('P', () => line(`Seller: ${seller}`, 110));
|
|
30
|
+
page.tag('Table', () => {
|
|
31
|
+
page.tag('TR', () => ['Net', 'VAT', 'Total'].forEach((h, i) => page.tag('TH', () => page.text(h, { x: 56 + i * 140, y: 150, font }), { scope: 'Column', id: `h${i}` })));
|
|
32
|
+
page.tag('TR', () => [field('TaxBasisTotalAmount'), field('TaxTotalAmount'), field('GrandTotalAmount')].forEach((v, i) =>
|
|
33
|
+
page.tag('TD', () => page.text(`${v} ${field('InvoiceCurrencyCode')}`, { x: 56 + i * 140, y: 170, font }), { headers: [`h${i}`] })));
|
|
34
|
+
});
|
|
35
|
+
await pdf.facturX(xml, { profile });
|
|
36
|
+
await pdf.end();
|
|
37
|
+
console.log(`wrote ${outFile}: PDF/A-3a with factur-x.xml (${profile})`);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// A shaper for @reportwright/pdf's embedFont(bytes, { shaper }) built on harfbuzzjs (npm i harfbuzzjs). harfbuzzjs is
|
|
2
|
+
// not a dependency of @reportwright/pdf: copy this file, or import it where harfbuzzjs is installed.
|
|
3
|
+
//
|
|
4
|
+
// import { harfbuzzShaper } from './harfbuzz-shaper.mjs';
|
|
5
|
+
// const bytes = fs.readFileSync('NotoSansArabic-Regular.ttf');
|
|
6
|
+
// const arabic = await pdf.embedFont(bytes, { shaper: harfbuzzShaper(bytes) });
|
|
7
|
+
// page.text('مرحبا بالعالم', { x: 40, y: 40, font: arabic, size: 18, direction: 'rtl' });
|
|
8
|
+
//
|
|
9
|
+
// The shaper gets one run (one font, one direction) and returns its glyphs in visual order, in font units:
|
|
10
|
+
// { g: glyph ID, cl: cluster (UTF-16 index of its first character), ax: x advance, dx, dy: offsets }.
|
|
11
|
+
import * as hb from 'harfbuzzjs';
|
|
12
|
+
|
|
13
|
+
const NO_KERN = [new hb.Feature('kern', 0)];
|
|
14
|
+
|
|
15
|
+
/** @param {Uint8Array} fontBytes the same bytes passed to embedFont */
|
|
16
|
+
export function harfbuzzShaper(fontBytes) {
|
|
17
|
+
const font = new hb.Font(new hb.Face(new hb.Blob(fontBytes))); // scale: units per em, so positions are font units
|
|
18
|
+
return (text, _font, { direction, kerning }) => {
|
|
19
|
+
const b = new hb.Buffer();
|
|
20
|
+
b.addText(text); // clusters are UTF-16 indices, as JavaScript strings count
|
|
21
|
+
b.guessSegmentProperties(); // script and language from the text
|
|
22
|
+
b.setDirection(direction === 'rtl' ? hb.Direction.RTL : hb.Direction.LTR);
|
|
23
|
+
hb.shape(font, b, kerning ? [] : NO_KERN);
|
|
24
|
+
const pos = b.getGlyphPositions();
|
|
25
|
+
return b.getGlyphInfos().map((g, i) => ({ g: g.codepoint, cl: g.cluster, ax: pos[i].xAdvance, dx: pos[i].xOffset, dy: pos[i].yOffset }));
|
|
26
|
+
};
|
|
27
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -74,6 +74,11 @@ export interface PdfOptions {
|
|
|
74
74
|
pdfa?: boolean | { part?: 1 | 2 | 3; conformance?: 'a' | 'b' | 'u'; outputIntent?: Uint8Array | ArrayBuffer };
|
|
75
75
|
/** PDF/UA-1: a structure tree from page.tag(). Needs title; fonts must be embedded. */
|
|
76
76
|
tagged?: boolean | { lang?: string };
|
|
77
|
+
/**
|
|
78
|
+
* false: no XMP metadata stream, the Info dictionary only (about 1 KB smaller: a one-page invoice). Default true.
|
|
79
|
+
* Refused with pdfa or tagged: PDF/A and PDF/UA require the XMP.
|
|
80
|
+
*/
|
|
81
|
+
metadata?: boolean;
|
|
77
82
|
/** stop the export: pending and later calls reject with signal.reason */
|
|
78
83
|
signal?: AbortSignal;
|
|
79
84
|
/** stop the export after this many milliseconds (a TimeoutError, code 'ETIMEDOUT') */
|
|
@@ -703,7 +708,7 @@ export interface ReadBudget {
|
|
|
703
708
|
maxSteps?: number;
|
|
704
709
|
/** cross-reference entries and scanned objects held (default 5,000,000) */
|
|
705
710
|
maxObjects?: number;
|
|
706
|
-
/** pieces of text one page's extraction holds (default
|
|
711
|
+
/** pieces of text one page's extraction holds (default 1,000,000; a page also stops at 16,000,000 characters) */
|
|
707
712
|
maxTextItems?: number;
|
|
708
713
|
}
|
|
709
714
|
export interface LoadOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reportwright/pdf",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
4
|
"description": "A streaming PDF writer for Node and browsers with no runtime dependencies: flat memory, PDF/A-1b to 3a, PDF/UA-1, Factur-X, embedded TrueType with Unicode text.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"./package.json": "./package.json"
|
|
16
16
|
},
|
|
17
17
|
"types": "./index.d.ts",
|
|
18
|
-
"files": ["dist", "index.d.ts", "README.md", "LICENSE", "THIRD-PARTY-NOTICES.md"],
|
|
18
|
+
"files": ["dist", "index.d.ts", "examples", "README.md", "LICENSE", "THIRD-PARTY-NOTICES.md"],
|
|
19
19
|
"engines": { "node": ">=20.16" },
|
|
20
20
|
"sideEffects": false
|
|
21
21
|
}
|