reamkit 1.33.2 → 1.34.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 +13 -10
- package/dist/esm/core/converter/facade.js +1 -0
- package/dist/esm/core/converter/ream.d.ts +6 -6
- package/dist/esm/core/converter/ream.js +11 -5
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +15 -0
- package/dist/esm/core/drawingml/text-warp.d.ts +34 -0
- package/dist/esm/core/drawingml/text-warp.js +49 -1
- package/dist/esm/core/font/index.d.ts +1 -1
- package/dist/esm/core/font/index.js +8 -0
- package/dist/esm/core/font/indic-shaper.d.ts +47 -0
- package/dist/esm/core/font/indic-shaper.js +1146 -0
- package/dist/esm/core/font/indic-table.d.ts +12 -0
- package/dist/esm/core/font/indic-table.js +463 -0
- package/dist/esm/core/font/measure.d.ts +45 -4
- package/dist/esm/core/font/measure.js +253 -14
- package/dist/esm/core/font/opentype-lookups.d.ts +214 -0
- package/dist/esm/core/font/opentype-lookups.js +1112 -0
- package/dist/esm/core/fonts/families.d.ts +4 -0
- package/dist/esm/core/fonts/families.js +43 -12
- package/dist/esm/core/fonts/provider.js +1 -0
- package/dist/esm/core/fonts/remote-fonts.d.ts +4 -2
- package/dist/esm/core/fonts/remote-fonts.js +36 -0
- package/dist/esm/core/fonts/scripts.d.ts +19 -0
- package/dist/esm/core/fonts/scripts.js +96 -1
- package/dist/esm/core/numbering/apply.js +12 -8
- package/dist/esm/core/style-cascade/index.d.ts +1 -1
- package/dist/esm/core/style-cascade/index.js +2 -2
- package/dist/esm/core/style-cascade/resolver.d.ts +9 -0
- package/dist/esm/core/style-cascade/resolver.js +31 -3
- package/dist/esm/core/style-cascade/table.js +3 -8
- package/dist/esm/excel/xlsx-to-pdf.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/layout/line-paint.d.ts +5 -4
- package/dist/esm/layout/line-paint.js +6 -5
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +8 -1
- package/dist/esm/layout/styled-layout.js +459 -112
- package/dist/esm/layout/undrawn.d.ts +18 -0
- package/dist/esm/layout/undrawn.js +59 -0
- package/dist/esm/pdf/cid-font.d.ts +5 -0
- package/dist/esm/pdf/cid-font.js +8 -2
- package/dist/esm/pdf/styled-page-emitter.js +117 -28
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf-reader/content.d.ts +1 -1
- package/dist/esm/pdf-reader/content.js +48 -13
- package/dist/esm/pdf-reader/embedded-fonts.js +1 -0
- package/dist/esm/pdf-reader/encodings.d.ts +9 -0
- package/dist/esm/pdf-reader/encodings.js +66 -1
- package/dist/esm/pdf-reader/face-outlines.js +1 -0
- package/dist/esm/pdf-reader/text.d.ts +10 -0
- package/dist/esm/pdf-reader/text.js +25 -3
- package/dist/esm/pdf-reader/vector.js +2 -2
- package/dist/esm/pptx/embedded-fonts.js +1 -0
- package/dist/esm/pptx/sp-helpers.js +4 -1
- package/dist/esm/svg/svg-text.js +30 -7
- package/dist/esm/word/doc/doc-reader.js +148 -57
- package/dist/esm/word/doc/doc-text.d.ts +80 -11
- package/dist/esm/word/doc/doc-text.js +438 -75
- package/dist/esm/word/document-parser.js +48 -28
- package/dist/esm/word/docx-to-pdf.d.ts +5 -5
- package/dist/esm/word/docx-to-pdf.js +9 -7
- package/dist/esm/word/docx-writer.js +3 -5
- package/dist/esm/word/font-embed.js +1 -0
- package/dist/esm/word/font-table.js +1 -0
- package/dist/esm/word/numbering-parser.js +3 -0
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.js +2 -2
- package/dist/esm/word/xml-helpers.d.ts +18 -0
- package/dist/esm/word/xml-helpers.js +48 -1
- package/package.json +7 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Loss } from '../core/ir/index.js';
|
|
2
|
+
import { LaidOutPage } from './page-doc.js';
|
|
3
|
+
/**
|
|
4
|
+
* The characters the pages draw with no glyph, each once, in the order they
|
|
5
|
+
* first appear.
|
|
6
|
+
*
|
|
7
|
+
* @param pages The laid-out pages.
|
|
8
|
+
* @returns The code points, and the 1-based page each first appears on.
|
|
9
|
+
*/
|
|
10
|
+
export declare function undrawnCharacters(pages: ReadonlyArray<LaidOutPage>): Map<number, number>;
|
|
11
|
+
/**
|
|
12
|
+
* The loss for the characters the pages draw as boxes, or `undefined` when
|
|
13
|
+
* every character has a glyph.
|
|
14
|
+
*
|
|
15
|
+
* @param pages The laid-out pages.
|
|
16
|
+
* @returns One loss that names the characters, at the page the first is on.
|
|
17
|
+
*/
|
|
18
|
+
export declare function undrawnLoss(pages: ReadonlyArray<LaidOutPage>): Loss | undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/layout/undrawn.ts
|
|
2
|
+
/** How many of the characters a loss names before it only counts the rest. */
|
|
3
|
+
var NAMED = 8;
|
|
4
|
+
/**
|
|
5
|
+
* The characters the pages draw with no glyph, each once, in the order they
|
|
6
|
+
* first appear.
|
|
7
|
+
*
|
|
8
|
+
* @param pages The laid-out pages.
|
|
9
|
+
* @returns The code points, and the 1-based page each first appears on.
|
|
10
|
+
*/
|
|
11
|
+
function undrawnCharacters(pages) {
|
|
12
|
+
const out = /* @__PURE__ */ new Map();
|
|
13
|
+
let pageNo = 0;
|
|
14
|
+
const readLine = (line) => {
|
|
15
|
+
for (const t of line.tokens) if (t.kind === "text") for (const ch of t.text) {
|
|
16
|
+
const cp = ch.codePointAt(0);
|
|
17
|
+
if (isControl(cp) || out.has(cp)) continue;
|
|
18
|
+
if (t.font.parsed.glyphForCodepoint(cp) === 0) out.set(cp, pageNo);
|
|
19
|
+
}
|
|
20
|
+
else if (t.kind === "math") for (const item of t.items) {
|
|
21
|
+
if (item.kind !== "glyph") continue;
|
|
22
|
+
for (const ch of item.text) {
|
|
23
|
+
const cp = ch.codePointAt(0);
|
|
24
|
+
if (isControl(cp) || out.has(cp)) continue;
|
|
25
|
+
if (item.font.parsed.glyphForCodepoint(cp) === 0) out.set(cp, pageNo);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else for (const text of t.metafile?.texts ?? []) readLine(text.line);
|
|
29
|
+
};
|
|
30
|
+
for (const page of pages) {
|
|
31
|
+
pageNo++;
|
|
32
|
+
for (const item of page.commands) if (item.type === "line") readLine(item.line);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The loss for the characters the pages draw as boxes, or `undefined` when
|
|
38
|
+
* every character has a glyph.
|
|
39
|
+
*
|
|
40
|
+
* @param pages The laid-out pages.
|
|
41
|
+
* @returns One loss that names the characters, at the page the first is on.
|
|
42
|
+
*/
|
|
43
|
+
function undrawnLoss(pages) {
|
|
44
|
+
const undrawn = undrawnCharacters(pages);
|
|
45
|
+
if (undrawn.size === 0) return void 0;
|
|
46
|
+
const cps = [...undrawn.keys()];
|
|
47
|
+
return {
|
|
48
|
+
severity: "dropped",
|
|
49
|
+
feature: "text",
|
|
50
|
+
detail: `no available font has a glyph for ${cps.slice(0, NAMED).map((cp) => `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`).join(", ")}${cps.length > NAMED ? ` and ${cps.length - NAMED} more` : ""}; they are drawn as empty boxes and are missing from the text layer`,
|
|
51
|
+
where: `page ${undrawn.get(cps[0])}`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** C0 and C1 controls: a line break or a tab is never drawn as a glyph. */
|
|
55
|
+
function isControl(cp) {
|
|
56
|
+
return cp < 32 || cp >= 127 && cp < 160;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { undrawnCharacters, undrawnLoss };
|
|
@@ -10,6 +10,11 @@ export interface EmbedTtfOptions {
|
|
|
10
10
|
* otherwise, since PDF/A-2/3 only constrain it when present).
|
|
11
11
|
*/
|
|
12
12
|
readonly cidSet?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The text of glyphs no character maps to — a conjunct, a reph, a vowel
|
|
15
|
+
* sign's contextual form — so that /ToUnicode names them too.
|
|
16
|
+
*/
|
|
17
|
+
readonly glyphText?: ReadonlyMap<number, string>;
|
|
13
18
|
}
|
|
14
19
|
/** The result of {@link embedTtfFont}: the Type 0 font reference plus measurement/encoding helpers. */
|
|
15
20
|
export interface EmbeddedFont {
|
package/dist/esm/pdf/cid-font.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { glyphClosure, subsetTtf } from "../core/font/ttf-subset.js";
|
|
2
2
|
import { lettersForLigature } from "../core/font/ligatures.js";
|
|
3
3
|
import { createFontMeasure } from "../core/font/measure.js";
|
|
4
|
+
import "../core/font/index.js";
|
|
4
5
|
import { deflatedStream, dict, name, ref } from "./objects.js";
|
|
5
6
|
//#region src/pdf/cid-font.ts
|
|
6
7
|
var encoder = new TextEncoder();
|
|
@@ -57,7 +58,7 @@ function embedTtfFont(doc, parsed, options = {}) {
|
|
|
57
58
|
W: widthRuns.flatMap(([first, ws]) => [first, ws]),
|
|
58
59
|
CIDToGIDMap: name("Identity")
|
|
59
60
|
}));
|
|
60
|
-
const toUnicodeRef = doc.add(deflatedStream({}, buildToUnicodeCMap(parsed, subsetGids)));
|
|
61
|
+
const toUnicodeRef = doc.add(deflatedStream({}, buildToUnicodeCMap(parsed, subsetGids, options.glyphText)));
|
|
61
62
|
const type0Ref = doc.add(dict({
|
|
62
63
|
Type: name("Font"),
|
|
63
64
|
Subtype: name("Type0"),
|
|
@@ -126,7 +127,7 @@ function codeRank(cp) {
|
|
|
126
127
|
if (cp >= 64256 && cp <= 65023 || cp >= 65136 && cp <= 65279) return 1;
|
|
127
128
|
return 0;
|
|
128
129
|
}
|
|
129
|
-
function buildToUnicodeCMap(parsed, subsetGids) {
|
|
130
|
+
function buildToUnicodeCMap(parsed, subsetGids, glyphText) {
|
|
130
131
|
const inSubset = subsetGids ? new Set(subsetGids) : void 0;
|
|
131
132
|
const gidToCps = /* @__PURE__ */ new Map();
|
|
132
133
|
const order = [];
|
|
@@ -169,6 +170,11 @@ function buildToUnicodeCMap(parsed, subsetGids) {
|
|
|
169
170
|
changed = true;
|
|
170
171
|
}
|
|
171
172
|
}
|
|
173
|
+
for (const [gid, text] of glyphText ?? []) {
|
|
174
|
+
if (gidToCps.has(gid)) continue;
|
|
175
|
+
gidToCps.set(gid, [...text].map((c) => c.codePointAt(0)));
|
|
176
|
+
order.push(gid);
|
|
177
|
+
}
|
|
172
178
|
const pairs = order.filter((g) => inSubset === void 0 || inSubset.has(g)).map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
|
|
173
179
|
const utf16beHex = (cps) => {
|
|
174
180
|
let hex = "";
|
|
@@ -7,7 +7,7 @@ import "../layout/styled-layout.js";
|
|
|
7
7
|
import { BORDER_DASHES, FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder, settableInOneMatrix, washVeil } from "../layout/line-paint.js";
|
|
8
8
|
import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
|
|
9
9
|
import { buildGradientAlphaMask, buildGradientPattern, shapeBbox } from "./shading.js";
|
|
10
|
-
import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
|
|
10
|
+
import { warpGlyphMatrix, warpGlyphRun } from "../core/drawingml/text-warp.js";
|
|
11
11
|
import { sanitizeHref } from "../core/links.js";
|
|
12
12
|
import { embedAssociatedFile } from "./embedded-file.js";
|
|
13
13
|
import { buildSrgbIccProfile } from "./icc-profile.js";
|
|
@@ -523,7 +523,8 @@ function embedFontResources(doc, laid, pdfaProfile) {
|
|
|
523
523
|
if (res.gids.size === 0) continue;
|
|
524
524
|
out.set(variant, embedTtfFont(doc, res.parsed, {
|
|
525
525
|
usedGids: res.gids,
|
|
526
|
-
cidSet
|
|
526
|
+
cidSet,
|
|
527
|
+
...res.glyphText ? { glyphText: res.glyphText } : {}
|
|
527
528
|
}));
|
|
528
529
|
}
|
|
529
530
|
return out;
|
|
@@ -677,7 +678,52 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
677
678
|
let lastColor = "";
|
|
678
679
|
let lastFauxWidth = 0;
|
|
679
680
|
let lastStroke;
|
|
681
|
+
let strokeFollowsFill = false;
|
|
680
682
|
let lastTz = 100;
|
|
683
|
+
/** A run's letter spacing, in the text space `Tz` scales (§9.4.4). */
|
|
684
|
+
const spacingOf = (tok) => {
|
|
685
|
+
const tz = Math.round((tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1) * 100);
|
|
686
|
+
return (tok.resolvedRun.letterSpacingPt ?? 0) / (tz > 0 ? tz / 100 : 1);
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* The `rg` that makes `hex` the fill colour, when it is not already, and
|
|
690
|
+
* the `RG` with it while a faked bold strokes the glyphs in their own colour.
|
|
691
|
+
*/
|
|
692
|
+
const fillTo = (hex) => {
|
|
693
|
+
const ops = [];
|
|
694
|
+
const [r, g, b] = hexToRgb01(hex);
|
|
695
|
+
const rgb = `${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)}`;
|
|
696
|
+
if (hex !== lastColor) {
|
|
697
|
+
ops.push(`${rgb} rg`);
|
|
698
|
+
lastColor = hex;
|
|
699
|
+
}
|
|
700
|
+
if (strokeFollowsFill && hex !== lastStroke) {
|
|
701
|
+
ops.push(`${rgb} RG`);
|
|
702
|
+
lastStroke = hex;
|
|
703
|
+
}
|
|
704
|
+
return ops.length > 0 ? ops.join(" ") : void 0;
|
|
705
|
+
};
|
|
706
|
+
/**
|
|
707
|
+
* The show operators for a token's text, with what the Indic shaper needs
|
|
708
|
+
* passed on. A syllable a run boundary cut is drawn in its runs' colours: the
|
|
709
|
+
* fill colour switches between its glyphs, each glyph in the colour of the
|
|
710
|
+
* first character it stands for, and the text state is left in the last.
|
|
711
|
+
*
|
|
712
|
+
* @param colours Per code point of `text`, its colour (the token's own,
|
|
713
|
+
* reversed with the text where it is).
|
|
714
|
+
*/
|
|
715
|
+
const showTextOf = (tok, text, colours = tok.charColorsHex) => {
|
|
716
|
+
const measure = tok.font.measure;
|
|
717
|
+
if (!measure.clustered(text)) return measure.showText(text);
|
|
718
|
+
const sources = colours ? measure.glyphSources(text) : void 0;
|
|
719
|
+
if (!colours || !sources) return measure.showText(text, tok.fontSizePt, spacingOf(tok));
|
|
720
|
+
const before = /* @__PURE__ */ new Map();
|
|
721
|
+
sources.forEach((s, i) => {
|
|
722
|
+
const op = fillTo(colours[s] ?? tok.resolvedRun.colorHex);
|
|
723
|
+
if (op !== void 0) before.set(i, op);
|
|
724
|
+
});
|
|
725
|
+
return measure.showText(text, tok.fontSizePt, spacingOf(tok), before);
|
|
726
|
+
};
|
|
681
727
|
const switchFontIfNeeded = (tok) => {
|
|
682
728
|
const fontKey = tok.font.resourceName;
|
|
683
729
|
if (fontKey !== lastFont || tok.fontSizePt !== lastSize) {
|
|
@@ -691,13 +737,11 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
691
737
|
out.push(fauxWidth > 0 ? `2 Tr ${formatNumber(fauxWidth)} w` : "0 Tr");
|
|
692
738
|
lastFauxWidth = fauxWidth;
|
|
693
739
|
}
|
|
694
|
-
|
|
695
|
-
if (
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
lastStroke = strokeHex;
|
|
740
|
+
strokeFollowsFill = outline === void 0 && fauxWidth > 0;
|
|
741
|
+
if (outline !== void 0 && outline.colorHex !== lastStroke) {
|
|
742
|
+
const [sr, sg, sb] = hexToRgb01(outline.colorHex);
|
|
743
|
+
out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} RG`);
|
|
744
|
+
lastStroke = outline.colorHex;
|
|
701
745
|
}
|
|
702
746
|
const scale = (tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1);
|
|
703
747
|
const tz = Math.round(scale * 100);
|
|
@@ -705,16 +749,13 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
705
749
|
out.push(`${formatNumber(tz)} Tz`);
|
|
706
750
|
lastTz = tz;
|
|
707
751
|
}
|
|
708
|
-
const tc = (tok.
|
|
752
|
+
const tc = tok.font.measure.clustered(tok.text) ? 0 : spacingOf(tok);
|
|
709
753
|
if (tc !== lastTc) {
|
|
710
754
|
out.push(`${formatNumber(tc)} Tc`);
|
|
711
755
|
lastTc = tc;
|
|
712
756
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
|
|
716
|
-
lastColor = tok.resolvedRun.colorHex;
|
|
717
|
-
}
|
|
757
|
+
const fill = fillTo(tok.resolvedRun.colorHex);
|
|
758
|
+
if (fill !== void 0) out.push(fill);
|
|
718
759
|
};
|
|
719
760
|
const emitImageToken = (tok, x, baselineY) => {
|
|
720
761
|
const meta = tok.metafile;
|
|
@@ -1024,12 +1065,32 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1024
1065
|
const hasImageToken = line.tokens.some((t) => t.kind === "image");
|
|
1025
1066
|
const hasMathToken = line.tokens.some((t) => t.kind === "math");
|
|
1026
1067
|
const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
|
|
1027
|
-
const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
|
|
1068
|
+
const hasTab = line.tokens.some((t) => t.kind === "text" && (t.tab === true || t.listMarkerSpace === true));
|
|
1028
1069
|
/** A text token carrying underline or strikethrough. */
|
|
1029
1070
|
const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
|
|
1030
1071
|
const showToken = (tok) => {
|
|
1031
|
-
|
|
1032
|
-
return tok.
|
|
1072
|
+
if (tok.bidiLevel % 2 === 0) return showTextOf(tok, tok.text);
|
|
1073
|
+
return showTextOf(tok, reverseByCodePoint(tok.text), tok.charColorsHex?.slice().reverse());
|
|
1074
|
+
};
|
|
1075
|
+
const actualText = (tokens) => {
|
|
1076
|
+
const open = tokens.map(() => "");
|
|
1077
|
+
const close = tokens.map(() => false);
|
|
1078
|
+
const shaped = (t) => t?.kind === "text" && !t.isSpace && t.tab !== true && t.font.measure.clustered(t.text);
|
|
1079
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1080
|
+
if (!shaped(tokens[i])) continue;
|
|
1081
|
+
let j = i;
|
|
1082
|
+
let text = "";
|
|
1083
|
+
for (let t = tokens[j]; shaped(t); t = tokens[++j]) text += t.text;
|
|
1084
|
+
let hex = "";
|
|
1085
|
+
for (const b of unicodeString(text).bytes) hex += b.toString(16).padStart(2, "0");
|
|
1086
|
+
open[i] = `/Span <</ActualText <${hex.toUpperCase()}>>> BDC`;
|
|
1087
|
+
close[j - 1] = true;
|
|
1088
|
+
i = j - 1;
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
open,
|
|
1092
|
+
close
|
|
1093
|
+
};
|
|
1033
1094
|
};
|
|
1034
1095
|
if (line.tokens.some((t) => t.kind === "text" && t.resolvedRun.shadingColorHex)) {
|
|
1035
1096
|
if (inBT) {
|
|
@@ -1129,13 +1190,31 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1129
1190
|
out.push("BT");
|
|
1130
1191
|
inBT = true;
|
|
1131
1192
|
}
|
|
1193
|
+
const placeGlyph = (m) => `${formatNumber(m[0])} ${formatNumber(-m[1])} ${formatNumber(m[2])} ${formatNumber(-m[3])} ${formatNumber(m[4])} ${formatNumber(H - m[5])} Tm`;
|
|
1194
|
+
const at = actualText(line.tokens);
|
|
1132
1195
|
let cursor = originX;
|
|
1133
|
-
for (
|
|
1196
|
+
for (let ti = 0; ti < line.tokens.length; ti++) {
|
|
1197
|
+
const tok = line.tokens[ti];
|
|
1134
1198
|
if (tok.kind !== "text" || tok.isSpace) {
|
|
1135
1199
|
cursor += tok.widthPt;
|
|
1136
1200
|
continue;
|
|
1137
1201
|
}
|
|
1138
1202
|
switchFontIfNeeded(tok);
|
|
1203
|
+
if (tok.font.measure.clustered(tok.text)) {
|
|
1204
|
+
if (at.open[ti]) out.push(at.open[ti] ?? "");
|
|
1205
|
+
const glyphs = warpGlyphRun(warp, tok.font.measure.glyphRun(tok.text), cursor, tok.widthPt, tok.fontSizePt / tok.font.parsed.unitsPerEm, cmd.baselineY);
|
|
1206
|
+
const colours = tok.charColorsHex;
|
|
1207
|
+
const sources = colours ? tok.font.measure.glyphSources(tok.text) : void 0;
|
|
1208
|
+
for (const g of glyphs) {
|
|
1209
|
+
const fill = sources ? fillTo(colours?.[sources[g.index]] ?? tok.resolvedRun.colorHex) : void 0;
|
|
1210
|
+
if (fill !== void 0) out.push(fill);
|
|
1211
|
+
out.push(placeGlyph(g.matrix));
|
|
1212
|
+
out.push(`<${g.gid.toString(16).padStart(4, "0").toUpperCase()}> Tj`);
|
|
1213
|
+
}
|
|
1214
|
+
if (at.close[ti]) out.push("EMC");
|
|
1215
|
+
cursor += tok.widthPt;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1139
1218
|
const chars = [...tok.text];
|
|
1140
1219
|
const raw = chars.map((c) => tok.font.measure.textWidthPt(c, tok.fontSizePt));
|
|
1141
1220
|
const total = raw.reduce((a, b) => a + b, 0);
|
|
@@ -1145,7 +1224,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1145
1224
|
const m = warpGlyphMatrix(warp, cursor, adv, cmd.baselineY);
|
|
1146
1225
|
cursor += adv;
|
|
1147
1226
|
if (!m) continue;
|
|
1148
|
-
out.push(
|
|
1227
|
+
out.push(placeGlyph(m));
|
|
1149
1228
|
out.push(tok.font.measure.showText(chars[i]));
|
|
1150
1229
|
}
|
|
1151
1230
|
}
|
|
@@ -1160,16 +1239,20 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1160
1239
|
const cos = formatNumber(Math.cos(rad));
|
|
1161
1240
|
const sin = formatNumber(Math.sin(rad));
|
|
1162
1241
|
out.push(`${cos} ${sin} ${formatNumber(-Math.sin(rad))} ${cos} ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
|
|
1163
|
-
|
|
1164
|
-
|
|
1242
|
+
const at = actualText(line.tokens);
|
|
1243
|
+
line.tokens.forEach((tok, i) => {
|
|
1244
|
+
if (tok.kind !== "text") return;
|
|
1165
1245
|
switchFontIfNeeded(tok);
|
|
1166
|
-
out.push(
|
|
1167
|
-
|
|
1246
|
+
if (at.open[i]) out.push(at.open[i] ?? "");
|
|
1247
|
+
out.push(showTextOf(tok, tok.text));
|
|
1248
|
+
if (at.close[i]) out.push("EMC");
|
|
1249
|
+
});
|
|
1168
1250
|
} else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasFauxItalic || hasTab) {
|
|
1169
1251
|
const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
|
|
1252
|
+
const at = actualText(order.map((i) => line.tokens[i]));
|
|
1170
1253
|
let x = originX;
|
|
1171
|
-
for (
|
|
1172
|
-
const tok = line.tokens[
|
|
1254
|
+
for (let oi = 0; oi < order.length; oi++) {
|
|
1255
|
+
const tok = line.tokens[order[oi]];
|
|
1173
1256
|
if (tok.kind === "image") {
|
|
1174
1257
|
flushLink();
|
|
1175
1258
|
emitImageToken(tok, x, baselineY);
|
|
@@ -1189,7 +1272,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1189
1272
|
switchFontIfNeeded(tok);
|
|
1190
1273
|
const slant = tok.synthetic?.italic === true ? FAUX_ITALIC_SHEAR : 0;
|
|
1191
1274
|
out.push(`1 0 ${formatNumber(slant)} 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
|
|
1275
|
+
if (at.open[oi]) out.push(at.open[oi] ?? "");
|
|
1192
1276
|
out.push(showToken(tok));
|
|
1277
|
+
if (at.close[oi]) out.push("EMC");
|
|
1193
1278
|
const tokenX0 = x;
|
|
1194
1279
|
x += tok.widthPt;
|
|
1195
1280
|
if (tok.isSpace) x += extraPerSpace;
|
|
@@ -1202,11 +1287,15 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1202
1287
|
inBT = true;
|
|
1203
1288
|
}
|
|
1204
1289
|
out.push(`1 0 0 1 ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
|
|
1290
|
+
const at = actualText(line.tokens);
|
|
1205
1291
|
let x = originX;
|
|
1206
|
-
for (
|
|
1292
|
+
for (let i = 0; i < line.tokens.length; i++) {
|
|
1293
|
+
const tok = line.tokens[i];
|
|
1207
1294
|
if (tok.kind !== "text") continue;
|
|
1208
1295
|
switchFontIfNeeded(tok);
|
|
1209
|
-
out.push(
|
|
1296
|
+
if (at.open[i]) out.push(at.open[i] ?? "");
|
|
1297
|
+
out.push(showTextOf(tok, tok.text));
|
|
1298
|
+
if (at.close[i]) out.push("EMC");
|
|
1210
1299
|
trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
|
|
1211
1300
|
x += tok.widthPt;
|
|
1212
1301
|
}
|
|
@@ -423,7 +423,7 @@ export type Matrix = readonly [number, number, number, number, number, number];
|
|
|
423
423
|
export declare const IDENTITY: Matrix;
|
|
424
424
|
/** Compose two {@link Matrix matrices}: `a` applied first, then `b`. */
|
|
425
425
|
export declare function multiply(a: Matrix, b: Matrix): Matrix;
|
|
426
|
-
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, PageGradient>, alphas?: ReadonlyMap<string, GsPaint>, spaces?: ReadonlyMap<string, ColorSpaceInfo>, hiddenOc?: ReadonlySet<string>): InterpretResult;
|
|
426
|
+
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, PageGradient>, alphas?: ReadonlyMap<string, GsPaint>, spaces?: ReadonlyMap<string, ColorSpaceInfo>, hiddenOc?: ReadonlySet<string>, actualTexts?: ReadonlyMap<string, string>): InterpretResult;
|
|
427
427
|
/**
|
|
428
428
|
* Whether a string is wholly right-to-left: at least one letter of an RTL
|
|
429
429
|
* script and nothing of any other, spaces and joiners aside.
|
|
@@ -2,6 +2,7 @@ import { PDF_NULL, PdfHexString, PdfName } from "../pdf/objects.js";
|
|
|
2
2
|
import { bidiClass } from "../core/bidi/char-types.js";
|
|
3
3
|
import { reorderVisual } from "../core/bidi/algorithm.js";
|
|
4
4
|
import { analyzeString, hasBidiCharacters } from "../core/bidi/index.js";
|
|
5
|
+
import { pdfTextString } from "./encodings.js";
|
|
5
6
|
import { Lexer } from "./lexer.js";
|
|
6
7
|
import { cmykHex, dashLengths, grayHex, rgbHex, spaceColor } from "./shading.js";
|
|
7
8
|
//#region src/pdf-reader/content.ts
|
|
@@ -184,7 +185,7 @@ function midGradient(gradient) {
|
|
|
184
185
|
function colorOfOperands(operands, space) {
|
|
185
186
|
return spaceColor(operands.filter((o) => typeof o === "number"), space);
|
|
186
187
|
}
|
|
187
|
-
function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map(), hiddenOc = /* @__PURE__ */ new Set()) {
|
|
188
|
+
function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map(), hiddenOc = /* @__PURE__ */ new Set(), actualTexts = /* @__PURE__ */ new Map()) {
|
|
188
189
|
const runs = [];
|
|
189
190
|
const images = [];
|
|
190
191
|
const vectors = [];
|
|
@@ -206,6 +207,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
206
207
|
const ocStack = [];
|
|
207
208
|
let hiddenDepth = 0;
|
|
208
209
|
const visible = () => hiddenDepth === 0;
|
|
210
|
+
let replacing;
|
|
209
211
|
const toPage = (x, y) => [x * state.ctm[0] + y * state.ctm[2] + state.ctm[4], x * state.ctm[1] + y * state.ctm[3] + state.ctm[5]];
|
|
210
212
|
const moveTo = (x, y) => {
|
|
211
213
|
const [px, py] = toPage(x, y);
|
|
@@ -379,17 +381,20 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
379
381
|
if (!set) codesShown.set(state.font, set = /* @__PURE__ */ new Set());
|
|
380
382
|
for (const code of codes) set.add(code);
|
|
381
383
|
}
|
|
382
|
-
return
|
|
384
|
+
return {
|
|
385
|
+
text: state.font.decode(codes),
|
|
386
|
+
glyphs: codes.length
|
|
387
|
+
};
|
|
383
388
|
};
|
|
384
389
|
const emitAt = (origin, shown, end) => {
|
|
385
|
-
const text = logicalOrder(shown);
|
|
386
|
-
if (text.length === 0) return;
|
|
390
|
+
const text = replacing ? "" : logicalOrder(shown.text);
|
|
391
|
+
if (replacing ? shown.glyphs === 0 : text.length === 0) return;
|
|
387
392
|
const scaleY = Math.hypot(origin[2], origin[3]) || 1;
|
|
388
393
|
const scaleX = (Math.hypot(origin[0], origin[1]) || 1) * state.hScale;
|
|
389
394
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
390
395
|
const angle = Math.atan2(origin[1], origin[0]) * 180 / Math.PI;
|
|
391
396
|
if (!visible()) return;
|
|
392
|
-
|
|
397
|
+
const run = {
|
|
393
398
|
text,
|
|
394
399
|
x: origin[4],
|
|
395
400
|
y: origin[5],
|
|
@@ -413,19 +418,36 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
413
418
|
...state.font.italic ? { italic: true } : {},
|
|
414
419
|
colorHex: state.fillGradient ? midGradient(state.fillGradient) : state.fillColor,
|
|
415
420
|
...mcid !== void 0 ? { mcid } : {}
|
|
421
|
+
};
|
|
422
|
+
if (replacing) {
|
|
423
|
+
replacing.first ??= run;
|
|
424
|
+
replacing.endX = end[4];
|
|
425
|
+
replacing.endY = end[5];
|
|
426
|
+
} else runs.push(run);
|
|
427
|
+
};
|
|
428
|
+
const closeReplacement = () => {
|
|
429
|
+
const open = replacing;
|
|
430
|
+
replacing = void 0;
|
|
431
|
+
if (!open?.first || open.text.length === 0) return;
|
|
432
|
+
outlines.splice(open.outlinesFrom);
|
|
433
|
+
runs.push({
|
|
434
|
+
...open.first,
|
|
435
|
+
text: open.text.replace(/[\t\n\v\f\r]/gu, " "),
|
|
436
|
+
endX: open.endX ?? open.first.endX,
|
|
437
|
+
endY: open.endY ?? open.first.endY
|
|
416
438
|
});
|
|
417
439
|
};
|
|
418
440
|
const showString = (operand) => {
|
|
419
441
|
const origin = multiply(tm, state.ctm);
|
|
420
|
-
const
|
|
421
|
-
emitAt(origin,
|
|
442
|
+
const shown = consume(operand);
|
|
443
|
+
emitAt(origin, shown, multiply(tm, state.ctm));
|
|
422
444
|
};
|
|
423
445
|
const showArray = (arr) => {
|
|
424
446
|
for (const el of arr) if (typeof el === "number") tm = multiply(translation(-el / 1e3 * state.fontSize * state.hScale, 0), tm);
|
|
425
447
|
else if (typeof el === "string" || el instanceof PdfHexString) {
|
|
426
448
|
const origin = multiply(tm, state.ctm);
|
|
427
|
-
const
|
|
428
|
-
emitAt(origin,
|
|
449
|
+
const shown = consume(el);
|
|
450
|
+
emitAt(origin, shown, multiply(tm, state.ctm));
|
|
429
451
|
}
|
|
430
452
|
};
|
|
431
453
|
const exec = (op) => {
|
|
@@ -507,20 +529,32 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
507
529
|
case "BDC": {
|
|
508
530
|
const tag = operands[operands.length - 2];
|
|
509
531
|
const props = operands[operands.length - 1];
|
|
510
|
-
const
|
|
511
|
-
|
|
532
|
+
const isArtifact = tag instanceof PdfName && tag.value === "Artifact";
|
|
533
|
+
const mcidVal = !isArtifact && props instanceof Map ? props.get("MCID") : void 0;
|
|
534
|
+
mcStack.push(typeof mcidVal === "number" ? mcidVal : isArtifact ? void 0 : mcStack[mcStack.length - 1]);
|
|
535
|
+
const own = props instanceof Map ? props.get("ActualText") : void 0;
|
|
536
|
+
const actual = typeof own === "string" || own instanceof PdfHexString ? pdfTextString(toBytes(own)) : props instanceof PdfName ? actualTexts.get(props.value) : void 0;
|
|
537
|
+
if (actual !== void 0 && replacing === void 0) replacing = {
|
|
538
|
+
text: actual,
|
|
539
|
+
depth: mcStack.length - 1,
|
|
540
|
+
outlinesFrom: outlines.length
|
|
541
|
+
};
|
|
512
542
|
const isOc = tag instanceof PdfName && tag.value === "OC" && props instanceof PdfName && hiddenOc.has(props.value);
|
|
513
543
|
ocStack.push(isOc);
|
|
514
544
|
if (isOc) hiddenDepth++;
|
|
515
545
|
break;
|
|
516
546
|
}
|
|
517
|
-
case "BMC":
|
|
518
|
-
|
|
547
|
+
case "BMC": {
|
|
548
|
+
const tag = operands[operands.length - 1];
|
|
549
|
+
const isArtifact = tag instanceof PdfName && tag.value === "Artifact";
|
|
550
|
+
mcStack.push(isArtifact ? void 0 : mcStack[mcStack.length - 1]);
|
|
519
551
|
ocStack.push(false);
|
|
520
552
|
break;
|
|
553
|
+
}
|
|
521
554
|
case "EMC":
|
|
522
555
|
mcStack.pop();
|
|
523
556
|
if (ocStack.pop() === true) hiddenDepth--;
|
|
557
|
+
if (replacing && mcStack.length <= replacing.depth) closeReplacement();
|
|
524
558
|
break;
|
|
525
559
|
case "Do": {
|
|
526
560
|
const nm = operands[0];
|
|
@@ -747,6 +781,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
747
781
|
default: operands = [];
|
|
748
782
|
}
|
|
749
783
|
}
|
|
784
|
+
closeReplacement();
|
|
750
785
|
return {
|
|
751
786
|
texts: runs,
|
|
752
787
|
images,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseTtf } from "../core/font/ttf-parser.js";
|
|
2
2
|
import { FontRegistry } from "../core/font/font-registry.js";
|
|
3
|
+
import "../core/font/index.js";
|
|
3
4
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
4
5
|
import { FEATURES } from "../core/ir/features.js";
|
|
5
6
|
//#region src/pdf-reader/embedded-fonts.ts
|
|
@@ -31,3 +31,12 @@ export declare function macGlyphName(index: number): string | undefined;
|
|
|
31
31
|
* @param baseFont The name as the font dictionary states it.
|
|
32
32
|
*/
|
|
33
33
|
export declare function isStandardLatinFace(baseFont: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* §7.9.2.2 — the characters of a text string: UTF-16BE behind its byte-order
|
|
36
|
+
* mark, UTF-8 behind its own (PDF 2.0), and otherwise PDFDocEncoding, one byte
|
|
37
|
+
* to a character.
|
|
38
|
+
*
|
|
39
|
+
* @param bytes The string's bytes, as written literal or in hex.
|
|
40
|
+
* @returns The text it stands for.
|
|
41
|
+
*/
|
|
42
|
+
export declare function pdfTextString(bytes: Uint8Array): string;
|
|
@@ -128,5 +128,70 @@ function macGlyphName(index) {
|
|
|
128
128
|
function isStandardLatinFace(baseFont) {
|
|
129
129
|
return STANDARD_LATIN_FACES.has(baseFont.trim().replace(/^[A-Za-z]{6}\+/u, "").toLowerCase());
|
|
130
130
|
}
|
|
131
|
+
var PDF_DOC = new Map([
|
|
132
|
+
...[
|
|
133
|
+
728,
|
|
134
|
+
711,
|
|
135
|
+
710,
|
|
136
|
+
729,
|
|
137
|
+
733,
|
|
138
|
+
731,
|
|
139
|
+
730,
|
|
140
|
+
732
|
|
141
|
+
].map((cp, i) => [24 + i, cp]),
|
|
142
|
+
...[
|
|
143
|
+
8226,
|
|
144
|
+
8224,
|
|
145
|
+
8225,
|
|
146
|
+
8230,
|
|
147
|
+
8212,
|
|
148
|
+
8211,
|
|
149
|
+
402,
|
|
150
|
+
8260,
|
|
151
|
+
8249,
|
|
152
|
+
8250,
|
|
153
|
+
8722,
|
|
154
|
+
8240,
|
|
155
|
+
8222,
|
|
156
|
+
8220,
|
|
157
|
+
8221,
|
|
158
|
+
8216,
|
|
159
|
+
8217,
|
|
160
|
+
8218,
|
|
161
|
+
8482,
|
|
162
|
+
64257,
|
|
163
|
+
64258,
|
|
164
|
+
321,
|
|
165
|
+
338,
|
|
166
|
+
352,
|
|
167
|
+
376,
|
|
168
|
+
381,
|
|
169
|
+
305,
|
|
170
|
+
322,
|
|
171
|
+
339,
|
|
172
|
+
353,
|
|
173
|
+
382
|
|
174
|
+
].map((cp, i) => [128 + i, cp]),
|
|
175
|
+
[160, 8364]
|
|
176
|
+
]);
|
|
177
|
+
/**
|
|
178
|
+
* §7.9.2.2 — the characters of a text string: UTF-16BE behind its byte-order
|
|
179
|
+
* mark, UTF-8 behind its own (PDF 2.0), and otherwise PDFDocEncoding, one byte
|
|
180
|
+
* to a character.
|
|
181
|
+
*
|
|
182
|
+
* @param bytes The string's bytes, as written literal or in hex.
|
|
183
|
+
* @returns The text it stands for.
|
|
184
|
+
*/
|
|
185
|
+
function pdfTextString(bytes) {
|
|
186
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
187
|
+
let out = "";
|
|
188
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) out += String.fromCharCode(bytes[i] << 8 | bytes[i + 1]);
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
if (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
192
|
+
let out = "";
|
|
193
|
+
for (const b of bytes) out += String.fromCodePoint(PDF_DOC.get(b) ?? b);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
131
196
|
//#endregion
|
|
132
|
-
export { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName };
|
|
197
|
+
export { baseEncodingTable, isStandardLatinFace, macGlyphName, pdfTextString, standardEncodingTable, winAnsiLatinName };
|
|
@@ -16,6 +16,16 @@ import { PdfFile, PdfPage } from './document.js';
|
|
|
16
16
|
* @returns The page's runs, each carrying an `href` when it sits under a link.
|
|
17
17
|
*/
|
|
18
18
|
export declare function extractPageText(file: PdfFile, page: PdfPage, painted?: ShownCodes): Array<TextRun>;
|
|
19
|
+
/**
|
|
20
|
+
* §14.9.4 — the replacement text of each property list the page's
|
|
21
|
+
* `/Properties` names: the content says `/Span /P1 BDC` and the `/ActualText`
|
|
22
|
+
* is in the resources, not in the stream.
|
|
23
|
+
*
|
|
24
|
+
* @param file The owning file.
|
|
25
|
+
* @param resources The resource dictionary in force.
|
|
26
|
+
* @returns Name → the text, for the lists that hold one.
|
|
27
|
+
*/
|
|
28
|
+
export declare function actualTextsOf(file: PdfFile, resources: PdfDict | undefined): Map<string, string>;
|
|
19
29
|
/**
|
|
20
30
|
* The `/Font` resources of one dictionary, built into interpreter fonts. Shared
|
|
21
31
|
* with the path and picture walks, which need them for one thing only: a Type 3
|