reamkit 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/esm/core/converter/ream.d.ts +0 -9
- package/dist/esm/core/converter/ream.js +0 -1
- package/dist/esm/core/document-model/types.d.ts +5 -3
- package/dist/esm/core/font/index.d.ts +1 -0
- package/dist/esm/core/font/ligatures.d.ts +17 -0
- package/dist/esm/core/font/ligatures.js +49 -0
- package/dist/esm/core/font/ttf-parser.d.ts +2 -1
- package/dist/esm/core/font/ttf-parser.js +11 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
- package/dist/esm/core/fonts/remote-fonts.js +47 -2
- package/dist/esm/core/fonts/scripts.js +10 -5
- package/dist/esm/excel/header-footer.js +53 -8
- package/dist/esm/layout/styled-layout.js +53 -3
- package/dist/esm/pdf/cid-font.js +35 -8
- package/dist/esm/pdf-reader/annot-draw.d.ts +65 -0
- package/dist/esm/pdf-reader/annot-draw.js +487 -0
- package/dist/esm/pdf-reader/annots.d.ts +0 -12
- package/dist/esm/pdf-reader/annots.js +30 -4
- package/dist/esm/pdf-reader/ccitt.d.ts +20 -3
- package/dist/esm/pdf-reader/ccitt.js +102 -6
- package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
- package/dist/esm/pdf-reader/cff-outline.js +1122 -0
- 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/cmap.js +5 -2
- package/dist/esm/pdf-reader/content.d.ts +123 -3
- package/dist/esm/pdf-reader/content.js +232 -54
- package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
- package/dist/esm/pdf-reader/dingbats.js +1033 -0
- package/dist/esm/pdf-reader/display.d.ts +1 -1
- package/dist/esm/pdf-reader/display.js +61 -6
- package/dist/esm/pdf-reader/document.d.ts +6 -0
- package/dist/esm/pdf-reader/document.js +55 -10
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +23 -3
- package/dist/esm/pdf-reader/embedded-fonts.js +36 -4
- package/dist/esm/pdf-reader/encodings.d.ts +25 -0
- package/dist/esm/pdf-reader/encodings.js +110 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +19 -5
- package/dist/esm/pdf-reader/flow-build.js +111 -11
- package/dist/esm/pdf-reader/font.js +403 -21
- package/dist/esm/pdf-reader/function.d.ts +16 -0
- package/dist/esm/pdf-reader/function.js +414 -0
- package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
- package/dist/esm/pdf-reader/glyf-outline.js +351 -0
- package/dist/esm/pdf-reader/glyph-names.js +20 -0
- package/dist/esm/pdf-reader/icc.d.ts +10 -0
- package/dist/esm/pdf-reader/icc.js +210 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +9 -4
- package/dist/esm/pdf-reader/image-decode.js +274 -72
- package/dist/esm/pdf-reader/images.d.ts +18 -0
- package/dist/esm/pdf-reader/images.js +154 -11
- package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
- package/dist/esm/pdf-reader/jbig2.js +126 -32
- package/dist/esm/pdf-reader/layout.d.ts +32 -0
- package/dist/esm/pdf-reader/layout.js +1316 -64
- package/dist/esm/pdf-reader/lexer.d.ts +2 -0
- package/dist/esm/pdf-reader/lexer.js +4 -0
- package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
- package/dist/esm/pdf-reader/math-rows.js +198 -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/predefined-cmap.d.ts +21 -0
- package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
- package/dist/esm/pdf-reader/reader.d.ts +6 -6
- package/dist/esm/pdf-reader/reader.js +102 -16
- package/dist/esm/pdf-reader/shading.d.ts +139 -8
- package/dist/esm/pdf-reader/shading.js +309 -39
- 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/stream-filters.d.ts +6 -0
- package/dist/esm/pdf-reader/stream-filters.js +67 -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 +185 -0
- package/dist/esm/pdf-reader/text.js +157 -4
- package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
- package/dist/esm/pdf-reader/type1-outline.js +576 -0
- package/dist/esm/pdf-reader/vector.d.ts +5 -0
- package/dist/esm/pdf-reader/vector.js +79 -27
- package/dist/esm/word/document-parser.js +4 -1
- package/dist/esm/word/docx-writer.js +74 -11
- package/package.json +1 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { CieSpace } from './cie-color.js';
|
|
2
|
+
import { IccTransform } from './icc.js';
|
|
3
|
+
import { PdfFunction } from './function.js';
|
|
1
4
|
import { ShapeGradient } from '../core/vector.js';
|
|
2
|
-
import {
|
|
5
|
+
import { PdfDict } from '../pdf/objects.js';
|
|
6
|
+
import { PdfFile } from './document.js';
|
|
3
7
|
/**
|
|
4
8
|
* Resolve a page's `/Pattern` resources into gradient fills (E-PDF EP16c, ISO
|
|
5
9
|
* 32000-1 §8.7.4.5). Every `PatternType` 2 (shading) pattern is evaluated — its
|
|
@@ -8,9 +12,76 @@ import { PdfFile, PdfPage } from './document.js';
|
|
|
8
12
|
* filled with `/Pattern cs /Pn scn`. The bare `sh` operator (clip-bounded) is
|
|
9
13
|
* not captured.
|
|
10
14
|
*
|
|
15
|
+
* @param file The owning file.
|
|
16
|
+
* @param resources The resource dictionary in force — a page's, or the form or
|
|
17
|
+
* annotation appearance's own.
|
|
11
18
|
* @returns A map from pattern resource name to its {@link ShapeGradient}.
|
|
12
19
|
*/
|
|
13
|
-
export declare function buildShadingMap(file: PdfFile,
|
|
20
|
+
export declare function buildShadingMap(file: PdfFile, resources: PdfDict | undefined): Map<string, ShapeGradient>;
|
|
21
|
+
/**
|
|
22
|
+
* §8.7.4.5.2 — an axial or radial shading as a gradient, for a bare `sh`.
|
|
23
|
+
*
|
|
24
|
+
* A `sh` paints the CLIP rather than a path, so what it needs is not a fill for
|
|
25
|
+
* a shape the page drew but the gradient itself, to fill the region with. It is
|
|
26
|
+
* the same reading `buildShadingMap` does for a pattern.
|
|
27
|
+
*
|
|
28
|
+
* @param file The owning file.
|
|
29
|
+
* @param sh The shading dictionary.
|
|
30
|
+
* @returns The gradient, or `undefined` for a type this does not read.
|
|
31
|
+
*/
|
|
32
|
+
export declare function gradientShading(file: PdfFile, sh: PdfDict): ShapeGradient | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* §8.7.4.5 — which kind of shading this is, as the file states it.
|
|
35
|
+
*
|
|
36
|
+
* @param file The owning file.
|
|
37
|
+
* @param sh The shading dictionary.
|
|
38
|
+
* @returns Its `/ShadingType`, or 0 where the file states none.
|
|
39
|
+
*/
|
|
40
|
+
export declare function shadingTypeOf(file: PdfFile, sh: PdfDict): number;
|
|
41
|
+
/**
|
|
42
|
+
* §8.7.4.5.3 — a FUNCTION-BASED shading, sampled into a picture.
|
|
43
|
+
*
|
|
44
|
+
* Type 1 is not a ramp between two points: it is a function of two variables
|
|
45
|
+
* over a rectangle, and no gradient can stand for one. Painted by a bare `sh`
|
|
46
|
+
* it fills the clip, and nothing here lifted it at all —
|
|
47
|
+
* function_based_shading.pdf is nine such squares and 43% of the page's ink,
|
|
48
|
+
* and reconstructed to a blank sheet.
|
|
49
|
+
*
|
|
50
|
+
* Sampled it is exactly a picture, which every format downstream can show. The
|
|
51
|
+
* grid is fixed: the function is smooth by construction (§8.7.4.5.3 gives it a
|
|
52
|
+
* `/Domain` and nothing else), so more samples buy nothing a reader can see.
|
|
53
|
+
*
|
|
54
|
+
* @param file The owning file.
|
|
55
|
+
* @param shading The shading dictionary.
|
|
56
|
+
* @returns The picture and the domain it covers, or `undefined` for a shading
|
|
57
|
+
* of another type or one whose function cannot be run.
|
|
58
|
+
*/
|
|
59
|
+
export declare function sampledShading(file: PdfFile, shading: PdfDict): {
|
|
60
|
+
rgb: Uint8Array;
|
|
61
|
+
size: number;
|
|
62
|
+
domain: [number, number, number, number];
|
|
63
|
+
} | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* §8.6.8 — the colour a run of `sc` / `scn` components comes to.
|
|
66
|
+
*
|
|
67
|
+
* The space in force decides, and where it was not read the COUNT is the next
|
|
68
|
+
* best witness: three numbers are RGB and four are CMYK on every device space
|
|
69
|
+
* there is. One number is the ambiguous case — grey in a device space, but the
|
|
70
|
+
* strength of a colorant in a Separation, where 1 is the ink at full and reads
|
|
71
|
+
* dark — so a lone component is only taken where the space said what it means.
|
|
72
|
+
*
|
|
73
|
+
* @param nums The components, as the page or a shading's function states them.
|
|
74
|
+
* @param space The space in force, where it was read.
|
|
75
|
+
* @returns The colour as 6 hex digits, or `undefined` where the numbers do not
|
|
76
|
+
* say what colour they are.
|
|
77
|
+
*/
|
|
78
|
+
export declare function spaceColor(nums: ReadonlyArray<number>, space: ColorSpaceInfo | undefined): string | undefined;
|
|
79
|
+
/** Three channels, each 0..1, as 6 upper-case hex digits. */
|
|
80
|
+
export declare function rgbHex(r: number, g: number, b: number): string;
|
|
81
|
+
/** One grey level, 0..1, as 6 upper-case hex digits. */
|
|
82
|
+
export declare function grayHex(v: number): string;
|
|
83
|
+
/** §8.6.4.4 — four inks, each 0..1, as 6 upper-case hex digits. */
|
|
84
|
+
export declare function cmykHex(c: number, m: number, y: number, k: number): string;
|
|
14
85
|
/**
|
|
15
86
|
* §11.6.4.4 — the constant fill alpha (`/ca`) of every `/ExtGState` the page
|
|
16
87
|
* names, by name.
|
|
@@ -20,11 +91,44 @@ export declare function buildShadingMap(file: PdfFile, page: PdfPage): Map<strin
|
|
|
20
91
|
* a green band at `ca` 0.6, meant to be read THROUGH: painted solid, the floor
|
|
21
92
|
* plan under each band disappears.
|
|
22
93
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
94
|
+
* §11.3.5 `/BM` comes off the same dictionary. A blend nothing downstream can
|
|
95
|
+
* perform is still worth knowing: `Multiply` and `Darken` both let dark ink
|
|
96
|
+
* under the paint show through, which is what a highlighter IS, and a mark that
|
|
97
|
+
* only darkens belongs UNDER the words rather than over them.
|
|
98
|
+
*
|
|
99
|
+
* @param file The owning file.
|
|
100
|
+
* @param resources The resource dictionary in force — a page's, or the form or
|
|
101
|
+
* annotation appearance's own, since `gs` resolves against
|
|
102
|
+
* whichever is current.
|
|
103
|
+
* @returns Name → the fill alpha and blend worth carrying, for the states that
|
|
104
|
+
* state one.
|
|
26
105
|
*/
|
|
27
|
-
export declare function buildAlphaMap(file: PdfFile,
|
|
106
|
+
export declare function buildAlphaMap(file: PdfFile, resources: PdfDict | undefined): Map<string, GsPaint>;
|
|
107
|
+
/**
|
|
108
|
+
* What a `/ExtGState` says about paint that the reconstruction can carry.
|
|
109
|
+
*
|
|
110
|
+
* Every field is absent when the state does not NAME that parameter, because a
|
|
111
|
+
* `gs` leaves what it does not name alone (§8.4.5).
|
|
112
|
+
*/
|
|
113
|
+
export interface GsPaint {
|
|
114
|
+
/** §11.6.4.4 `/ca` — the constant fill alpha, where the state names one. */
|
|
115
|
+
readonly alpha?: number;
|
|
116
|
+
/** §11.3.5 — whether `/BM` is named at all, since naming `/Normal` ends a blend. */
|
|
117
|
+
readonly statesBlend?: boolean;
|
|
118
|
+
/** §11.3.5 `/BM` — the paint only darkens, so what it covers shows through. */
|
|
119
|
+
readonly darkens?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* §11.3.5 `/BM` — a blend NOTHING downstream can perform, named so the loss
|
|
122
|
+
* report can say which. `Normal` is no blend at all and is never named here.
|
|
123
|
+
*/
|
|
124
|
+
readonly blend?: string;
|
|
125
|
+
/**
|
|
126
|
+
* §11.6.5 `/SMask` — the paint's opacity varies from place to place, out of
|
|
127
|
+
* another group's luminosity or alpha. Nothing downstream has a mask like it.
|
|
128
|
+
* `false` where the state names `/None`, which takes a mask off.
|
|
129
|
+
*/
|
|
130
|
+
readonly masked?: boolean;
|
|
131
|
+
}
|
|
28
132
|
/**
|
|
29
133
|
* §8.6 — how many components a colour space takes, and what they mean.
|
|
30
134
|
*
|
|
@@ -35,6 +139,33 @@ export declare function buildAlphaMap(file: PdfFile, page: PdfPage): Map<string,
|
|
|
35
139
|
export interface ColorSpaceInfo {
|
|
36
140
|
readonly kind: 'gray' | 'rgb' | 'cmyk' | 'tint';
|
|
37
141
|
readonly components: number;
|
|
142
|
+
/**
|
|
143
|
+
* §8.6.5.6/§8.6.5.7 — the CIE parameters, for a `CalGray` or `CalRGB` space.
|
|
144
|
+
* Its numbers look like a device space's and are not: they mean what comes
|
|
145
|
+
* out of this transform.
|
|
146
|
+
*/
|
|
147
|
+
readonly cie?: CieSpace;
|
|
148
|
+
/**
|
|
149
|
+
* §8.6.5.5 — the transform an `/ICCBased` profile states, where its form is
|
|
150
|
+
* one this reads. Its numbers mean what the PROFILE makes of them, which for
|
|
151
|
+
* anything but an sRGB-like profile is not what a device space would.
|
|
152
|
+
*/
|
|
153
|
+
readonly icc?: IccTransform;
|
|
154
|
+
/**
|
|
155
|
+
* §8.6.6.4/§8.6.6.5 — for a `Separation` or `DeviceN`, the way OUT of it: the
|
|
156
|
+
* tint transform and the space its numbers land in. Absent where the file
|
|
157
|
+
* states a transform this cannot run, and then a tint is only "this much ink".
|
|
158
|
+
*/
|
|
159
|
+
readonly tint?: {
|
|
160
|
+
readonly transform: PdfFunction;
|
|
161
|
+
readonly alternate: ColorSpaceInfo;
|
|
162
|
+
};
|
|
38
163
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
164
|
+
/**
|
|
165
|
+
* The spaces a `/ColorSpace` resource dictionary names, by name.
|
|
166
|
+
*
|
|
167
|
+
* @param file The owning file.
|
|
168
|
+
* @param resources The resource dictionary in force.
|
|
169
|
+
* @returns Name → what the space comes to, for the spaces that are read.
|
|
170
|
+
*/
|
|
171
|
+
export declare function buildColorSpaceMap(file: PdfFile, resources: PdfDict | undefined): Map<string, ColorSpaceInfo>;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { cieToSrgb } from "./cie-color.js";
|
|
3
|
+
import { iccTransform } from "./icc.js";
|
|
4
|
+
import { readFunction } from "./function.js";
|
|
2
5
|
//#region src/pdf-reader/shading.ts
|
|
3
6
|
/**
|
|
4
7
|
* Resolve a page's `/Pattern` resources into gradient fills (E-PDF EP16c, ISO
|
|
@@ -8,12 +11,15 @@ import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
|
8
11
|
* filled with `/Pattern cs /Pn scn`. The bare `sh` operator (clip-bounded) is
|
|
9
12
|
* not captured.
|
|
10
13
|
*
|
|
14
|
+
* @param file The owning file.
|
|
15
|
+
* @param resources The resource dictionary in force — a page's, or the form or
|
|
16
|
+
* annotation appearance's own.
|
|
11
17
|
* @returns A map from pattern resource name to its {@link ShapeGradient}.
|
|
12
18
|
*/
|
|
13
|
-
function buildShadingMap(file,
|
|
19
|
+
function buildShadingMap(file, resources) {
|
|
14
20
|
const out = /* @__PURE__ */ new Map();
|
|
15
|
-
if (!
|
|
16
|
-
const patterns = file.get(
|
|
21
|
+
if (!resources) return out;
|
|
22
|
+
const patterns = file.get(resources, "Pattern");
|
|
17
23
|
if (!(patterns instanceof Map)) return out;
|
|
18
24
|
for (const [nm, value] of patterns) {
|
|
19
25
|
const pat = file.resolve(value);
|
|
@@ -25,10 +31,34 @@ function buildShadingMap(file, page) {
|
|
|
25
31
|
}
|
|
26
32
|
return out;
|
|
27
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* §8.7.4.5.2 — an axial or radial shading as a gradient, for a bare `sh`.
|
|
36
|
+
*
|
|
37
|
+
* A `sh` paints the CLIP rather than a path, so what it needs is not a fill for
|
|
38
|
+
* a shape the page drew but the gradient itself, to fill the region with. It is
|
|
39
|
+
* the same reading `buildShadingMap` does for a pattern.
|
|
40
|
+
*
|
|
41
|
+
* @param file The owning file.
|
|
42
|
+
* @param sh The shading dictionary.
|
|
43
|
+
* @returns The gradient, or `undefined` for a type this does not read.
|
|
44
|
+
*/
|
|
45
|
+
function gradientShading(file, sh) {
|
|
46
|
+
return parseShading(file, sh);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* §8.7.4.5 — which kind of shading this is, as the file states it.
|
|
50
|
+
*
|
|
51
|
+
* @param file The owning file.
|
|
52
|
+
* @param sh The shading dictionary.
|
|
53
|
+
* @returns Its `/ShadingType`, or 0 where the file states none.
|
|
54
|
+
*/
|
|
55
|
+
function shadingTypeOf(file, sh) {
|
|
56
|
+
return numOf(file.get(sh, "ShadingType"));
|
|
57
|
+
}
|
|
28
58
|
function parseShading(file, sh) {
|
|
29
59
|
const type = numOf(file.get(sh, "ShadingType"));
|
|
30
60
|
if (type !== 2 && type !== 3) return void 0;
|
|
31
|
-
const stops = parseFunction(file, sh.get("Function"));
|
|
61
|
+
const stops = parseFunction(file, sh.get("Function"), shadingSpace(file, sh));
|
|
32
62
|
if (!stops || stops.length === 0) return void 0;
|
|
33
63
|
if (type === 3) return {
|
|
34
64
|
kind: "radial",
|
|
@@ -41,14 +71,14 @@ function parseShading(file, sh) {
|
|
|
41
71
|
stops
|
|
42
72
|
};
|
|
43
73
|
}
|
|
44
|
-
function parseFunction(file, value) {
|
|
74
|
+
function parseFunction(file, value, space) {
|
|
45
75
|
const resolved = value !== void 0 ? file.resolve(value) : void 0;
|
|
46
76
|
const dict = dictOf(resolved);
|
|
47
77
|
if (!dict) return void 0;
|
|
48
78
|
const type = numOf(file.get(dict, "FunctionType"));
|
|
49
79
|
if (type === 2) {
|
|
50
|
-
const c0 = colorOf(numArray(file, dict.get("C0")) ?? [0]);
|
|
51
|
-
const c1 = colorOf(numArray(file, dict.get("C1")) ?? [1]);
|
|
80
|
+
const c0 = colorOf(numArray(file, dict.get("C0")) ?? [0], space);
|
|
81
|
+
const c1 = colorOf(numArray(file, dict.get("C1")) ?? [1], space);
|
|
52
82
|
return [{
|
|
53
83
|
offset: 0,
|
|
54
84
|
colorHex: c0
|
|
@@ -73,16 +103,100 @@ function parseFunction(file, value) {
|
|
|
73
103
|
const stops = [];
|
|
74
104
|
const span = d1 - d0 || 1;
|
|
75
105
|
for (let i = 0; i < subs.length; i++) {
|
|
76
|
-
const sub = parseFunction(file, subs[i]);
|
|
77
|
-
if (!sub || sub.length
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
const sub = parseFunction(file, subs[i], space);
|
|
107
|
+
if (!sub || sub.length === 0) continue;
|
|
108
|
+
const lo = ((edges[i] ?? d0) - d0) / span;
|
|
109
|
+
const hi = ((edges[i + 1] ?? d1) - d0) / span;
|
|
110
|
+
for (const stop of sub) pushStop(stops, lo + stop.offset * (hi - lo), stop.colorHex);
|
|
80
111
|
}
|
|
81
112
|
return stops.length > 0 ? stops : void 0;
|
|
82
113
|
}
|
|
83
|
-
if (type === 0 && resolved instanceof PdfStream) return sampleFunction(file, resolved);
|
|
114
|
+
if (type === 0 && resolved instanceof PdfStream) return sampleFunction(file, resolved, space);
|
|
115
|
+
if (type === 4) {
|
|
116
|
+
const fn = readFunction(file, resolved);
|
|
117
|
+
if (!fn) return void 0;
|
|
118
|
+
const domain = numArray(file, dict.get("Domain")) ?? [0, 1];
|
|
119
|
+
const d0 = domain[0] ?? 0;
|
|
120
|
+
const d1 = domain[1] ?? 1;
|
|
121
|
+
const stops = [];
|
|
122
|
+
for (let s = 0; s < PS_STOPS; s++) {
|
|
123
|
+
const off = s / (PS_STOPS - 1);
|
|
124
|
+
pushStop(stops, off, colorOf(fn([d0 + off * (d1 - d0)]), space));
|
|
125
|
+
}
|
|
126
|
+
return stops.length > 0 ? stops : void 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* §8.7.4.5.3 — a FUNCTION-BASED shading, sampled into a picture.
|
|
131
|
+
*
|
|
132
|
+
* Type 1 is not a ramp between two points: it is a function of two variables
|
|
133
|
+
* over a rectangle, and no gradient can stand for one. Painted by a bare `sh`
|
|
134
|
+
* it fills the clip, and nothing here lifted it at all —
|
|
135
|
+
* function_based_shading.pdf is nine such squares and 43% of the page's ink,
|
|
136
|
+
* and reconstructed to a blank sheet.
|
|
137
|
+
*
|
|
138
|
+
* Sampled it is exactly a picture, which every format downstream can show. The
|
|
139
|
+
* grid is fixed: the function is smooth by construction (§8.7.4.5.3 gives it a
|
|
140
|
+
* `/Domain` and nothing else), so more samples buy nothing a reader can see.
|
|
141
|
+
*
|
|
142
|
+
* @param file The owning file.
|
|
143
|
+
* @param shading The shading dictionary.
|
|
144
|
+
* @returns The picture and the domain it covers, or `undefined` for a shading
|
|
145
|
+
* of another type or one whose function cannot be run.
|
|
146
|
+
*/
|
|
147
|
+
function sampledShading(file, shading) {
|
|
148
|
+
if (numOf(file.get(shading, "ShadingType")) !== 1) return void 0;
|
|
149
|
+
const space = shadingSpace(file, shading);
|
|
150
|
+
const fn = readFunction(file, shading.get("Function"));
|
|
151
|
+
if (!fn) return void 0;
|
|
152
|
+
const d = numArray(file, shading.get("Domain")) ?? [
|
|
153
|
+
0,
|
|
154
|
+
1,
|
|
155
|
+
0,
|
|
156
|
+
1
|
|
157
|
+
];
|
|
158
|
+
if (d.length < 4) return void 0;
|
|
159
|
+
const domain = [
|
|
160
|
+
d[0],
|
|
161
|
+
d[1],
|
|
162
|
+
d[2],
|
|
163
|
+
d[3]
|
|
164
|
+
];
|
|
165
|
+
const size = SAMPLED_SIDE;
|
|
166
|
+
const rgb = new Uint8Array(size * size * 3);
|
|
167
|
+
for (let row = 0; row < size; row++) {
|
|
168
|
+
const y = domain[3] - (row + .5) / size * (domain[3] - domain[2]);
|
|
169
|
+
for (let col = 0; col < size; col++) {
|
|
170
|
+
const hex = colorOf(fn([domain[0] + (col + .5) / size * (domain[1] - domain[0]), y]), space);
|
|
171
|
+
const at = (row * size + col) * 3;
|
|
172
|
+
rgb[at] = Number.parseInt(hex.slice(0, 2), 16);
|
|
173
|
+
rgb[at + 1] = Number.parseInt(hex.slice(2, 4), 16);
|
|
174
|
+
rgb[at + 2] = Number.parseInt(hex.slice(4, 6), 16);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
rgb,
|
|
179
|
+
size,
|
|
180
|
+
domain
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* §8.7.4.5 — the colour space a shading's function lands in.
|
|
185
|
+
*
|
|
186
|
+
* The components alone do not say: a `/Separation` function gives ONE number
|
|
187
|
+
* and that number is a strength of ink, not a grey level.
|
|
188
|
+
* function_based_shading_cmyk.pdf's third square is a spot colour whose full
|
|
189
|
+
* tint is a warm red, and read as grey it came back a black-to-white ramp.
|
|
190
|
+
*/
|
|
191
|
+
function shadingSpace(file, sh) {
|
|
192
|
+
const cs = sh.get("ColorSpace");
|
|
193
|
+
return cs === void 0 ? void 0 : colorSpaceAt(file, file.resolve(cs), 0);
|
|
84
194
|
}
|
|
85
|
-
function
|
|
195
|
+
/** How many samples a side a function-based shading is drawn at. */
|
|
196
|
+
var SAMPLED_SIDE = 128;
|
|
197
|
+
/** How many places a type-4 gradient is sampled at along its domain. */
|
|
198
|
+
var PS_STOPS = 16;
|
|
199
|
+
function sampleFunction(file, stream, space) {
|
|
86
200
|
const d = stream.dict;
|
|
87
201
|
const size = numArray(file, d.get("Size"));
|
|
88
202
|
const range = numArray(file, d.get("Range"));
|
|
@@ -129,18 +243,63 @@ function numArray(file, v) {
|
|
|
129
243
|
if (!Array.isArray(r)) return void 0;
|
|
130
244
|
return r.map((x) => typeof x === "number" ? x : 0);
|
|
131
245
|
}
|
|
132
|
-
function colorOf(c) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
246
|
+
function colorOf(c, space) {
|
|
247
|
+
return spaceColor(c, space) ?? spaceColor(c, void 0) ?? grayHex(c[0] ?? 0);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* §8.6.8 — the colour a run of `sc` / `scn` components comes to.
|
|
251
|
+
*
|
|
252
|
+
* The space in force decides, and where it was not read the COUNT is the next
|
|
253
|
+
* best witness: three numbers are RGB and four are CMYK on every device space
|
|
254
|
+
* there is. One number is the ambiguous case — grey in a device space, but the
|
|
255
|
+
* strength of a colorant in a Separation, where 1 is the ink at full and reads
|
|
256
|
+
* dark — so a lone component is only taken where the space said what it means.
|
|
257
|
+
*
|
|
258
|
+
* @param nums The components, as the page or a shading's function states them.
|
|
259
|
+
* @param space The space in force, where it was read.
|
|
260
|
+
* @returns The colour as 6 hex digits, or `undefined` where the numbers do not
|
|
261
|
+
* say what colour they are.
|
|
262
|
+
*/
|
|
263
|
+
function spaceColor(nums, space) {
|
|
264
|
+
if (nums.length === 0) return void 0;
|
|
265
|
+
if (space?.icc && nums.length >= space.components) {
|
|
266
|
+
const [r, g, b] = space.icc(nums);
|
|
267
|
+
return rgbHex(r, g, b);
|
|
268
|
+
}
|
|
269
|
+
if (space?.cie) {
|
|
270
|
+
const [r, g, b] = cieToSrgb(space.cie, nums);
|
|
271
|
+
return rgbHex(r, g, b);
|
|
272
|
+
}
|
|
273
|
+
switch (space?.kind ?? (nums.length === 3 ? "rgb" : nums.length === 4 ? "cmyk" : void 0)) {
|
|
274
|
+
case "rgb": return nums.length >= 3 ? rgbHex(nums[0], nums[1], nums[2]) : void 0;
|
|
275
|
+
case "cmyk": return nums.length >= 4 ? cmykHex(nums[0], nums[1], nums[2], nums[3]) : void 0;
|
|
276
|
+
case "gray": return grayHex(nums[0]);
|
|
277
|
+
case "tint":
|
|
278
|
+
if (space?.tint) {
|
|
279
|
+
const hex = spaceColor(space.tint.transform(nums), space.tint.alternate);
|
|
280
|
+
if (hex !== void 0) return hex;
|
|
281
|
+
}
|
|
282
|
+
return grayHex(1 - Math.max(...nums));
|
|
283
|
+
default: return;
|
|
136
284
|
}
|
|
137
|
-
if (c.length === 3) return hex255(c[0] * 255, c[1] * 255, c[2] * 255);
|
|
138
|
-
const g = (c[0] ?? 0) * 255;
|
|
139
|
-
return hex255(g, g, g);
|
|
140
285
|
}
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
|
|
286
|
+
function clamp255(v) {
|
|
287
|
+
return Math.max(0, Math.min(255, Math.round(v * 255)));
|
|
288
|
+
}
|
|
289
|
+
function hex2(v) {
|
|
290
|
+
return clamp255(v).toString(16).padStart(2, "0");
|
|
291
|
+
}
|
|
292
|
+
/** Three channels, each 0..1, as 6 upper-case hex digits. */
|
|
293
|
+
function rgbHex(r, g, b) {
|
|
294
|
+
return (hex2(r) + hex2(g) + hex2(b)).toUpperCase();
|
|
295
|
+
}
|
|
296
|
+
/** One grey level, 0..1, as 6 upper-case hex digits. */
|
|
297
|
+
function grayHex(v) {
|
|
298
|
+
return rgbHex(v, v, v);
|
|
299
|
+
}
|
|
300
|
+
/** §8.6.4.4 — four inks, each 0..1, as 6 upper-case hex digits. */
|
|
301
|
+
function cmykHex(c, m, y, k) {
|
|
302
|
+
return rgbHex((1 - c) * (1 - k), (1 - m) * (1 - k), (1 - y) * (1 - k));
|
|
144
303
|
}
|
|
145
304
|
function pushStop(stops, offset, colorHex) {
|
|
146
305
|
const o = Math.max(0, Math.min(1, offset));
|
|
@@ -162,37 +321,70 @@ function pushStop(stops, offset, colorHex) {
|
|
|
162
321
|
* a green band at `ca` 0.6, meant to be read THROUGH: painted solid, the floor
|
|
163
322
|
* plan under each band disappears.
|
|
164
323
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
324
|
+
* §11.3.5 `/BM` comes off the same dictionary. A blend nothing downstream can
|
|
325
|
+
* perform is still worth knowing: `Multiply` and `Darken` both let dark ink
|
|
326
|
+
* under the paint show through, which is what a highlighter IS, and a mark that
|
|
327
|
+
* only darkens belongs UNDER the words rather than over them.
|
|
328
|
+
*
|
|
329
|
+
* @param file The owning file.
|
|
330
|
+
* @param resources The resource dictionary in force — a page's, or the form or
|
|
331
|
+
* annotation appearance's own, since `gs` resolves against
|
|
332
|
+
* whichever is current.
|
|
333
|
+
* @returns Name → the fill alpha and blend worth carrying, for the states that
|
|
334
|
+
* state one.
|
|
168
335
|
*/
|
|
169
|
-
function buildAlphaMap(file,
|
|
336
|
+
function buildAlphaMap(file, resources) {
|
|
170
337
|
const out = /* @__PURE__ */ new Map();
|
|
171
|
-
if (!
|
|
172
|
-
const states = file.get(
|
|
338
|
+
if (!resources) return out;
|
|
339
|
+
const states = file.get(resources, "ExtGState");
|
|
173
340
|
if (!(states instanceof Map)) return out;
|
|
174
341
|
for (const [name, value] of states) {
|
|
175
342
|
const state = file.resolve(value);
|
|
176
343
|
if (!(state instanceof Map)) continue;
|
|
177
344
|
const ca = file.resolve(state.get("ca") ?? PDF_NULL);
|
|
178
|
-
|
|
345
|
+
const alpha = typeof ca === "number" && ca >= 0 && ca <= 1 ? ca : void 0;
|
|
346
|
+
const bm = file.resolve(state.get("BM") ?? PDF_NULL);
|
|
347
|
+
const first = Array.isArray(bm) ? file.resolve(bm[0] ?? PDF_NULL) : bm;
|
|
348
|
+
const mode = first instanceof PdfName ? first.value : void 0;
|
|
349
|
+
const darkens = mode === "Multiply" || mode === "Darken";
|
|
350
|
+
const blend = mode !== void 0 && mode !== "Normal" && mode !== "Compatible" && !darkens ? mode : void 0;
|
|
351
|
+
const statesMask = state.has("SMask");
|
|
352
|
+
const masked = statesMask ? file.resolve(state.get("SMask") ?? PDF_NULL) instanceof Map : void 0;
|
|
353
|
+
if (alpha === void 0 && mode === void 0 && !statesMask) continue;
|
|
354
|
+
out.set(name, {
|
|
355
|
+
...alpha !== void 0 ? { alpha } : {},
|
|
356
|
+
...mode !== void 0 ? {
|
|
357
|
+
statesBlend: true,
|
|
358
|
+
darkens
|
|
359
|
+
} : {},
|
|
360
|
+
...blend !== void 0 ? { blend } : {},
|
|
361
|
+
...masked !== void 0 ? { masked } : {}
|
|
362
|
+
});
|
|
179
363
|
}
|
|
180
364
|
return out;
|
|
181
365
|
}
|
|
182
|
-
/**
|
|
183
|
-
|
|
366
|
+
/**
|
|
367
|
+
* The spaces a `/ColorSpace` resource dictionary names, by name.
|
|
368
|
+
*
|
|
369
|
+
* @param file The owning file.
|
|
370
|
+
* @param resources The resource dictionary in force.
|
|
371
|
+
* @returns Name → what the space comes to, for the spaces that are read.
|
|
372
|
+
*/
|
|
373
|
+
function buildColorSpaceMap(file, resources) {
|
|
184
374
|
const out = /* @__PURE__ */ new Map();
|
|
185
|
-
if (!
|
|
186
|
-
const spaces = file.get(
|
|
375
|
+
if (!resources) return out;
|
|
376
|
+
const spaces = file.get(resources, "ColorSpace");
|
|
187
377
|
if (!(spaces instanceof Map)) return out;
|
|
188
378
|
for (const [name, value] of spaces) {
|
|
189
|
-
const info =
|
|
379
|
+
const info = colorSpaceAt(file, file.resolve(value), 0);
|
|
190
380
|
if (info) out.set(name, info);
|
|
191
381
|
}
|
|
192
382
|
return out;
|
|
193
383
|
}
|
|
384
|
+
/** An alternate space may name another; this is where that stops. */
|
|
385
|
+
var MAX_ALTERNATE = 4;
|
|
194
386
|
/** What a colour space object comes to, or `undefined` for one not read. */
|
|
195
|
-
function
|
|
387
|
+
function colorSpaceAt(file, cs, depth) {
|
|
196
388
|
if (cs instanceof PdfName) return byFamily(cs.value, 0);
|
|
197
389
|
if (!Array.isArray(cs) || cs.length === 0) return void 0;
|
|
198
390
|
const head = file.resolve(cs[0]);
|
|
@@ -200,18 +392,96 @@ function colorSpaceInfo(file, cs) {
|
|
|
200
392
|
if (head.value === "ICCBased") {
|
|
201
393
|
const stream = file.resolve(cs[1] ?? PDF_NULL);
|
|
202
394
|
const n = stream instanceof PdfStream ? file.get(stream.dict, "N") : void 0;
|
|
203
|
-
|
|
395
|
+
const base = byFamily("ICCBased", typeof n === "number" ? n : 3);
|
|
396
|
+
if (!base || !(stream instanceof PdfStream)) return base;
|
|
397
|
+
let icc;
|
|
398
|
+
try {
|
|
399
|
+
icc = iccTransform(file.streamData(stream));
|
|
400
|
+
} catch {
|
|
401
|
+
icc = void 0;
|
|
402
|
+
}
|
|
403
|
+
return icc ? {
|
|
404
|
+
...base,
|
|
405
|
+
icc
|
|
406
|
+
} : base;
|
|
204
407
|
}
|
|
205
408
|
if (head.value === "Indexed") return;
|
|
409
|
+
if (head.value === "CalRGB") {
|
|
410
|
+
const params = file.resolve(cs[1] ?? PDF_NULL);
|
|
411
|
+
const cie = params instanceof Map ? cieParams(file, params, true) : void 0;
|
|
412
|
+
const base = {
|
|
413
|
+
kind: "rgb",
|
|
414
|
+
components: 3
|
|
415
|
+
};
|
|
416
|
+
return cie ? {
|
|
417
|
+
...base,
|
|
418
|
+
cie
|
|
419
|
+
} : base;
|
|
420
|
+
}
|
|
421
|
+
if (head.value === "CalGray") {
|
|
422
|
+
const params = file.resolve(cs[1] ?? PDF_NULL);
|
|
423
|
+
const cie = params instanceof Map ? cieParams(file, params, false) : void 0;
|
|
424
|
+
const base = {
|
|
425
|
+
kind: "gray",
|
|
426
|
+
components: 1
|
|
427
|
+
};
|
|
428
|
+
return cie ? {
|
|
429
|
+
...base,
|
|
430
|
+
cie
|
|
431
|
+
} : base;
|
|
432
|
+
}
|
|
206
433
|
if (head.value === "Separation" || head.value === "DeviceN") {
|
|
207
434
|
const names = file.resolve(cs[1] ?? PDF_NULL);
|
|
435
|
+
const n = head.value === "Separation" ? 1 : Array.isArray(names) ? names.length : 1;
|
|
436
|
+
const alternate = depth < MAX_ALTERNATE ? colorSpaceAt(file, file.resolve(cs[2] ?? PDF_NULL), depth + 1) : void 0;
|
|
437
|
+
const transform = readFunction(file, cs[3]);
|
|
438
|
+
if (alternate && transform && alternate.kind !== "tint") return {
|
|
439
|
+
kind: "tint",
|
|
440
|
+
components: n,
|
|
441
|
+
tint: {
|
|
442
|
+
transform,
|
|
443
|
+
alternate
|
|
444
|
+
}
|
|
445
|
+
};
|
|
208
446
|
return {
|
|
209
447
|
kind: "tint",
|
|
210
|
-
components:
|
|
448
|
+
components: n
|
|
211
449
|
};
|
|
212
450
|
}
|
|
213
451
|
return byFamily(head.value, 0);
|
|
214
452
|
}
|
|
453
|
+
/** §8.6.5.6/§8.6.5.7 — the white point, gamma and matrix a CIE space states. */
|
|
454
|
+
function cieParams(file, params, rgb) {
|
|
455
|
+
const nums = (v) => Array.isArray(v) ? v.map((x) => file.resolve(x)).filter((x) => typeof x === "number") : [];
|
|
456
|
+
const white = nums(file.get(params, "WhitePoint"));
|
|
457
|
+
if (white.length < 3 || !(white[1] > 0)) return void 0;
|
|
458
|
+
const gammaRaw = file.get(params, "Gamma");
|
|
459
|
+
const gamma = typeof gammaRaw === "number" ? [gammaRaw] : nums(gammaRaw);
|
|
460
|
+
const matrix = nums(file.get(params, "Matrix"));
|
|
461
|
+
return {
|
|
462
|
+
white: [
|
|
463
|
+
white[0],
|
|
464
|
+
white[1],
|
|
465
|
+
white[2]
|
|
466
|
+
],
|
|
467
|
+
gamma: gamma.length > 0 ? gamma : rgb ? [
|
|
468
|
+
1,
|
|
469
|
+
1,
|
|
470
|
+
1
|
|
471
|
+
] : [1],
|
|
472
|
+
...rgb ? { matrix: matrix.length === 9 ? matrix : [
|
|
473
|
+
1,
|
|
474
|
+
0,
|
|
475
|
+
0,
|
|
476
|
+
0,
|
|
477
|
+
1,
|
|
478
|
+
0,
|
|
479
|
+
0,
|
|
480
|
+
0,
|
|
481
|
+
1
|
|
482
|
+
] } : {}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
215
485
|
function byFamily(family, n) {
|
|
216
486
|
switch (family) {
|
|
217
487
|
case "DeviceGray":
|
|
@@ -246,4 +516,4 @@ function byFamily(family, n) {
|
|
|
246
516
|
}
|
|
247
517
|
}
|
|
248
518
|
//#endregion
|
|
249
|
-
export { buildAlphaMap, buildColorSpaceMap, buildShadingMap };
|
|
519
|
+
export { buildAlphaMap, buildColorSpaceMap, buildShadingMap, cmykHex, gradientShading, grayHex, rgbHex, sampledShading, shadingTypeOf, spaceColor };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One face's metrics: `name code width` triples, where the name is the
|
|
3
|
+
* glyph's own, the code is the one Adobe's built-in encoding gives it, and
|
|
4
|
+
* the width is its advance in 1000ths of an em.
|
|
5
|
+
*/
|
|
6
|
+
export declare const STANDARD_METRICS: Readonly<Record<string, string>>;
|
|
7
|
+
/** Every Courier is 600 wide, every glyph — it is a typewriter face. */
|
|
8
|
+
export declare const COURIER_WIDTH = 600;
|