reamkit 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/esm/pdf-reader/annot-draw.d.ts +65 -0
- package/dist/esm/pdf-reader/annot-draw.js +374 -0
- package/dist/esm/pdf-reader/annots.d.ts +3 -1
- package/dist/esm/pdf-reader/annots.js +18 -4
- package/dist/esm/pdf-reader/ccitt.d.ts +18 -0
- package/dist/esm/pdf-reader/ccitt.js +70 -2
- package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
- package/dist/esm/pdf-reader/cie-color.js +112 -0
- package/dist/esm/pdf-reader/content.d.ts +47 -3
- package/dist/esm/pdf-reader/content.js +108 -13
- package/dist/esm/pdf-reader/display.d.ts +1 -1
- package/dist/esm/pdf-reader/display.js +21 -6
- package/dist/esm/pdf-reader/document.d.ts +6 -0
- package/dist/esm/pdf-reader/document.js +29 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +12 -3
- package/dist/esm/pdf-reader/embedded-fonts.js +22 -3
- package/dist/esm/pdf-reader/flow-build.d.ts +12 -4
- package/dist/esm/pdf-reader/flow-build.js +102 -8
- package/dist/esm/pdf-reader/font.js +77 -16
- package/dist/esm/pdf-reader/function.d.ts +16 -0
- package/dist/esm/pdf-reader/function.js +414 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +8 -4
- package/dist/esm/pdf-reader/image-decode.js +192 -11
- package/dist/esm/pdf-reader/images.d.ts +12 -0
- package/dist/esm/pdf-reader/images.js +109 -9
- package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
- package/dist/esm/pdf-reader/jbig2.js +23 -14
- package/dist/esm/pdf-reader/layout.d.ts +32 -0
- package/dist/esm/pdf-reader/layout.js +189 -40
- package/dist/esm/pdf-reader/lexer.d.ts +2 -0
- package/dist/esm/pdf-reader/lexer.js +4 -0
- package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
- package/dist/esm/pdf-reader/optional-content.js +93 -0
- package/dist/esm/pdf-reader/reader.d.ts +5 -2
- package/dist/esm/pdf-reader/reader.js +80 -7
- package/dist/esm/pdf-reader/shading.d.ts +60 -8
- package/dist/esm/pdf-reader/shading.js +124 -20
- package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
- package/dist/esm/pdf-reader/standard-metrics.js +18 -0
- package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
- package/dist/esm/pdf-reader/standard-widths.js +62 -0
- package/dist/esm/pdf-reader/tagged.js +204 -32
- package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
- package/dist/esm/pdf-reader/text-rules.js +112 -0
- package/dist/esm/pdf-reader/text.js +78 -4
- package/dist/esm/pdf-reader/vector.d.ts +5 -0
- package/dist/esm/pdf-reader/vector.js +39 -26
- package/dist/esm/word/docx-writer.js +73 -11
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/pdf-reader/cie-color.ts
|
|
2
|
+
/**
|
|
3
|
+
* One colour in a CIE-based space, as sRGB in 0..1.
|
|
4
|
+
*
|
|
5
|
+
* @param space The space's own parameters.
|
|
6
|
+
* @param abc Its components: one for a grey space, three for an RGB one.
|
|
7
|
+
* @returns The sRGB triple a viewer shows for it.
|
|
8
|
+
*/
|
|
9
|
+
function cieToSrgb(space, abc) {
|
|
10
|
+
const clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
11
|
+
if (space.matrix === void 0 || abc.length < 3) {
|
|
12
|
+
const v = srgbTransfer(clamp01(abc[0] ?? 0) ** (space.gamma[0] ?? 1));
|
|
13
|
+
return [
|
|
14
|
+
v,
|
|
15
|
+
v,
|
|
16
|
+
v
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
const m = space.matrix;
|
|
20
|
+
const a = clamp01(abc[0]) ** (space.gamma[0] ?? 1);
|
|
21
|
+
const b = clamp01(abc[1]) ** (space.gamma[1] ?? 1);
|
|
22
|
+
const c = clamp01(abc[2]) ** (space.gamma[2] ?? 1);
|
|
23
|
+
const [ax, ay, az] = adaptToD65((m[0] ?? 0) * a + (m[3] ?? 0) * b + (m[6] ?? 0) * c, (m[1] ?? 0) * a + (m[4] ?? 0) * b + (m[7] ?? 0) * c, (m[2] ?? 0) * a + (m[5] ?? 0) * b + (m[8] ?? 0) * c, space.white);
|
|
24
|
+
const r = 3.2404542 * ax - 1.5371385 * ay - .4985314 * az;
|
|
25
|
+
const g = -.969266 * ax + 1.8760108 * ay + .041556 * az;
|
|
26
|
+
const bl = .0556434 * ax - .2040259 * ay + 1.0572252 * az;
|
|
27
|
+
return [
|
|
28
|
+
srgbTransfer(clamp01(r)),
|
|
29
|
+
srgbTransfer(clamp01(g)),
|
|
30
|
+
srgbTransfer(clamp01(bl))
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* §8.6.5.8 — one colour in a `Lab` space, as sRGB in 0..1.
|
|
35
|
+
*
|
|
36
|
+
* Lab is the one CIE space a file states in absolute terms: `L*` from 0 to 100
|
|
37
|
+
* is lightness and `a*`/`b*` are the two opponent axes, and unlike `CalRGB`
|
|
38
|
+
* (see the note above) there is no argument about the way out — every renderer
|
|
39
|
+
* takes it through XYZ against the stated white and adapts to the screen's.
|
|
40
|
+
* issue10339_reduced.pdf paints two grids of blue swatches through an `Indexed`
|
|
41
|
+
* palette whose base is one, and read as anything else the page came back
|
|
42
|
+
* blank.
|
|
43
|
+
*
|
|
44
|
+
* @param white The space's `/WhitePoint`, in XYZ.
|
|
45
|
+
* @param lab `L*`, `a*`, `b*`.
|
|
46
|
+
* @returns The sRGB triple a viewer shows for it.
|
|
47
|
+
*/
|
|
48
|
+
function labToSrgb(white, lab) {
|
|
49
|
+
const m = (Math.min(100, Math.max(0, lab[0])) + 16) / 116;
|
|
50
|
+
const l = m + lab[1] / 500;
|
|
51
|
+
const n = m - lab[2] / 200;
|
|
52
|
+
const [ax, ay, az] = adaptToD65(white[0] * inverseTransfer(l), white[1] * inverseTransfer(m), white[2] * inverseTransfer(n), white);
|
|
53
|
+
const r = 3.2404542 * ax - 1.5371385 * ay - .4985314 * az;
|
|
54
|
+
const g = -.969266 * ax + 1.8760108 * ay + .041556 * az;
|
|
55
|
+
const b = .0556434 * ax - .2040259 * ay + 1.0572252 * az;
|
|
56
|
+
const clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
57
|
+
return [
|
|
58
|
+
srgbTransfer(clamp01(r)),
|
|
59
|
+
srgbTransfer(clamp01(g)),
|
|
60
|
+
srgbTransfer(clamp01(b))
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
/** §8.6.5.8 — the `g` of the Lab definition, which is a cube with a linear toe. */
|
|
64
|
+
function inverseTransfer(v) {
|
|
65
|
+
return v >= 6 / 29 ? v ** 3 : 108 / 841 * (v - 4 / 29);
|
|
66
|
+
}
|
|
67
|
+
/** The D65 white every sRGB screen is referred to. */
|
|
68
|
+
var D65 = [
|
|
69
|
+
.9505,
|
|
70
|
+
1,
|
|
71
|
+
1.089
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* Bradford chromatic adaptation from the space's own white to D65.
|
|
75
|
+
*
|
|
76
|
+
* A file may state any illuminant — calrgb.pdf states several — and a colour
|
|
77
|
+
* measured against one white shown against another is the wrong colour. The
|
|
78
|
+
* Bradford transform is the one every colour-managed renderer uses for this.
|
|
79
|
+
*/
|
|
80
|
+
function adaptToD65(x, y, z, white) {
|
|
81
|
+
const [sx, sy, sz] = coneResponse(white[0], white[1], white[2]);
|
|
82
|
+
const [dx, dy, dz] = coneResponse(D65[0], D65[1], D65[2]);
|
|
83
|
+
if (sx === 0 || sy === 0 || sz === 0) return [
|
|
84
|
+
x,
|
|
85
|
+
y,
|
|
86
|
+
z
|
|
87
|
+
];
|
|
88
|
+
const [cx, cy, cz] = coneResponse(x, y, z);
|
|
89
|
+
return fromConeResponse(cx * dx / sx, cy * dy / sy, cz * dz / sz);
|
|
90
|
+
}
|
|
91
|
+
/** XYZ into the Bradford cone-response space. */
|
|
92
|
+
function coneResponse(x, y, z) {
|
|
93
|
+
return [
|
|
94
|
+
.8951 * x + .2664 * y - .1614 * z,
|
|
95
|
+
-.7502 * x + 1.7135 * y + .0367 * z,
|
|
96
|
+
.0389 * x - .0685 * y + 1.0296 * z
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
/** …and back out of it. */
|
|
100
|
+
function fromConeResponse(l, m, s) {
|
|
101
|
+
return [
|
|
102
|
+
.9869929 * l - .1470543 * m + .1599627 * s,
|
|
103
|
+
.4323053 * l + .5183603 * m + .0492912 * s,
|
|
104
|
+
-.0085287 * l + .0400428 * m + .9684867 * s
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
/** IEC 61966-2-1 — linear light to the sRGB signal a screen takes. */
|
|
108
|
+
function srgbTransfer(v) {
|
|
109
|
+
return v <= .0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - .055;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { cieToSrgb, labToSrgb };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ColorSpaceInfo } from './shading.js';
|
|
1
|
+
import { ColorSpaceInfo, GsPaint } from './shading.js';
|
|
2
|
+
import { TextMarkup } from './annot-draw.js';
|
|
2
3
|
import { ShapeGradient } from '../core/vector.js';
|
|
3
4
|
import { PdfDict, PdfStream } from '../pdf/objects.js';
|
|
4
5
|
/**
|
|
@@ -72,8 +73,21 @@ export interface TextRun {
|
|
|
72
73
|
* than in a substitute (see `./embedded-fonts`).
|
|
73
74
|
*/
|
|
74
75
|
readonly fontName?: string;
|
|
76
|
+
/**
|
|
77
|
+
* §9.4.4 — the advance of this face's SPACE at the size the run was shown
|
|
78
|
+
* at, which is what says whether a gap between two runs was a word space.
|
|
79
|
+
* Absent where the face states no width for it.
|
|
80
|
+
*/
|
|
81
|
+
readonly spaceWidthPt?: number;
|
|
75
82
|
/** §9.8.1 — the face the glyphs were shown in is a bold one. */
|
|
76
83
|
readonly bold?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* §12.5.6.10 — a text-markup annotation marks these glyphs: highlighted,
|
|
86
|
+
* underlined, struck through. Applied after extraction, from the page's
|
|
87
|
+
* `/Annots` (see `./text`), because it is stated about the words rather than
|
|
88
|
+
* painted among them.
|
|
89
|
+
*/
|
|
90
|
+
readonly markup?: TextMarkup;
|
|
77
91
|
/**
|
|
78
92
|
* §8.6.6.2 — the glyphs are filled with a tiling PATTERN, named here for the
|
|
79
93
|
* caller to resolve: a pattern is a content stream, not a colour, and the
|
|
@@ -120,13 +134,33 @@ export interface TextRun {
|
|
|
120
134
|
* square to page space, so it carries both the placement and the size; the
|
|
121
135
|
* `mcid` links the paint to its structure element (a `/Figure`, E-PDF EP6).
|
|
122
136
|
*/
|
|
137
|
+
/** §8.9.7 — an image written into the content stream rather than named. */
|
|
138
|
+
export interface InlineImage {
|
|
139
|
+
/** Its dictionary, keys as written — the abbreviated ones (`/W`, `/CS`, `/F`). */
|
|
140
|
+
readonly dict: PdfDict;
|
|
141
|
+
/** Its bytes, still filtered as the dictionary says. */
|
|
142
|
+
readonly data: Uint8Array;
|
|
143
|
+
}
|
|
123
144
|
export interface ImagePlacement {
|
|
124
145
|
/** Where the `Do` fell in the stream's painting order — see {@link VectorPlacement.order}. */
|
|
125
146
|
readonly order: number;
|
|
126
|
-
/** XObject resource name (no leading slash). */
|
|
147
|
+
/** XObject resource name (no leading slash), empty for an inline image. */
|
|
127
148
|
readonly name: string;
|
|
149
|
+
/** §8.9.7 — the image itself, where it was written into the stream. */
|
|
150
|
+
readonly inline?: InlineImage;
|
|
128
151
|
readonly ctm: Matrix;
|
|
152
|
+
/** §8.5.4 — the clip in force when it was painted, when there was one. */
|
|
153
|
+
readonly clip?: ClipRegion;
|
|
154
|
+
/**
|
|
155
|
+
* §8.9.6.2 — the non-stroking colour in force. A stencil `/ImageMask` carries
|
|
156
|
+
* no colour of its own: it says only WHERE to paint, and this is what.
|
|
157
|
+
*/
|
|
158
|
+
readonly fillHex: string;
|
|
129
159
|
readonly mcid?: number;
|
|
160
|
+
/** §11.3.5 `/BM` — a blend the page asked for that nothing here performs. */
|
|
161
|
+
readonly blend?: string;
|
|
162
|
+
/** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */
|
|
163
|
+
readonly masked?: boolean;
|
|
130
164
|
}
|
|
131
165
|
/**
|
|
132
166
|
* One segment of a painted path (E-PDF EP10/EP11), in page space (y-up): a
|
|
@@ -178,6 +212,16 @@ export interface VectorPlacement {
|
|
|
178
212
|
readonly gradient?: ShapeGradient;
|
|
179
213
|
/** §11.6.4.4 `/ca` — how opaque the fill is, when the page asked for less. */
|
|
180
214
|
readonly alpha?: number;
|
|
215
|
+
/**
|
|
216
|
+
* §11.3.5 `/BM` — the fill only DARKENS what it covers (`Multiply`,
|
|
217
|
+
* `Darken`), so the marks under it read through. A highlighter is this and
|
|
218
|
+
* nothing else.
|
|
219
|
+
*/
|
|
220
|
+
readonly darkens?: boolean;
|
|
221
|
+
/** §11.3.5 `/BM` — a blend the page asked for that nothing here performs. */
|
|
222
|
+
readonly blend?: string;
|
|
223
|
+
/** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */
|
|
224
|
+
readonly masked?: boolean;
|
|
181
225
|
/**
|
|
182
226
|
* §8.7.3 — the TILING pattern resource name the path is filled with. Its
|
|
183
227
|
* content is a stream of its own, so what the fill actually shows is only
|
|
@@ -222,7 +266,7 @@ export type Matrix = readonly [number, number, number, number, number, number];
|
|
|
222
266
|
export declare const IDENTITY: Matrix;
|
|
223
267
|
/** Compose two {@link Matrix matrices}: `a` applied first, then `b`. */
|
|
224
268
|
export declare function multiply(a: Matrix, b: Matrix): Matrix;
|
|
225
|
-
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, ShapeGradient>, alphas?: ReadonlyMap<string,
|
|
269
|
+
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, ShapeGradient>, alphas?: ReadonlyMap<string, GsPaint>, spaces?: ReadonlyMap<string, ColorSpaceInfo>, hiddenOc?: ReadonlySet<string>): InterpretResult;
|
|
226
270
|
/**
|
|
227
271
|
* Whether a string is wholly right-to-left: at least one letter of an RTL
|
|
228
272
|
* script and nothing of any other, spaces and joiners aside.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PDF_NULL, PdfHexString, PdfName } from "../pdf/objects.js";
|
|
2
2
|
import { Lexer } from "./lexer.js";
|
|
3
|
+
import { cieToSrgb } from "./cie-color.js";
|
|
3
4
|
//#region src/pdf-reader/content.ts
|
|
4
5
|
/**
|
|
5
6
|
* A painted path emitted by the path-painting operators (§8.5.3), captured in
|
|
@@ -91,6 +92,9 @@ function initialState() {
|
|
|
91
92
|
fillGradient: void 0,
|
|
92
93
|
fillPattern: void 0,
|
|
93
94
|
fillAlpha: 1,
|
|
95
|
+
fillDarkens: false,
|
|
96
|
+
blendMode: void 0,
|
|
97
|
+
softMask: false,
|
|
94
98
|
clip: void 0
|
|
95
99
|
};
|
|
96
100
|
}
|
|
@@ -143,15 +147,24 @@ function spaceOf(operands, spaces) {
|
|
|
143
147
|
function componentColor(operands, space) {
|
|
144
148
|
const nums = operands.filter((o) => typeof o === "number");
|
|
145
149
|
if (nums.length === 0) return void 0;
|
|
150
|
+
if (space?.cie) {
|
|
151
|
+
const [r, g, b] = cieToSrgb(space.cie, nums);
|
|
152
|
+
return rgbHex(r, g, b);
|
|
153
|
+
}
|
|
146
154
|
switch (space?.kind ?? (nums.length === 3 ? "rgb" : nums.length === 4 ? "cmyk" : void 0)) {
|
|
147
155
|
case "rgb": return nums.length >= 3 ? rgbHex(nums[0], nums[1], nums[2]) : void 0;
|
|
148
156
|
case "cmyk": return nums.length >= 4 ? cmykHex(nums[0], nums[1], nums[2], nums[3]) : void 0;
|
|
149
157
|
case "gray": return grayHex(nums[0]);
|
|
150
|
-
case "tint":
|
|
158
|
+
case "tint":
|
|
159
|
+
if (space?.tint) {
|
|
160
|
+
const hex = componentColor(space.tint.transform(nums), space.tint.alternate);
|
|
161
|
+
if (hex !== void 0) return hex;
|
|
162
|
+
}
|
|
163
|
+
return grayHex(1 - Math.max(...nums));
|
|
151
164
|
default: return;
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
|
-
function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map()) {
|
|
167
|
+
function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map(), hiddenOc = /* @__PURE__ */ new Set()) {
|
|
155
168
|
const runs = [];
|
|
156
169
|
const images = [];
|
|
157
170
|
const vectors = [];
|
|
@@ -167,6 +180,9 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
167
180
|
let paintOrder = 0;
|
|
168
181
|
let operands = [];
|
|
169
182
|
const mcStack = [];
|
|
183
|
+
const ocStack = [];
|
|
184
|
+
let hiddenDepth = 0;
|
|
185
|
+
const visible = () => hiddenDepth === 0;
|
|
170
186
|
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]];
|
|
171
187
|
const moveTo = (x, y) => {
|
|
172
188
|
const [px, py] = toPage(x, y);
|
|
@@ -211,7 +227,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
211
227
|
return state.lineWidth * scale;
|
|
212
228
|
};
|
|
213
229
|
const paintPath = (fill, stroke) => {
|
|
214
|
-
if (path.length >= 2 && (fill || stroke)) {
|
|
230
|
+
if (path.length >= 2 && (fill || stroke) && visible()) {
|
|
215
231
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
216
232
|
vectors.push({
|
|
217
233
|
order: paintOrder++,
|
|
@@ -220,6 +236,9 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
220
236
|
...fill && state.fillPattern !== void 0 ? { patternName: state.fillPattern } : {},
|
|
221
237
|
...fill ? { fillHex: state.fillColor } : {},
|
|
222
238
|
...fill && state.fillAlpha < 1 ? { alpha: state.fillAlpha } : {},
|
|
239
|
+
...fill && state.fillDarkens ? { darkens: true } : {},
|
|
240
|
+
...state.blendMode !== void 0 ? { blend: state.blendMode } : {},
|
|
241
|
+
...state.softMask ? { masked: true } : {},
|
|
223
242
|
...fill && state.fillGradient ? { gradient: state.fillGradient } : {},
|
|
224
243
|
...stroke ? {
|
|
225
244
|
strokeHex: state.strokeColor,
|
|
@@ -249,7 +268,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
249
268
|
const type3 = state.font.type3;
|
|
250
269
|
if (type3) {
|
|
251
270
|
const stream = type3.proc(code);
|
|
252
|
-
if (stream) {
|
|
271
|
+
if (stream && visible()) {
|
|
253
272
|
const scale = [
|
|
254
273
|
state.fontSize * state.hScale,
|
|
255
274
|
0,
|
|
@@ -279,8 +298,10 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
279
298
|
const text = logicalOrder(shown);
|
|
280
299
|
if (text.length === 0) return;
|
|
281
300
|
const scaleY = Math.hypot(origin[2], origin[3]) || 1;
|
|
301
|
+
const scaleX = (Math.hypot(origin[0], origin[1]) || 1) * state.hScale;
|
|
282
302
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
283
303
|
const angle = Math.atan2(origin[1], origin[0]) * 180 / Math.PI;
|
|
304
|
+
if (!visible()) return;
|
|
284
305
|
runs.push({
|
|
285
306
|
text,
|
|
286
307
|
x: origin[4],
|
|
@@ -288,7 +309,8 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
288
309
|
endX: end[4],
|
|
289
310
|
endY: end[5],
|
|
290
311
|
...Math.abs(angle) > UPRIGHT_TOLERANCE_DEG ? { angleDeg: angle } : {},
|
|
291
|
-
fontSizePt: state.fontSize * scaleY,
|
|
312
|
+
fontSizePt: Math.abs(state.fontSize) * scaleY,
|
|
313
|
+
spaceWidthPt: (state.font.width(32) / 1e3 * state.fontSize + state.charSpacing + state.wordSpacing) * scaleX,
|
|
292
314
|
fontKey: state.fontKey,
|
|
293
315
|
...state.font.name !== void 0 ? { fontName: state.font.name } : {},
|
|
294
316
|
...state.font.type3 ? { type3: true } : {},
|
|
@@ -392,23 +414,31 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
392
414
|
const props = operands[operands.length - 1];
|
|
393
415
|
const mcidVal = !(tag instanceof PdfName && tag.value === "Artifact") && props instanceof Map ? props.get("MCID") : void 0;
|
|
394
416
|
mcStack.push(typeof mcidVal === "number" ? mcidVal : void 0);
|
|
417
|
+
const isOc = tag instanceof PdfName && tag.value === "OC" && props instanceof PdfName && hiddenOc.has(props.value);
|
|
418
|
+
ocStack.push(isOc);
|
|
419
|
+
if (isOc) hiddenDepth++;
|
|
395
420
|
break;
|
|
396
421
|
}
|
|
397
422
|
case "BMC":
|
|
398
423
|
mcStack.push(void 0);
|
|
424
|
+
ocStack.push(false);
|
|
399
425
|
break;
|
|
400
426
|
case "EMC":
|
|
401
427
|
mcStack.pop();
|
|
428
|
+
if (ocStack.pop() === true) hiddenDepth--;
|
|
402
429
|
break;
|
|
403
430
|
case "Do": {
|
|
404
431
|
const nm = operands[0];
|
|
405
|
-
if (nm instanceof PdfName) {
|
|
432
|
+
if (nm instanceof PdfName && visible()) {
|
|
406
433
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
407
434
|
images.push({
|
|
408
435
|
order: paintOrder++,
|
|
409
436
|
name: nm.value,
|
|
410
437
|
ctm: state.ctm,
|
|
411
|
-
...
|
|
438
|
+
...state.clip ? { clip: state.clip } : {},
|
|
439
|
+
fillHex: state.fillColor,
|
|
440
|
+
...mcid !== void 0 ? { mcid } : {},
|
|
441
|
+
...state.blendMode !== void 0 ? { blend: state.blendMode } : {}
|
|
412
442
|
});
|
|
413
443
|
}
|
|
414
444
|
break;
|
|
@@ -511,7 +541,13 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
511
541
|
break;
|
|
512
542
|
case "gs": {
|
|
513
543
|
const nm = operands[operands.length - 1];
|
|
514
|
-
if (nm instanceof PdfName)
|
|
544
|
+
if (nm instanceof PdfName) {
|
|
545
|
+
const paint = alphas.get(nm.value);
|
|
546
|
+
state.fillAlpha = paint?.alpha ?? 1;
|
|
547
|
+
state.fillDarkens = paint?.darkens ?? false;
|
|
548
|
+
state.blendMode = paint?.blend;
|
|
549
|
+
state.softMask = paint?.masked ?? false;
|
|
550
|
+
}
|
|
515
551
|
break;
|
|
516
552
|
}
|
|
517
553
|
case "W":
|
|
@@ -545,8 +581,19 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
545
581
|
operands.push(readDict(lexer));
|
|
546
582
|
break;
|
|
547
583
|
case "keyword":
|
|
548
|
-
if (tok.value === "BI")
|
|
549
|
-
|
|
584
|
+
if (tok.value === "BI") {
|
|
585
|
+
const inline = readInlineImage(lexer);
|
|
586
|
+
if (inline && visible()) images.push({
|
|
587
|
+
order: paintOrder++,
|
|
588
|
+
name: "",
|
|
589
|
+
inline,
|
|
590
|
+
ctm: state.ctm,
|
|
591
|
+
...state.clip ? { clip: state.clip } : {},
|
|
592
|
+
fillHex: state.fillColor,
|
|
593
|
+
...mcStack.length > 0 ? { mcid: mcStack[mcStack.length - 1] } : {},
|
|
594
|
+
...state.blendMode !== void 0 ? { blend: state.blendMode } : {}
|
|
595
|
+
});
|
|
596
|
+
} else exec(tok.value);
|
|
550
597
|
operands = [];
|
|
551
598
|
break;
|
|
552
599
|
default:
|
|
@@ -687,14 +734,62 @@ function readValue(lexer) {
|
|
|
687
734
|
default: return PDF_NULL;
|
|
688
735
|
}
|
|
689
736
|
}
|
|
690
|
-
|
|
737
|
+
/**
|
|
738
|
+
* §8.9.7 — an inline image: its dictionary between `BI` and `ID`, then its
|
|
739
|
+
* bytes up to `EI`.
|
|
740
|
+
*
|
|
741
|
+
* The bytes are binary and may hold `EI` themselves, so the end is found by
|
|
742
|
+
* MEASURING where the dictionary says how much there is — an unfiltered image
|
|
743
|
+
* is exactly `ceil(W · BPC · components / 8) · H` bytes — and only searched for
|
|
744
|
+
* where a filter makes the length unknowable. images_1bit_grayscale.pdf draws
|
|
745
|
+
* two of them and both were skipped over.
|
|
746
|
+
*/
|
|
747
|
+
function readInlineImage(lexer) {
|
|
748
|
+
const dict = /* @__PURE__ */ new Map();
|
|
691
749
|
for (;;) {
|
|
692
750
|
const tok = lexer.nextToken();
|
|
693
|
-
if (tok.kind === "eof") return;
|
|
751
|
+
if (tok.kind === "eof") return void 0;
|
|
694
752
|
if (tok.kind === "keyword" && tok.value === "ID") break;
|
|
753
|
+
if (tok.kind === "name") dict.set(tok.value, readValue(lexer));
|
|
695
754
|
}
|
|
696
|
-
const
|
|
755
|
+
const start = lexer.pos + 1;
|
|
756
|
+
const measured = inlineLength(dict);
|
|
757
|
+
let end = measured !== void 0 ? start + measured : -1;
|
|
758
|
+
if (end < 0 || end > lexer.length) {
|
|
759
|
+
end = lexer.indexOfAscii("EI", start);
|
|
760
|
+
if (end < 0) {
|
|
761
|
+
lexer.pos = lexer.length;
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const data = lexer.slice(start, Math.min(end, lexer.length));
|
|
766
|
+
const ei = lexer.indexOfAscii("EI", end);
|
|
697
767
|
lexer.pos = ei < 0 ? lexer.length : ei + 2;
|
|
768
|
+
return {
|
|
769
|
+
dict,
|
|
770
|
+
data
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
/** How many bytes an UNFILTERED inline image's samples take, if that is known. */
|
|
774
|
+
function inlineLength(dict) {
|
|
775
|
+
if (dict.has("F") || dict.has("Filter")) return void 0;
|
|
776
|
+
const num = (...keys) => {
|
|
777
|
+
for (const k of keys) {
|
|
778
|
+
const v = dict.get(k);
|
|
779
|
+
if (typeof v === "number") return v;
|
|
780
|
+
}
|
|
781
|
+
return 0;
|
|
782
|
+
};
|
|
783
|
+
const w = num("W", "Width");
|
|
784
|
+
const h = num("H", "Height");
|
|
785
|
+
if (w <= 0 || h <= 0) return void 0;
|
|
786
|
+
const mask = dict.get("IM") === true || dict.get("ImageMask") === true;
|
|
787
|
+
const bpc = mask ? 1 : num("BPC", "BitsPerComponent") || 8;
|
|
788
|
+
const cs = dict.get("CS") ?? dict.get("ColorSpace");
|
|
789
|
+
const name = cs instanceof PdfName ? cs.value : "";
|
|
790
|
+
const comps = mask ? 1 : /^(RGB|DeviceRGB|CalRGB)$/u.test(name) ? 3 : /^(CMYK|DeviceCMYK)$/u.test(name) ? 4 : /^(G|DeviceGray|CalGray|I|Indexed)$/u.test(name) || name === "" ? 1 : 0;
|
|
791
|
+
if (comps === 0) return void 0;
|
|
792
|
+
return ((w * comps * bpc + 7) / 8 | 0) * h;
|
|
698
793
|
}
|
|
699
794
|
//#endregion
|
|
700
795
|
export { IDENTITY, interpretContent, isRightToLeft, multiply };
|
|
@@ -17,7 +17,7 @@ export interface Display {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* The {@link Display} a page's
|
|
20
|
+
* The {@link Display} a page's shown box and `/Rotate` describe.
|
|
21
21
|
*
|
|
22
22
|
* `/Rotate` turns the page CLOCKWISE when shown, so the content turns with it —
|
|
23
23
|
* counter-clockwise by the same amount as seen from the content's own frame,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/pdf-reader/display.ts
|
|
2
2
|
/**
|
|
3
|
-
* The {@link Display} a page's
|
|
3
|
+
* The {@link Display} a page's shown box and `/Rotate` describe.
|
|
4
4
|
*
|
|
5
5
|
* `/Rotate` turns the page CLOCKWISE when shown, so the content turns with it —
|
|
6
6
|
* counter-clockwise by the same amount as seen from the content's own frame,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @returns Its size, its turn, and the map onto it.
|
|
11
11
|
*/
|
|
12
12
|
function displayOf(page) {
|
|
13
|
-
const [x0, y0, x1, y1] = page.
|
|
13
|
+
const [x0, y0, x1, y1] = page.cropBox;
|
|
14
14
|
const left = Math.min(x0, x1);
|
|
15
15
|
const bottom = Math.min(y0, y1);
|
|
16
16
|
const w = Math.abs(x1 - x0);
|
|
@@ -66,7 +66,20 @@ function placeRuns(runs, d) {
|
|
|
66
66
|
endY: end.y,
|
|
67
67
|
...Math.abs(angle) > .5 ? { angleDeg: angle } : {}
|
|
68
68
|
};
|
|
69
|
-
});
|
|
69
|
+
}).filter((r) => shows(d, Math.min(r.x, r.endX), Math.min(r.y, r.endY) - r.fontSizePt * .25, Math.max(r.x, r.endX), Math.max(r.y, r.endY) + r.fontSizePt * .85));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* §14.11.2 — whether a box falls inside the region the page SHOWS.
|
|
73
|
+
*
|
|
74
|
+
* The crop box is the region "to which the contents of the page shall be
|
|
75
|
+
* clipped", and a page cropped to less than its sheet is generally a page with
|
|
76
|
+
* something outside the crop: freeculture.pdf carries a printer's slug —
|
|
77
|
+
* "14773_07_347-348_r4jm.qxd 2/10/04 4:45 PM Page 347" — in the 42 points the
|
|
78
|
+
* crop cuts off the top, and lifted with the rest it stood at the head of the
|
|
79
|
+
* page and pushed the sheet taller to hold it.
|
|
80
|
+
*/
|
|
81
|
+
function shows(d, minX, minY, maxX, maxY) {
|
|
82
|
+
return maxX > 0 && minX < d.width && maxY > 0 && minY < d.height;
|
|
70
83
|
}
|
|
71
84
|
/**
|
|
72
85
|
* The same pictures, placed on the shown page. A quarter turn swaps a picture's
|
|
@@ -89,14 +102,16 @@ function placeImages(images, d) {
|
|
|
89
102
|
const ys = corners.map((c) => c.y);
|
|
90
103
|
const minX = Math.min(...xs);
|
|
91
104
|
const minY = Math.min(...ys);
|
|
105
|
+
const turned = (img.rotationDeg ?? 0) + d.turnDeg;
|
|
92
106
|
return {
|
|
93
107
|
...img,
|
|
94
108
|
x: minX,
|
|
95
109
|
y: minY,
|
|
96
110
|
widthPt: Math.max(...xs) - minX,
|
|
97
|
-
heightPt: Math.max(...ys) - minY
|
|
111
|
+
heightPt: Math.max(...ys) - minY,
|
|
112
|
+
...Math.abs(turned) > .5 ? { rotationDeg: turned } : {}
|
|
98
113
|
};
|
|
99
|
-
});
|
|
114
|
+
}).filter((img) => shows(d, img.x, img.y, img.x + img.widthPt, img.y + img.heightPt));
|
|
100
115
|
}
|
|
101
116
|
/**
|
|
102
117
|
* The same paths, placed on the shown page. A path is geometry, so every point
|
|
@@ -156,7 +171,7 @@ function placeVectors(vectors, d) {
|
|
|
156
171
|
maxX: Math.max(...xs),
|
|
157
172
|
maxY: Math.max(...ys)
|
|
158
173
|
};
|
|
159
|
-
});
|
|
174
|
+
}).filter((v) => shows(d, v.minX, v.minY, v.maxX, v.maxY));
|
|
160
175
|
}
|
|
161
176
|
//#endregion
|
|
162
177
|
export { displayOf, placeImages, placeRuns, placeVectors };
|
|
@@ -5,6 +5,12 @@ export type Rectangle = readonly [number, number, number, number];
|
|
|
5
5
|
export interface PdfPage {
|
|
6
6
|
readonly dict: PdfDict;
|
|
7
7
|
readonly mediaBox: Rectangle;
|
|
8
|
+
/**
|
|
9
|
+
* §14.11.2 `/CropBox` — the region of the page a viewer SHOWS, which is what
|
|
10
|
+
* a page's size means to anyone looking at it. Defaults to the media box and
|
|
11
|
+
* is clipped to it; inherited down the page tree like the media box is.
|
|
12
|
+
*/
|
|
13
|
+
readonly cropBox: Rectangle;
|
|
8
14
|
/**
|
|
9
15
|
* §14.11.1 `/Rotate` — how far the page turns CLOCKWISE when it is shown,
|
|
10
16
|
* normalised to 0, 90, 180 or 270 and inherited down the page tree.
|
|
@@ -162,6 +162,7 @@ var PdfFile = class PdfFile {
|
|
|
162
162
|
if (out.length >= MAX_PAGES || seen.has(node)) return;
|
|
163
163
|
seen.add(node);
|
|
164
164
|
const mediaBox = readRectangle(this.get(node, "MediaBox")) ?? inherited.mediaBox;
|
|
165
|
+
const cropBox = readRectangle(this.get(node, "CropBox")) ?? inherited.cropBox;
|
|
165
166
|
const resourcesVal = this.get(node, "Resources");
|
|
166
167
|
const resources = resourcesVal instanceof Map ? resourcesVal : inherited.resources;
|
|
167
168
|
const rotate = readRotate(this.get(node, "Rotate")) ?? inherited.rotate;
|
|
@@ -172,6 +173,7 @@ var PdfFile = class PdfFile {
|
|
|
172
173
|
const kidNode = this.resolve(kid);
|
|
173
174
|
if (kidNode instanceof Map) this.walkPageTree(kidNode, {
|
|
174
175
|
mediaBox,
|
|
176
|
+
cropBox,
|
|
175
177
|
resources,
|
|
176
178
|
rotate
|
|
177
179
|
}, out, seen);
|
|
@@ -179,9 +181,11 @@ var PdfFile = class PdfFile {
|
|
|
179
181
|
}
|
|
180
182
|
return;
|
|
181
183
|
}
|
|
184
|
+
const media = mediaBox ?? DEFAULT_MEDIA_BOX;
|
|
182
185
|
out.push({
|
|
183
186
|
dict: node,
|
|
184
|
-
mediaBox:
|
|
187
|
+
mediaBox: media,
|
|
188
|
+
cropBox: shownBox(media, cropBox),
|
|
185
189
|
rotate: rotate ?? 0,
|
|
186
190
|
resources
|
|
187
191
|
});
|
|
@@ -526,5 +530,29 @@ function lastIndexOfAscii(buf, needle) {
|
|
|
526
530
|
}
|
|
527
531
|
return -1;
|
|
528
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* §14.11.2 — the box a viewer SHOWS: the crop box where there is one, clipped
|
|
535
|
+
* to the media box, and the media box where there is not.
|
|
536
|
+
*
|
|
537
|
+
* A crop box that states nothing usable — zero-sized, or outside the sheet
|
|
538
|
+
* altogether — is a crop box no viewer honours, and the media box stands.
|
|
539
|
+
* bug1844576.pdf is 612×792 of paper cropped to 181×53.75, and read as its
|
|
540
|
+
* media box the form on it arrived a fifth of the way down a letter page.
|
|
541
|
+
*/
|
|
542
|
+
function shownBox(media, crop) {
|
|
543
|
+
if (!crop) return media;
|
|
544
|
+
const [mx0, my0, mx1, my1] = media;
|
|
545
|
+
const [cx0, cy0, cx1, cy1] = crop;
|
|
546
|
+
const x0 = Math.max(Math.min(mx0, mx1), Math.min(cx0, cx1));
|
|
547
|
+
const y0 = Math.max(Math.min(my0, my1), Math.min(cy0, cy1));
|
|
548
|
+
const x1 = Math.min(Math.max(mx0, mx1), Math.max(cx0, cx1));
|
|
549
|
+
const y1 = Math.min(Math.max(my0, my1), Math.max(cy0, cy1));
|
|
550
|
+
return x1 - x0 > 1 && y1 - y0 > 1 ? [
|
|
551
|
+
x0,
|
|
552
|
+
y0,
|
|
553
|
+
x1,
|
|
554
|
+
y1
|
|
555
|
+
] : media;
|
|
556
|
+
}
|
|
529
557
|
//#endregion
|
|
530
558
|
export { PdfFile };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PdfDict } from '../pdf/objects.js';
|
|
2
2
|
import { PdfFile, PdfPage } from './document.js';
|
|
3
|
+
import { Loss } from '../core/ir/index.js';
|
|
3
4
|
import { FontRegistry } from '../core/font/index.js';
|
|
4
5
|
/**
|
|
5
6
|
* Every embedded TrueType program the pages use, by the name a run will ask for
|
|
@@ -15,11 +16,19 @@ import { FontRegistry } from '../core/font/index.js';
|
|
|
15
16
|
* (`/FontFile`) are different formats that the layout's parser does not take;
|
|
16
17
|
* those keep their substitute.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* And only one whose OUTLINES this pipeline can carry: a program with no
|
|
20
|
+
* `glyf`/`loca` cannot be subset, and a face offered here is one the writer
|
|
21
|
+
* will be asked to embed. bug1186827.pdf ships an OpenType/CFF program under
|
|
22
|
+
* `/FontFile2`, which parses like any other and then killed the whole
|
|
23
|
+
* conversion — "Subsetting requires a TrueType font with glyf+loca tables" —
|
|
24
|
+
* rather than losing one face.
|
|
25
|
+
*
|
|
26
|
+
* @param file The owning file.
|
|
27
|
+
* @param pages The pages whose fonts are wanted.
|
|
28
|
+
* @param losses Appended to for a face the page embeds and this cannot carry.
|
|
20
29
|
* @returns Name → a one-face registry holding that program.
|
|
21
30
|
*/
|
|
22
|
-
export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray<PdfPage>): Map<string, FontRegistry>;
|
|
31
|
+
export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray<PdfPage>, losses?: Array<Loss>): Map<string, FontRegistry>;
|
|
23
32
|
/**
|
|
24
33
|
* The name a run set in `fontDict` will ask for: its `/BaseFont` without the
|
|
25
34
|
* six-capital subset prefix (§9.6.4), lowercased.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { parseTtf } from "../core/font/ttf-parser.js";
|
|
1
2
|
import { FontRegistry } from "../core/font/font-registry.js";
|
|
2
3
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
4
|
+
import { FEATURES } from "../core/ir/features.js";
|
|
3
5
|
//#region src/pdf-reader/embedded-fonts.ts
|
|
4
6
|
var MAX_FORM_DEPTH = 8;
|
|
5
7
|
/**
|
|
@@ -16,11 +18,19 @@ var MAX_FORM_DEPTH = 8;
|
|
|
16
18
|
* (`/FontFile`) are different formats that the layout's parser does not take;
|
|
17
19
|
* those keep their substitute.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* And only one whose OUTLINES this pipeline can carry: a program with no
|
|
22
|
+
* `glyf`/`loca` cannot be subset, and a face offered here is one the writer
|
|
23
|
+
* will be asked to embed. bug1186827.pdf ships an OpenType/CFF program under
|
|
24
|
+
* `/FontFile2`, which parses like any other and then killed the whole
|
|
25
|
+
* conversion — "Subsetting requires a TrueType font with glyf+loca tables" —
|
|
26
|
+
* rather than losing one face.
|
|
27
|
+
*
|
|
28
|
+
* @param file The owning file.
|
|
29
|
+
* @param pages The pages whose fonts are wanted.
|
|
30
|
+
* @param losses Appended to for a face the page embeds and this cannot carry.
|
|
21
31
|
* @returns Name → a one-face registry holding that program.
|
|
22
32
|
*/
|
|
23
|
-
function collectEmbeddedFonts(file, pages) {
|
|
33
|
+
function collectEmbeddedFonts(file, pages, losses) {
|
|
24
34
|
const out = /* @__PURE__ */ new Map();
|
|
25
35
|
const seen = /* @__PURE__ */ new Set();
|
|
26
36
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -32,6 +42,15 @@ function collectEmbeddedFonts(file, pages) {
|
|
|
32
42
|
const program = fontProgram(file, fontDict);
|
|
33
43
|
if (!program) return;
|
|
34
44
|
try {
|
|
45
|
+
const parsed = parseTtf(program);
|
|
46
|
+
if (!parsed.tables.has("glyf") || !parsed.tables.has("loca")) {
|
|
47
|
+
losses?.push({
|
|
48
|
+
severity: "degraded",
|
|
49
|
+
feature: FEATURES.text,
|
|
50
|
+
detail: `embedded font ${name} has no TrueType outlines (a CFF program under /FontFile2); its text is re-set in a substitute face`
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
35
54
|
out.set(name, FontRegistry.fromBytes({ regular: program }));
|
|
36
55
|
} catch {}
|
|
37
56
|
};
|