reamkit 1.30.0 → 1.31.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 +60 -24
- package/dist/esm/core/converter/project.js +3 -1
- package/dist/esm/core/crypto/offcrypto.js +1 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +63 -0
- package/dist/esm/core/font/index.d.ts +2 -0
- package/dist/esm/core/font/ttf-build.d.ts +113 -0
- package/dist/esm/core/font/ttf-build.js +1224 -0
- package/dist/esm/core/font/ttf-subset.d.ts +19 -0
- package/dist/esm/core/font/ttf-subset.js +14 -1
- package/dist/esm/core/fonts/provider.d.ts +17 -0
- package/dist/esm/core/fonts/provider.js +27 -2
- package/dist/esm/core/fonts/remote-fonts.js +8 -1
- package/dist/esm/core/ir/flow.d.ts +71 -1
- package/dist/esm/core/numbering/index.d.ts +1 -1
- package/dist/esm/core/numbering/state.d.ts +11 -1
- package/dist/esm/core/numbering/state.js +10 -1
- package/dist/esm/core/style-cascade/resolver.js +35 -4
- package/dist/esm/core/style-cascade/types.d.ts +19 -1
- package/dist/esm/core/style-cascade/types.js +3 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/layout/page-doc.d.ts +10 -3
- package/dist/esm/layout/styled-layout.d.ts +48 -7
- package/dist/esm/layout/styled-layout.js +881 -119
- package/dist/esm/layout/turned-section.d.ts +38 -0
- package/dist/esm/layout/turned-section.js +193 -0
- package/dist/esm/pdf/styled-page-emitter.js +78 -2
- package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
- package/dist/esm/pdf-reader/cff-outline.js +169 -21
- package/dist/esm/pdf-reader/content.d.ts +53 -0
- package/dist/esm/pdf-reader/content.js +9 -1
- package/dist/esm/pdf-reader/display.d.ts +36 -0
- package/dist/esm/pdf-reader/display.js +66 -1
- package/dist/esm/pdf-reader/document.js +5 -1
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
- package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
- package/dist/esm/pdf-reader/encodings.d.ts +8 -0
- package/dist/esm/pdf-reader/encodings.js +25 -3
- package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
- package/dist/esm/pdf-reader/face-outlines.js +362 -0
- package/dist/esm/pdf-reader/figures.d.ts +52 -0
- package/dist/esm/pdf-reader/figures.js +433 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
- package/dist/esm/pdf-reader/flow-build.js +345 -26
- package/dist/esm/pdf-reader/font.js +321 -36
- package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
- package/dist/esm/pdf-reader/glyf-outline.js +135 -1
- package/dist/esm/pdf-reader/glyph-names.js +154 -1
- package/dist/esm/pdf-reader/layout.d.ts +93 -0
- package/dist/esm/pdf-reader/layout.js +1703 -214
- package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
- package/dist/esm/pdf-reader/page-numbers.js +167 -0
- package/dist/esm/pdf-reader/tagged.js +182 -23
- package/dist/esm/pdf-reader/text.d.ts +6 -3
- package/dist/esm/pdf-reader/text.js +98 -9
- package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
- package/dist/esm/pdf-reader/type1-outline.js +63 -8
- package/dist/esm/pdf-reader/vector.js +71 -1
- package/dist/esm/word/doc/doc-reader.js +6 -2
- package/dist/esm/word/doc/doc-text.d.ts +6 -0
- package/dist/esm/word/doc/doc-text.js +19 -1
- package/dist/esm/word/document-parser.d.ts +2 -2
- package/dist/esm/word/document-parser.js +11 -1
- package/dist/esm/word/docx-reader.js +5 -3
- package/dist/esm/word/docx-writer.js +207 -21
- package/dist/esm/word/drawing-parser.d.ts +5 -3
- package/dist/esm/word/drawing-parser.js +49 -8
- package/dist/esm/word/font-embed.d.ts +30 -0
- package/dist/esm/word/font-embed.js +173 -0
- package/dist/esm/word/font-table.d.ts +10 -0
- package/dist/esm/word/font-table.js +13 -1
- package/dist/esm/word/index.js +1 -1
- package/dist/esm/word/numbering-parser.d.ts +3 -1
- package/dist/esm/word/numbering-parser.js +2 -1
- package/dist/esm/word/paragraph-properties.d.ts +7 -6
- package/dist/esm/word/paragraph-properties.js +14 -2
- package/dist/esm/word/run-properties.js +26 -0
- package/package.json +8 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BodyElement } from '../core/document-model/index.js';
|
|
2
|
+
import { PageItem } from './page-doc.js';
|
|
3
|
+
/** The sheet a turned section prints on, and the margins it states for it. */
|
|
4
|
+
export interface Sheet {
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly marginLeft: number;
|
|
8
|
+
readonly marginRight: number;
|
|
9
|
+
readonly marginTop: number;
|
|
10
|
+
readonly marginBottom: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One item of a turned section's body, carried from the frame the text reads
|
|
14
|
+
* in onto the sheet: a point `(x, y)` of the frame (top-left origin, y down)
|
|
15
|
+
* lands at `(sheetWidth − y, x)` — the frame's top edge is the sheet's right
|
|
16
|
+
* edge, and its left edge the sheet's top.
|
|
17
|
+
*
|
|
18
|
+
* @param item The item as the frame's layout placed it.
|
|
19
|
+
* @param sheetWidth The sheet's width, which is the frame's height.
|
|
20
|
+
* @returns The same item on the sheet, turned a quarter clockwise.
|
|
21
|
+
*/
|
|
22
|
+
export declare function turnOntoSheet(item: PageItem, sheetWidth: number): PageItem;
|
|
23
|
+
/**
|
|
24
|
+
* A drawing anchored in a turned section, given the place in the text's frame
|
|
25
|
+
* from which turning the body carries it to where Word stands it: on the sheet,
|
|
26
|
+
* at the offsets its anchor states, upright.
|
|
27
|
+
*
|
|
28
|
+
* The anchor is resolved against the SHEET — its page, its margins and the
|
|
29
|
+
* bands beside them. What is measured from the text itself (a paragraph, a
|
|
30
|
+
* line, a column) has no place on the sheet before the text is laid out, and is
|
|
31
|
+
* measured from the margin instead. Only pictures and shapes are carried; a
|
|
32
|
+
* chart or a floating table turns with the text.
|
|
33
|
+
*
|
|
34
|
+
* @param el The body element, anchored or not.
|
|
35
|
+
* @param sheet The sheet the section prints on.
|
|
36
|
+
* @returns The element, its anchor restated in the frame and its turn backed off.
|
|
37
|
+
*/
|
|
38
|
+
export declare function floatIntoFrame(el: BodyElement, sheet: Sheet): BodyElement;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
//#region src/layout/turned-section.ts
|
|
3
|
+
/** A quarter turn in the 1/60000° a DrawingML rotation is stated in (§20.1.7.6). */
|
|
4
|
+
var QUARTER_60K = 90 * 6e4;
|
|
5
|
+
/**
|
|
6
|
+
* One item of a turned section's body, carried from the frame the text reads
|
|
7
|
+
* in onto the sheet: a point `(x, y)` of the frame (top-left origin, y down)
|
|
8
|
+
* lands at `(sheetWidth − y, x)` — the frame's top edge is the sheet's right
|
|
9
|
+
* edge, and its left edge the sheet's top.
|
|
10
|
+
*
|
|
11
|
+
* @param item The item as the frame's layout placed it.
|
|
12
|
+
* @param sheetWidth The sheet's width, which is the frame's height.
|
|
13
|
+
* @returns The same item on the sheet, turned a quarter clockwise.
|
|
14
|
+
*/
|
|
15
|
+
function turnOntoSheet(item, sheetWidth) {
|
|
16
|
+
const box = (x, y, w, h) => ({
|
|
17
|
+
x: pt(sheetWidth - y - h),
|
|
18
|
+
y: pt(x),
|
|
19
|
+
width: pt(h),
|
|
20
|
+
height: pt(w)
|
|
21
|
+
});
|
|
22
|
+
switch (item.type) {
|
|
23
|
+
case "line": {
|
|
24
|
+
const quarter = withinTurn((item.rotationDeg ?? 0) - 90);
|
|
25
|
+
const turned = quarter > 180 ? quarter - 360 : quarter;
|
|
26
|
+
const { rotationDeg: _flat, clip, ...rest } = item;
|
|
27
|
+
return {
|
|
28
|
+
...rest,
|
|
29
|
+
originX: pt(sheetWidth - item.baselineY),
|
|
30
|
+
baselineY: pt(item.originX),
|
|
31
|
+
...turned !== 0 ? { rotationDeg: turned } : {},
|
|
32
|
+
...clip ? { clip: box(clip.x, clip.y, clip.width, clip.height) } : {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
case "image": {
|
|
36
|
+
const cx = item.x + item.width / 2;
|
|
37
|
+
const cy = item.y + item.height / 2;
|
|
38
|
+
const turned = withinTurn((item.rotationDeg ?? 0) + 90);
|
|
39
|
+
const { rotationDeg: _flat, clip, ...rest } = item;
|
|
40
|
+
return {
|
|
41
|
+
...rest,
|
|
42
|
+
x: pt(sheetWidth - cy - item.width / 2),
|
|
43
|
+
y: pt(cx - item.height / 2),
|
|
44
|
+
...turned !== 0 ? { rotationDeg: turned } : {},
|
|
45
|
+
...clip ? { clip: {
|
|
46
|
+
paths: clip.paths,
|
|
47
|
+
transform: turnMatrix(clip.transform, sheetWidth)
|
|
48
|
+
} } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
case "shape": {
|
|
52
|
+
const shadow = item.shape.shadow;
|
|
53
|
+
return {
|
|
54
|
+
...item,
|
|
55
|
+
shape: {
|
|
56
|
+
...item.shape,
|
|
57
|
+
transform: turnMatrix(item.shape.transform, sheetWidth),
|
|
58
|
+
...shadow ? { shadow: {
|
|
59
|
+
...shadow,
|
|
60
|
+
dxPt: -shadow.dyPt,
|
|
61
|
+
dyPt: shadow.dxPt
|
|
62
|
+
} } : {}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
case "border": return {
|
|
67
|
+
...item,
|
|
68
|
+
...box(item.x, item.y, item.width, item.height),
|
|
69
|
+
side: TURNED_SIDE[item.side]
|
|
70
|
+
};
|
|
71
|
+
case "fill": return {
|
|
72
|
+
...item,
|
|
73
|
+
...box(item.x, item.y, item.width, item.height)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** An angle in degrees brought into one full turn, `[0, 360)`, with the float noise off. */
|
|
78
|
+
function withinTurn(deg) {
|
|
79
|
+
const t = (deg % 360 + 360) % 360;
|
|
80
|
+
return Math.abs(t) < 1e-9 || Math.abs(t - 360) < 1e-9 ? 0 : t;
|
|
81
|
+
}
|
|
82
|
+
/** Which edge of a box each edge becomes when the box turns a quarter clockwise. */
|
|
83
|
+
var TURNED_SIDE = {
|
|
84
|
+
top: "right",
|
|
85
|
+
right: "bottom",
|
|
86
|
+
bottom: "left",
|
|
87
|
+
left: "top"
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* A local→page matrix `[a b c d e f]` (x' = a·u + c·v + e, y' = b·u + d·v + f,
|
|
91
|
+
* page top-left, y down) followed by the turn onto the sheet.
|
|
92
|
+
*/
|
|
93
|
+
function turnMatrix(m, sheetWidth) {
|
|
94
|
+
return [
|
|
95
|
+
-m[1],
|
|
96
|
+
m[0],
|
|
97
|
+
-m[3],
|
|
98
|
+
m[2],
|
|
99
|
+
sheetWidth - m[5],
|
|
100
|
+
m[4]
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A drawing anchored in a turned section, given the place in the text's frame
|
|
105
|
+
* from which turning the body carries it to where Word stands it: on the sheet,
|
|
106
|
+
* at the offsets its anchor states, upright.
|
|
107
|
+
*
|
|
108
|
+
* The anchor is resolved against the SHEET — its page, its margins and the
|
|
109
|
+
* bands beside them. What is measured from the text itself (a paragraph, a
|
|
110
|
+
* line, a column) has no place on the sheet before the text is laid out, and is
|
|
111
|
+
* measured from the margin instead. Only pictures and shapes are carried; a
|
|
112
|
+
* chart or a floating table turns with the text.
|
|
113
|
+
*
|
|
114
|
+
* @param el The body element, anchored or not.
|
|
115
|
+
* @param sheet The sheet the section prints on.
|
|
116
|
+
* @returns The element, its anchor restated in the frame and its turn backed off.
|
|
117
|
+
*/
|
|
118
|
+
function floatIntoFrame(el, sheet) {
|
|
119
|
+
if (el.kind === "image" && el.image.float !== void 0) {
|
|
120
|
+
const { image } = el;
|
|
121
|
+
const float = image.float;
|
|
122
|
+
const placed = frameAnchor(float, image.width, image.height, sheet);
|
|
123
|
+
return {
|
|
124
|
+
...el,
|
|
125
|
+
image: {
|
|
126
|
+
...image,
|
|
127
|
+
float: placed,
|
|
128
|
+
rotation60k: backedOff(image.rotation60k)
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (el.kind === "shape" && el.shape.float !== void 0) {
|
|
133
|
+
const { shape } = el;
|
|
134
|
+
const float = shape.float;
|
|
135
|
+
const placed = frameAnchor(float, shape.width, shape.height, sheet);
|
|
136
|
+
return {
|
|
137
|
+
...el,
|
|
138
|
+
shape: {
|
|
139
|
+
...shape,
|
|
140
|
+
float: placed,
|
|
141
|
+
transform: {
|
|
142
|
+
...shape.transform,
|
|
143
|
+
rotation60k: backedOff(shape.transform?.rotation60k)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return el;
|
|
149
|
+
}
|
|
150
|
+
/** A clockwise turn with a quarter taken off, kept in the one full turn. */
|
|
151
|
+
function backedOff(rotation60k) {
|
|
152
|
+
const full = 360 * 6e4;
|
|
153
|
+
return (((rotation60k ?? 0) - QUARTER_60K) % full + full) % full;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The anchor a drawing of `w × h` needs in the frame for the body's turn to put
|
|
157
|
+
* it where `float` stands it on the sheet.
|
|
158
|
+
*/
|
|
159
|
+
function frameAnchor(float, w, h, sheet) {
|
|
160
|
+
const x = sheetX(float.posH, w, sheet);
|
|
161
|
+
const cx = sheetY(float.posV, h, sheet) + h / 2;
|
|
162
|
+
const cy = sheet.width - (x + w / 2);
|
|
163
|
+
const { posH: _h, posV: _v, ...rest } = float;
|
|
164
|
+
return {
|
|
165
|
+
...rest,
|
|
166
|
+
posH: {
|
|
167
|
+
relativeFrom: "page",
|
|
168
|
+
offsetPt: pt(cx - w / 2)
|
|
169
|
+
},
|
|
170
|
+
posV: {
|
|
171
|
+
relativeFrom: "page",
|
|
172
|
+
offsetPt: pt(cy - h / 2)
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** §20.4.3.3 — the box's left edge on the sheet. */
|
|
177
|
+
function sheetX(h, w, sheet) {
|
|
178
|
+
const textRight = sheet.width - sheet.marginRight;
|
|
179
|
+
const [base, span] = h?.relativeFrom === "page" ? [0, sheet.width] : h?.relativeFrom === "leftMargin" ? [0, sheet.marginLeft] : h?.relativeFrom === "rightMargin" ? [textRight, sheet.marginRight] : [sheet.marginLeft, textRight - sheet.marginLeft];
|
|
180
|
+
if (h?.align === "center") return base + (span - w) / 2;
|
|
181
|
+
if (h?.align === "right") return base + span - w;
|
|
182
|
+
return base + (h?.offsetPt ?? 0);
|
|
183
|
+
}
|
|
184
|
+
/** §20.4.3.4 — the box's top edge on the sheet. */
|
|
185
|
+
function sheetY(v, h, sheet) {
|
|
186
|
+
const textBottom = sheet.height - sheet.marginBottom;
|
|
187
|
+
const [base, span] = v?.relativeFrom === "page" ? [0, sheet.height] : v?.relativeFrom === "topMargin" ? [0, sheet.marginTop] : v?.relativeFrom === "bottomMargin" ? [textBottom, sheet.marginBottom] : [sheet.marginTop, textBottom - sheet.marginTop];
|
|
188
|
+
if (v?.align === "center") return base + (span - h) / 2;
|
|
189
|
+
if (v?.align === "bottom") return base + span - h;
|
|
190
|
+
return base + (v?.offsetPt ?? 0);
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { floatIntoFrame, turnOntoSheet };
|
|
@@ -676,12 +676,13 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
676
676
|
}
|
|
677
677
|
lastStroke = strokeHex;
|
|
678
678
|
}
|
|
679
|
-
const
|
|
679
|
+
const scale = (tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1);
|
|
680
|
+
const tz = Math.round(scale * 100);
|
|
680
681
|
if (tz !== lastTz) {
|
|
681
682
|
out.push(`${formatNumber(tz)} Tz`);
|
|
682
683
|
lastTz = tz;
|
|
683
684
|
}
|
|
684
|
-
const tc = tok.resolvedRun.letterSpacingPt ?? 0;
|
|
685
|
+
const tc = (tok.resolvedRun.letterSpacingPt ?? 0) / (tz > 0 ? tz / 100 : 1);
|
|
685
686
|
if (tc !== lastTc) {
|
|
686
687
|
out.push(`${formatNumber(tc)} Tc`);
|
|
687
688
|
lastTc = tc;
|
|
@@ -888,7 +889,69 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
888
889
|
lastColor = "";
|
|
889
890
|
lastTc = 0;
|
|
890
891
|
};
|
|
892
|
+
const emitTurned = (cmd) => {
|
|
893
|
+
if (inBT) {
|
|
894
|
+
out.push("ET");
|
|
895
|
+
inBT = false;
|
|
896
|
+
}
|
|
897
|
+
const rad = (cmd.rotationDeg ?? 0) * Math.PI / 180;
|
|
898
|
+
const cos = Math.cos(rad);
|
|
899
|
+
const sin = Math.sin(rad);
|
|
900
|
+
const ox = cmd.originX;
|
|
901
|
+
const oy = H - cmd.baselineY;
|
|
902
|
+
out.push("q");
|
|
903
|
+
out.push(`${formatNumber(cos)} ${formatNumber(sin)} ${formatNumber(-sin)} ${formatNumber(cos)} ${formatNumber(ox - ox * cos + oy * sin)} ${formatNumber(oy - ox * sin - oy * cos)} cm`);
|
|
904
|
+
lastFont = "";
|
|
905
|
+
lastFauxWidth = 0;
|
|
906
|
+
lastStroke = void 0;
|
|
907
|
+
lastTz = 100;
|
|
908
|
+
lastSize = -1;
|
|
909
|
+
lastColor = "";
|
|
910
|
+
lastTc = 0;
|
|
911
|
+
const from = links.length;
|
|
912
|
+
const { rotationDeg: _turned, ...flat } = cmd;
|
|
913
|
+
out.push("BT");
|
|
914
|
+
inBT = true;
|
|
915
|
+
emitOneLine(flat);
|
|
916
|
+
if (inBT) {
|
|
917
|
+
out.push("ET");
|
|
918
|
+
inBT = false;
|
|
919
|
+
}
|
|
920
|
+
out.push("Q");
|
|
921
|
+
lastFont = "";
|
|
922
|
+
lastFauxWidth = 0;
|
|
923
|
+
lastStroke = void 0;
|
|
924
|
+
lastTz = 100;
|
|
925
|
+
lastSize = -1;
|
|
926
|
+
lastColor = "";
|
|
927
|
+
lastTc = 0;
|
|
928
|
+
for (let k = from; k < links.length; k++) {
|
|
929
|
+
const link = links[k];
|
|
930
|
+
const [x0, y0, x1, y1] = link.rect;
|
|
931
|
+
const corners = [
|
|
932
|
+
[x0, y0],
|
|
933
|
+
[x1, y0],
|
|
934
|
+
[x1, y1],
|
|
935
|
+
[x0, y1]
|
|
936
|
+
].map(([x, y]) => [ox + (x - ox) * cos - (y - oy) * sin, oy + (x - ox) * sin + (y - oy) * cos]);
|
|
937
|
+
const xs = corners.map((c) => c[0]);
|
|
938
|
+
const ys = corners.map((c) => c[1]);
|
|
939
|
+
links[k] = {
|
|
940
|
+
...link,
|
|
941
|
+
rect: [
|
|
942
|
+
Math.min(...xs),
|
|
943
|
+
Math.min(...ys),
|
|
944
|
+
Math.max(...xs),
|
|
945
|
+
Math.max(...ys)
|
|
946
|
+
]
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
};
|
|
891
950
|
const emitOneLine = (cmd) => {
|
|
951
|
+
if (cmd.rotationDeg && cmd.warp === void 0 && !settableInOneMatrix(cmd.line)) {
|
|
952
|
+
emitTurned(cmd);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
892
955
|
const line = cmd.line;
|
|
893
956
|
const originX = cmd.originX;
|
|
894
957
|
const baselineY = H - cmd.baselineY;
|
|
@@ -1397,6 +1460,19 @@ function rotateAboutCm(cx, cy, deg) {
|
|
|
1397
1460
|
const s = Math.sin(rad);
|
|
1398
1461
|
return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
|
|
1399
1462
|
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
|
|
1465
|
+
* on its own — a tab, a stretched space, a picture, a formula, a right-to-left
|
|
1466
|
+
* run, a rise, a faked italic — and nothing drawn beside its glyphs, a run's
|
|
1467
|
+
* shading, a highlight or a rule. Only such a line can be turned by its matrix
|
|
1468
|
+
* alone; any other is placed token by token in page space.
|
|
1469
|
+
*
|
|
1470
|
+
* @param line The laid-out line.
|
|
1471
|
+
* @returns True when a single `Tm` sets the whole of it.
|
|
1472
|
+
*/
|
|
1473
|
+
function settableInOneMatrix(line) {
|
|
1474
|
+
return computeJustifyExtra(line) === 0 && line.tokens.every((t) => t.kind === "text" && t.tab !== true && t.bidiLevel % 2 === 0 && t.risePt === void 0 && t.synthetic?.italic !== true && t.highlight !== true && t.resolvedRun.shadingColorHex === void 0 && t.resolvedRun.underline === "none" && !t.resolvedRun.strike);
|
|
1475
|
+
}
|
|
1400
1476
|
function computeJustifyExtra(line) {
|
|
1401
1477
|
const alignment = line.resolved.alignment;
|
|
1402
1478
|
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
@@ -32,5 +32,32 @@ export declare function cffNameToGid(program: Uint8Array): Map<string, number> |
|
|
|
32
32
|
* CID-keyed or cannot be read.
|
|
33
33
|
*/
|
|
34
34
|
export declare function cffCidToGid(program: Uint8Array): Map<number, number> | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* How far the pen moves after a name-keyed CFF program's `space`, in
|
|
37
|
+
* thousandths of an em — the width a writer gives the space a page never
|
|
38
|
+
* showed, which is the one it was typeset with.
|
|
39
|
+
*
|
|
40
|
+
* @param program The raw CFF bytes.
|
|
41
|
+
* @returns The advance, or `undefined` where the program has no `space` or its
|
|
42
|
+
* width cannot be read off the charstring.
|
|
43
|
+
*/
|
|
44
|
+
export declare function cffSpaceAdvance(program: Uint8Array): number | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The embedding a CFF program's licence allows: CFF has no OS/2 table, and
|
|
47
|
+
* Adobe writes the OS/2 `fsType` into the Top DICT's `PostScript` string
|
|
48
|
+
* instead (TN 5176 §9, key 12 21) as `/FSType 8 def`.
|
|
49
|
+
*
|
|
50
|
+
* @param program The raw CFF bytes.
|
|
51
|
+
* @returns The value, or `undefined` where the program states none.
|
|
52
|
+
*/
|
|
53
|
+
export declare function cffFsType(program: Uint8Array): number | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* The `/FSType n def` a PostScript font program writes where an OpenType one
|
|
56
|
+
* has its OS/2 field (Adobe TN 5176 §9; a Type 1 font's `FontInfo` alike).
|
|
57
|
+
*
|
|
58
|
+
* @param text The PostScript to search.
|
|
59
|
+
* @returns The value, or `undefined` where the text states none.
|
|
60
|
+
*/
|
|
61
|
+
export declare function postScriptFsType(text: string): number | undefined;
|
|
35
62
|
/** The `CFF ` table of an OpenType wrapper, where the program is one. */
|
|
36
63
|
export declare function openTypeCff(program: Uint8Array): Uint8Array | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { standardEncodingTable } from "./encodings.js";
|
|
1
2
|
//#region src/pdf-reader/cff-outline.ts
|
|
2
3
|
/**
|
|
3
4
|
* Prepare an embedded CFF program for outline reading.
|
|
@@ -69,6 +70,90 @@ function cffCidToGid(program) {
|
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* How far the pen moves after a name-keyed CFF program's `space`, in
|
|
75
|
+
* thousandths of an em — the width a writer gives the space a page never
|
|
76
|
+
* showed, which is the one it was typeset with.
|
|
77
|
+
*
|
|
78
|
+
* @param program The raw CFF bytes.
|
|
79
|
+
* @returns The advance, or `undefined` where the program has no `space` or its
|
|
80
|
+
* width cannot be read off the charstring.
|
|
81
|
+
*/
|
|
82
|
+
function cffSpaceAdvance(program) {
|
|
83
|
+
try {
|
|
84
|
+
const font = parseCff(program);
|
|
85
|
+
const gid = font?.nameToGid?.get("space");
|
|
86
|
+
if (!font || gid === void 0) return void 0;
|
|
87
|
+
const width = glyphWidth(font, gid);
|
|
88
|
+
return width === void 0 ? void 0 : width * font.scale * 1e3;
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* TN 5177 §4.1 — a glyph's width: `nominalWidthX` plus the operand a
|
|
95
|
+
* charstring puts before its first stack-clearing operator, or
|
|
96
|
+
* `defaultWidthX` where it puts none. Whether one is there is a matter of
|
|
97
|
+
* COUNT — one more than the operator takes.
|
|
98
|
+
*/
|
|
99
|
+
function glyphWidth(font, gid) {
|
|
100
|
+
const code = font.charStrings[gid];
|
|
101
|
+
if (!code) return void 0;
|
|
102
|
+
const { defaultWidth, nominalWidth } = font.widthsOf(gid);
|
|
103
|
+
const stack = [];
|
|
104
|
+
for (let i = 0; i < code.length;) {
|
|
105
|
+
const b = code[i++];
|
|
106
|
+
if (b === 28) {
|
|
107
|
+
stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
|
|
108
|
+
i += 2;
|
|
109
|
+
} else if (b >= 32 && b <= 246) stack.push(b - 139);
|
|
110
|
+
else if (b >= 247 && b <= 250) stack.push((b - 247) * 256 + code[i++] + 108);
|
|
111
|
+
else if (b >= 251 && b <= 254) stack.push(-(b - 251) * 256 - code[i++] - 108);
|
|
112
|
+
else if (b === 255) {
|
|
113
|
+
stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
|
|
114
|
+
i += 4;
|
|
115
|
+
} else {
|
|
116
|
+
const takes = b === 21 ? 2 : b === 22 || b === 4 ? 1 : b === 14 ? stack.length >= 4 ? 4 : 0 : -1;
|
|
117
|
+
if (b === 1 || b === 3 || b === 18 || b === 23 || b === 19 || b === 20) return stack.length % 2 === 1 ? nominalWidth + stack[0] : defaultWidth;
|
|
118
|
+
if (takes < 0) return void 0;
|
|
119
|
+
return stack.length > takes ? nominalWidth + stack[0] : defaultWidth;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The embedding a CFF program's licence allows: CFF has no OS/2 table, and
|
|
125
|
+
* Adobe writes the OS/2 `fsType` into the Top DICT's `PostScript` string
|
|
126
|
+
* instead (TN 5176 §9, key 12 21) as `/FSType 8 def`.
|
|
127
|
+
*
|
|
128
|
+
* @param program The raw CFF bytes.
|
|
129
|
+
* @returns The value, or `undefined` where the program states none.
|
|
130
|
+
*/
|
|
131
|
+
function cffFsType(program) {
|
|
132
|
+
try {
|
|
133
|
+
const names = readIndex(program, program[2] ?? 4);
|
|
134
|
+
const tops = names ? readIndex(program, names.end) : void 0;
|
|
135
|
+
const strings = tops ? readIndex(program, tops.end) : void 0;
|
|
136
|
+
const top = tops?.items[0];
|
|
137
|
+
if (!top || !strings) return void 0;
|
|
138
|
+
const sid = parseDict(top).get(1221)?.[0];
|
|
139
|
+
if (sid === void 0 || sid < STANDARD_STRINGS.length) return void 0;
|
|
140
|
+
const own = strings.items[sid - STANDARD_STRINGS.length];
|
|
141
|
+
return own ? postScriptFsType(new TextDecoder("latin1").decode(own)) : void 0;
|
|
142
|
+
} catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The `/FSType n def` a PostScript font program writes where an OpenType one
|
|
148
|
+
* has its OS/2 field (Adobe TN 5176 §9; a Type 1 font's `FontInfo` alike).
|
|
149
|
+
*
|
|
150
|
+
* @param text The PostScript to search.
|
|
151
|
+
* @returns The value, or `undefined` where the text states none.
|
|
152
|
+
*/
|
|
153
|
+
function postScriptFsType(text) {
|
|
154
|
+
const m = /\/FSType\s+(\d+)\s+def/u.exec(text);
|
|
155
|
+
return m ? Number(m[1]) : void 0;
|
|
156
|
+
}
|
|
72
157
|
/** The `CFF ` table of an OpenType wrapper, where the program is one. */
|
|
73
158
|
function openTypeCff(program) {
|
|
74
159
|
if (program.length < 12) return void 0;
|
|
@@ -102,27 +187,52 @@ function parseCff(program) {
|
|
|
102
187
|
if (!charStrings) return void 0;
|
|
103
188
|
const matrix = top.get(1207);
|
|
104
189
|
const scale = matrix && matrix.length >= 4 ? matrix[0] ?? .001 : .001;
|
|
105
|
-
const
|
|
190
|
+
const privateOf = (dict) => {
|
|
191
|
+
const none = {
|
|
192
|
+
subrs: [],
|
|
193
|
+
widths: {
|
|
194
|
+
defaultWidth: 0,
|
|
195
|
+
nominalWidth: 0
|
|
196
|
+
}
|
|
197
|
+
};
|
|
106
198
|
const priv = dict.get(18);
|
|
107
|
-
if (!priv || priv.length < 2) return
|
|
199
|
+
if (!priv || priv.length < 2) return none;
|
|
108
200
|
const size = priv[0];
|
|
109
201
|
const offset = priv[1];
|
|
110
|
-
if (offset < 0 || offset + size > program.length) return
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
202
|
+
if (offset < 0 || offset + size > program.length) return none;
|
|
203
|
+
const inner = parseDict(program.subarray(offset, offset + size));
|
|
204
|
+
const widths = {
|
|
205
|
+
defaultWidth: inner.get(20)?.[0] ?? 0,
|
|
206
|
+
nominalWidth: inner.get(21)?.[0] ?? 0
|
|
207
|
+
};
|
|
208
|
+
const subrsAt = inner.get(19)?.[0];
|
|
209
|
+
if (subrsAt === void 0) return {
|
|
210
|
+
subrs: [],
|
|
211
|
+
widths
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
subrs: readIndex(program, offset + subrsAt)?.items ?? [],
|
|
215
|
+
widths
|
|
216
|
+
};
|
|
114
217
|
};
|
|
115
218
|
const isCid = top.has(1230);
|
|
116
219
|
let localSubrs;
|
|
220
|
+
let widthsOf;
|
|
117
221
|
if (isCid) {
|
|
118
222
|
const fdArrayAt = top.get(1236)?.[0];
|
|
119
223
|
const fdSelectAt = top.get(1237)?.[0];
|
|
120
|
-
const
|
|
224
|
+
const privatePerFd = (fdArrayAt !== void 0 ? readIndex(program, fdArrayAt)?.items ?? [] : []).map((fd) => privateOf(parseDict(fd)));
|
|
121
225
|
const select = fdSelectAt !== void 0 ? readFdSelect(program, fdSelectAt, charStrings.items.length) : void 0;
|
|
122
|
-
|
|
226
|
+
const privateFor = (gid) => privatePerFd[select ? select[gid] ?? 0 : 0];
|
|
227
|
+
localSubrs = (gid) => privateFor(gid)?.subrs ?? [];
|
|
228
|
+
widthsOf = (gid) => privateFor(gid)?.widths ?? {
|
|
229
|
+
defaultWidth: 0,
|
|
230
|
+
nominalWidth: 0
|
|
231
|
+
};
|
|
123
232
|
} else {
|
|
124
|
-
const only =
|
|
125
|
-
localSubrs = () => only;
|
|
233
|
+
const only = privateOf(top);
|
|
234
|
+
localSubrs = () => only.subrs;
|
|
235
|
+
widthsOf = () => only.widths;
|
|
126
236
|
}
|
|
127
237
|
const charsetAt = top.get(15)?.[0];
|
|
128
238
|
const sids = charsetAt !== void 0 && charsetAt > 2 ? readCharset(program, charsetAt, charStrings.items.length) : void 0;
|
|
@@ -134,7 +244,8 @@ function parseCff(program) {
|
|
|
134
244
|
localSubrs,
|
|
135
245
|
scale,
|
|
136
246
|
...cidToGid ? { cidToGid } : {},
|
|
137
|
-
...nameToGid ? { nameToGid } : {}
|
|
247
|
+
...nameToGid ? { nameToGid } : {},
|
|
248
|
+
widthsOf
|
|
138
249
|
};
|
|
139
250
|
}
|
|
140
251
|
/**
|
|
@@ -689,9 +800,10 @@ function bias(subrs) {
|
|
|
689
800
|
/** How deep a charstring may call, and how many operators it may run. */
|
|
690
801
|
var MAX_CALL_DEPTH = 10;
|
|
691
802
|
var MAX_STEPS = 65536;
|
|
692
|
-
function runGlyph(font, gid) {
|
|
803
|
+
function runGlyph(font, gid, part = false) {
|
|
693
804
|
const program = font.charStrings[gid];
|
|
694
805
|
if (!program) return void 0;
|
|
806
|
+
let seac;
|
|
695
807
|
const local = font.localSubrs(gid);
|
|
696
808
|
const localBias = bias(local);
|
|
697
809
|
const globalBias = bias(font.globalSubrs);
|
|
@@ -885,7 +997,16 @@ function runGlyph(font, gid) {
|
|
|
885
997
|
}
|
|
886
998
|
case 11: return true;
|
|
887
999
|
case 14:
|
|
888
|
-
takeWidth(stack.length
|
|
1000
|
+
takeWidth(stack.length >= 4 ? 4 : 0);
|
|
1001
|
+
if (stack.length >= 4) {
|
|
1002
|
+
const [adx = 0, ady = 0, bchar = 0, achar = 0] = stack;
|
|
1003
|
+
seac = {
|
|
1004
|
+
adx,
|
|
1005
|
+
ady,
|
|
1006
|
+
bchar,
|
|
1007
|
+
achar
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
889
1010
|
if (open) out.push({ op: "close" });
|
|
890
1011
|
open = false;
|
|
891
1012
|
return false;
|
|
@@ -919,8 +1040,35 @@ function runGlyph(font, gid) {
|
|
|
919
1040
|
}
|
|
920
1041
|
run(program, 0);
|
|
921
1042
|
if (out.length > 0 && out[out.length - 1]?.op !== "close") out.push({ op: "close" });
|
|
922
|
-
|
|
923
|
-
|
|
1043
|
+
const own = out.map((seg) => scaleSeg(seg, font.scale));
|
|
1044
|
+
if (seac && !part) {
|
|
1045
|
+
const at = seac;
|
|
1046
|
+
const glyphOf = (code) => {
|
|
1047
|
+
const name = standardEncodingTable().get(code);
|
|
1048
|
+
const gid = name !== void 0 ? font.nameToGid?.get(name) : void 0;
|
|
1049
|
+
return gid !== void 0 ? runGlyph(font, gid, true) ?? [] : [];
|
|
1050
|
+
};
|
|
1051
|
+
own.push(...glyphOf(at.bchar), ...glyphOf(at.achar).map((seg) => shiftSeg(seg, at.adx * font.scale, at.ady * font.scale)));
|
|
1052
|
+
}
|
|
1053
|
+
return own.length > 0 ? own : void 0;
|
|
1054
|
+
}
|
|
1055
|
+
/** A segment moved by `(dx, dy)`: where a `seac` puts its accent. */
|
|
1056
|
+
function shiftSeg(seg, dx, dy) {
|
|
1057
|
+
if (seg.op === "close") return seg;
|
|
1058
|
+
if (seg.op === "cubic") return {
|
|
1059
|
+
op: "cubic",
|
|
1060
|
+
x1: seg.x1 + dx,
|
|
1061
|
+
y1: seg.y1 + dy,
|
|
1062
|
+
x2: seg.x2 + dx,
|
|
1063
|
+
y2: seg.y2 + dy,
|
|
1064
|
+
x: seg.x + dx,
|
|
1065
|
+
y: seg.y + dy
|
|
1066
|
+
};
|
|
1067
|
+
return {
|
|
1068
|
+
op: seg.op,
|
|
1069
|
+
x: seg.x + dx,
|
|
1070
|
+
y: seg.y + dy
|
|
1071
|
+
};
|
|
924
1072
|
}
|
|
925
1073
|
function runEscape(op, stack, trans) {
|
|
926
1074
|
switch (op) {
|
|
@@ -1030,8 +1178,8 @@ function flexCurves(op, stack, x0, y0) {
|
|
|
1030
1178
|
]);
|
|
1031
1179
|
push([
|
|
1032
1180
|
n(4),
|
|
1033
|
-
n(
|
|
1034
|
-
|
|
1181
|
+
n(5),
|
|
1182
|
+
n(6)
|
|
1035
1183
|
], [
|
|
1036
1184
|
0,
|
|
1037
1185
|
startY - y,
|
|
@@ -1051,12 +1199,12 @@ function flexCurves(op, stack, x0, y0) {
|
|
|
1051
1199
|
]);
|
|
1052
1200
|
push([
|
|
1053
1201
|
n(5),
|
|
1054
|
-
n(
|
|
1202
|
+
n(6),
|
|
1055
1203
|
n(8)
|
|
1056
1204
|
], [
|
|
1057
1205
|
0,
|
|
1058
|
-
n(
|
|
1059
|
-
startY - (y + n(
|
|
1206
|
+
n(7),
|
|
1207
|
+
startY - (y + n(7))
|
|
1060
1208
|
]);
|
|
1061
1209
|
y = startY;
|
|
1062
1210
|
} else if (op === 37) {
|
|
@@ -1119,4 +1267,4 @@ function scaleSeg(seg, k) {
|
|
|
1119
1267
|
};
|
|
1120
1268
|
}
|
|
1121
1269
|
//#endregion
|
|
1122
|
-
export { cffCidToGid, cffNameToGid, cffOutlineSource, openTypeCff };
|
|
1270
|
+
export { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff, postScriptFsType };
|