reamkit 1.25.1 → 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 +12 -5
- 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 -17
- package/dist/esm/pdf-reader/content.js +175 -14
- 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 +97 -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/glyph-names.d.ts +6 -0
- package/dist/esm/pdf-reader/glyph-names.js +408 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +8 -4
- package/dist/esm/pdf-reader/image-decode.js +224 -12
- 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 +109 -0
- package/dist/esm/pdf-reader/jbig2.js +2606 -0
- 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 +71 -6
- package/dist/esm/pdf-reader/shading.js +185 -15
- 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 -25
- package/dist/esm/word/docx-writer.js +213 -23
- 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,3 +1,5 @@
|
|
|
1
|
+
import { ColorSpaceInfo, GsPaint } from './shading.js';
|
|
2
|
+
import { TextMarkup } from './annot-draw.js';
|
|
1
3
|
import { ShapeGradient } from '../core/vector.js';
|
|
2
4
|
import { PdfDict, PdfStream } from '../pdf/objects.js';
|
|
3
5
|
/**
|
|
@@ -71,8 +73,21 @@ export interface TextRun {
|
|
|
71
73
|
* than in a substitute (see `./embedded-fonts`).
|
|
72
74
|
*/
|
|
73
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;
|
|
74
82
|
/** §9.8.1 — the face the glyphs were shown in is a bold one. */
|
|
75
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;
|
|
76
91
|
/**
|
|
77
92
|
* §8.6.6.2 — the glyphs are filled with a tiling PATTERN, named here for the
|
|
78
93
|
* caller to resolve: a pattern is a content stream, not a colour, and the
|
|
@@ -119,13 +134,33 @@ export interface TextRun {
|
|
|
119
134
|
* square to page space, so it carries both the placement and the size; the
|
|
120
135
|
* `mcid` links the paint to its structure element (a `/Figure`, E-PDF EP6).
|
|
121
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
|
+
}
|
|
122
144
|
export interface ImagePlacement {
|
|
123
145
|
/** Where the `Do` fell in the stream's painting order — see {@link VectorPlacement.order}. */
|
|
124
146
|
readonly order: number;
|
|
125
|
-
/** XObject resource name (no leading slash). */
|
|
147
|
+
/** XObject resource name (no leading slash), empty for an inline image. */
|
|
126
148
|
readonly name: string;
|
|
149
|
+
/** §8.9.7 — the image itself, where it was written into the stream. */
|
|
150
|
+
readonly inline?: InlineImage;
|
|
127
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;
|
|
128
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;
|
|
129
164
|
}
|
|
130
165
|
/**
|
|
131
166
|
* One segment of a painted path (E-PDF EP10/EP11), in page space (y-up): a
|
|
@@ -177,6 +212,16 @@ export interface VectorPlacement {
|
|
|
177
212
|
readonly gradient?: ShapeGradient;
|
|
178
213
|
/** §11.6.4.4 `/ca` — how opaque the fill is, when the page asked for less. */
|
|
179
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;
|
|
180
225
|
/**
|
|
181
226
|
* §8.7.3 — the TILING pattern resource name the path is filled with. Its
|
|
182
227
|
* content is a stream of its own, so what the fill actually shows is only
|
|
@@ -221,22 +266,7 @@ export type Matrix = readonly [number, number, number, number, number, number];
|
|
|
221
266
|
export declare const IDENTITY: Matrix;
|
|
222
267
|
/** Compose two {@link Matrix matrices}: `a` applied first, then `b`. */
|
|
223
268
|
export declare function multiply(a: Matrix, b: Matrix): Matrix;
|
|
224
|
-
|
|
225
|
-
* Walk a page's decoded content stream (§9.4) and extract its positioned text,
|
|
226
|
-
* painted XObjects and painted paths. Tracks the graphics state (CTM via
|
|
227
|
-
* `q`/`Q`/`cm`) and text state (text/line matrices, font, size, spacing), and
|
|
228
|
-
* emits one {@link TextRun} per show operator (`Tj` / `TJ` / `'` / `"`) in page
|
|
229
|
-
* space (points).
|
|
230
|
-
*
|
|
231
|
-
* @param bytes The page's decoded content-stream bytes.
|
|
232
|
-
* @param fonts Page fonts by resource key (`Tf` name), for decoding + widths;
|
|
233
|
-
* an unmapped key falls back to Latin-1 with a half-em advance.
|
|
234
|
-
* @param initialCtm The starting CTM mapping user space to page space.
|
|
235
|
-
* @param shadings Shading patterns by name, selected by `scn`/`sc` (EP16c).
|
|
236
|
-
* @param alphas Constant fill alphas by `/ExtGState` name, selected by `gs`.
|
|
237
|
-
* @returns The extracted text runs, image placements and vector paths.
|
|
238
|
-
*/
|
|
239
|
-
export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, ShapeGradient>, alphas?: ReadonlyMap<string, number>): InterpretResult;
|
|
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;
|
|
240
270
|
/**
|
|
241
271
|
* Whether a string is wholly right-to-left: at least one letter of an RTL
|
|
242
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
|
|
@@ -73,6 +74,8 @@ var FALLBACK_FONT = {
|
|
|
73
74
|
var UPRIGHT_TOLERANCE_DEG = .5;
|
|
74
75
|
function initialState() {
|
|
75
76
|
return {
|
|
77
|
+
fillSpace: void 0,
|
|
78
|
+
strokeSpace: void 0,
|
|
76
79
|
ctm: IDENTITY,
|
|
77
80
|
fontKey: "",
|
|
78
81
|
font: FALLBACK_FONT,
|
|
@@ -89,6 +92,9 @@ function initialState() {
|
|
|
89
92
|
fillGradient: void 0,
|
|
90
93
|
fillPattern: void 0,
|
|
91
94
|
fillAlpha: 1,
|
|
95
|
+
fillDarkens: false,
|
|
96
|
+
blendMode: void 0,
|
|
97
|
+
softMask: false,
|
|
92
98
|
clip: void 0
|
|
93
99
|
};
|
|
94
100
|
}
|
|
@@ -107,7 +113,58 @@ function initialState() {
|
|
|
107
113
|
* @param alphas Constant fill alphas by `/ExtGState` name, selected by `gs`.
|
|
108
114
|
* @returns The extracted text runs, image placements and vector paths.
|
|
109
115
|
*/
|
|
110
|
-
|
|
116
|
+
/** §8.6.8 — the space a `cs` / `CS` operand names, if it is one we read. */
|
|
117
|
+
function spaceOf(operands, spaces) {
|
|
118
|
+
const name = operands[operands.length - 1];
|
|
119
|
+
if (!(name instanceof PdfName)) return void 0;
|
|
120
|
+
const direct = spaces.get(name.value);
|
|
121
|
+
if (direct) return direct;
|
|
122
|
+
switch (name.value) {
|
|
123
|
+
case "DeviceGray": return {
|
|
124
|
+
kind: "gray",
|
|
125
|
+
components: 1
|
|
126
|
+
};
|
|
127
|
+
case "DeviceRGB": return {
|
|
128
|
+
kind: "rgb",
|
|
129
|
+
components: 3
|
|
130
|
+
};
|
|
131
|
+
case "DeviceCMYK": return {
|
|
132
|
+
kind: "cmyk",
|
|
133
|
+
components: 4
|
|
134
|
+
};
|
|
135
|
+
default: return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* §8.6.8 — the colour a run of `sc` / `scn` components comes to.
|
|
140
|
+
*
|
|
141
|
+
* The space in force decides, and where it was not read the COUNT is the next
|
|
142
|
+
* best witness: three numbers are RGB and four are CMYK on every device space
|
|
143
|
+
* there is. One number is the ambiguous case — grey in a device space, but the
|
|
144
|
+
* strength of a colorant in a Separation, where 1 is the ink at full and reads
|
|
145
|
+
* dark — so a lone component is only taken where the space said what it means.
|
|
146
|
+
*/
|
|
147
|
+
function componentColor(operands, space) {
|
|
148
|
+
const nums = operands.filter((o) => typeof o === "number");
|
|
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
|
+
}
|
|
154
|
+
switch (space?.kind ?? (nums.length === 3 ? "rgb" : nums.length === 4 ? "cmyk" : void 0)) {
|
|
155
|
+
case "rgb": return nums.length >= 3 ? rgbHex(nums[0], nums[1], nums[2]) : void 0;
|
|
156
|
+
case "cmyk": return nums.length >= 4 ? cmykHex(nums[0], nums[1], nums[2], nums[3]) : void 0;
|
|
157
|
+
case "gray": return grayHex(nums[0]);
|
|
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));
|
|
164
|
+
default: return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map(), hiddenOc = /* @__PURE__ */ new Set()) {
|
|
111
168
|
const runs = [];
|
|
112
169
|
const images = [];
|
|
113
170
|
const vectors = [];
|
|
@@ -123,6 +180,9 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
123
180
|
let paintOrder = 0;
|
|
124
181
|
let operands = [];
|
|
125
182
|
const mcStack = [];
|
|
183
|
+
const ocStack = [];
|
|
184
|
+
let hiddenDepth = 0;
|
|
185
|
+
const visible = () => hiddenDepth === 0;
|
|
126
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]];
|
|
127
187
|
const moveTo = (x, y) => {
|
|
128
188
|
const [px, py] = toPage(x, y);
|
|
@@ -167,7 +227,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
167
227
|
return state.lineWidth * scale;
|
|
168
228
|
};
|
|
169
229
|
const paintPath = (fill, stroke) => {
|
|
170
|
-
if (path.length >= 2 && (fill || stroke)) {
|
|
230
|
+
if (path.length >= 2 && (fill || stroke) && visible()) {
|
|
171
231
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
172
232
|
vectors.push({
|
|
173
233
|
order: paintOrder++,
|
|
@@ -176,6 +236,9 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
176
236
|
...fill && state.fillPattern !== void 0 ? { patternName: state.fillPattern } : {},
|
|
177
237
|
...fill ? { fillHex: state.fillColor } : {},
|
|
178
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 } : {},
|
|
179
242
|
...fill && state.fillGradient ? { gradient: state.fillGradient } : {},
|
|
180
243
|
...stroke ? {
|
|
181
244
|
strokeHex: state.strokeColor,
|
|
@@ -205,7 +268,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
205
268
|
const type3 = state.font.type3;
|
|
206
269
|
if (type3) {
|
|
207
270
|
const stream = type3.proc(code);
|
|
208
|
-
if (stream) {
|
|
271
|
+
if (stream && visible()) {
|
|
209
272
|
const scale = [
|
|
210
273
|
state.fontSize * state.hScale,
|
|
211
274
|
0,
|
|
@@ -235,8 +298,10 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
235
298
|
const text = logicalOrder(shown);
|
|
236
299
|
if (text.length === 0) return;
|
|
237
300
|
const scaleY = Math.hypot(origin[2], origin[3]) || 1;
|
|
301
|
+
const scaleX = (Math.hypot(origin[0], origin[1]) || 1) * state.hScale;
|
|
238
302
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
239
303
|
const angle = Math.atan2(origin[1], origin[0]) * 180 / Math.PI;
|
|
304
|
+
if (!visible()) return;
|
|
240
305
|
runs.push({
|
|
241
306
|
text,
|
|
242
307
|
x: origin[4],
|
|
@@ -244,7 +309,8 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
244
309
|
endX: end[4],
|
|
245
310
|
endY: end[5],
|
|
246
311
|
...Math.abs(angle) > UPRIGHT_TOLERANCE_DEG ? { angleDeg: angle } : {},
|
|
247
|
-
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,
|
|
248
314
|
fontKey: state.fontKey,
|
|
249
315
|
...state.font.name !== void 0 ? { fontName: state.font.name } : {},
|
|
250
316
|
...state.font.type3 ? { type3: true } : {},
|
|
@@ -348,23 +414,31 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
348
414
|
const props = operands[operands.length - 1];
|
|
349
415
|
const mcidVal = !(tag instanceof PdfName && tag.value === "Artifact") && props instanceof Map ? props.get("MCID") : void 0;
|
|
350
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++;
|
|
351
420
|
break;
|
|
352
421
|
}
|
|
353
422
|
case "BMC":
|
|
354
423
|
mcStack.push(void 0);
|
|
424
|
+
ocStack.push(false);
|
|
355
425
|
break;
|
|
356
426
|
case "EMC":
|
|
357
427
|
mcStack.pop();
|
|
428
|
+
if (ocStack.pop() === true) hiddenDepth--;
|
|
358
429
|
break;
|
|
359
430
|
case "Do": {
|
|
360
431
|
const nm = operands[0];
|
|
361
|
-
if (nm instanceof PdfName) {
|
|
432
|
+
if (nm instanceof PdfName && visible()) {
|
|
362
433
|
const mcid = mcStack.length > 0 ? mcStack[mcStack.length - 1] : void 0;
|
|
363
434
|
images.push({
|
|
364
435
|
order: paintOrder++,
|
|
365
436
|
name: nm.value,
|
|
366
437
|
ctm: state.ctm,
|
|
367
|
-
...
|
|
438
|
+
...state.clip ? { clip: state.clip } : {},
|
|
439
|
+
fillHex: state.fillColor,
|
|
440
|
+
...mcid !== void 0 ? { mcid } : {},
|
|
441
|
+
...state.blendMode !== void 0 ? { blend: state.blendMode } : {}
|
|
368
442
|
});
|
|
369
443
|
}
|
|
370
444
|
break;
|
|
@@ -384,12 +458,34 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
384
458
|
state.fillGradient = void 0;
|
|
385
459
|
state.fillPattern = void 0;
|
|
386
460
|
break;
|
|
461
|
+
case "cs":
|
|
462
|
+
state.fillSpace = spaceOf(operands, spaces);
|
|
463
|
+
break;
|
|
464
|
+
case "CS":
|
|
465
|
+
state.strokeSpace = spaceOf(operands, spaces);
|
|
466
|
+
break;
|
|
387
467
|
case "scn":
|
|
388
468
|
case "sc": {
|
|
389
469
|
const last = operands[operands.length - 1];
|
|
390
470
|
const named = last instanceof PdfName ? last.value : void 0;
|
|
391
|
-
|
|
392
|
-
|
|
471
|
+
if (named !== void 0) {
|
|
472
|
+
state.fillGradient = shadings.get(named);
|
|
473
|
+
state.fillPattern = state.fillGradient ? void 0 : named;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
const hex = componentColor(operands, state.fillSpace);
|
|
477
|
+
if (hex !== void 0) {
|
|
478
|
+
state.fillColor = hex;
|
|
479
|
+
state.fillGradient = void 0;
|
|
480
|
+
state.fillPattern = void 0;
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "SCN":
|
|
485
|
+
case "SC": {
|
|
486
|
+
if (operands[operands.length - 1] instanceof PdfName) break;
|
|
487
|
+
const hex = componentColor(operands, state.strokeSpace);
|
|
488
|
+
if (hex !== void 0) state.strokeColor = hex;
|
|
393
489
|
break;
|
|
394
490
|
}
|
|
395
491
|
case "RG":
|
|
@@ -445,7 +541,13 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
445
541
|
break;
|
|
446
542
|
case "gs": {
|
|
447
543
|
const nm = operands[operands.length - 1];
|
|
448
|
-
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
|
+
}
|
|
449
551
|
break;
|
|
450
552
|
}
|
|
451
553
|
case "W":
|
|
@@ -479,8 +581,19 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
|
|
|
479
581
|
operands.push(readDict(lexer));
|
|
480
582
|
break;
|
|
481
583
|
case "keyword":
|
|
482
|
-
if (tok.value === "BI")
|
|
483
|
-
|
|
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);
|
|
484
597
|
operands = [];
|
|
485
598
|
break;
|
|
486
599
|
default:
|
|
@@ -621,14 +734,62 @@ function readValue(lexer) {
|
|
|
621
734
|
default: return PDF_NULL;
|
|
622
735
|
}
|
|
623
736
|
}
|
|
624
|
-
|
|
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();
|
|
625
749
|
for (;;) {
|
|
626
750
|
const tok = lexer.nextToken();
|
|
627
|
-
if (tok.kind === "eof") return;
|
|
751
|
+
if (tok.kind === "eof") return void 0;
|
|
628
752
|
if (tok.kind === "keyword" && tok.value === "ID") break;
|
|
753
|
+
if (tok.kind === "name") dict.set(tok.value, readValue(lexer));
|
|
629
754
|
}
|
|
630
|
-
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);
|
|
631
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;
|
|
632
793
|
}
|
|
633
794
|
//#endregion
|
|
634
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 };
|