@hufe921/jsofd 1.0.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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/jsofd.cjs +4388 -0
- package/dist/jsofd.d.cts +1127 -0
- package/dist/jsofd.d.ts +1127 -0
- package/dist/jsofd.esm.js +4367 -0
- package/dist/jsofd.umd.js +4390 -0
- package/dist/pdf.cjs +5149 -0
- package/dist/pdf.d.cts +1122 -0
- package/dist/pdf.d.ts +1122 -0
- package/dist/pdf.esm.js +5118 -0
- package/package.json +82 -0
|
@@ -0,0 +1,4390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var jsOFD = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
26
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
27
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
28
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
BUILTIN_FONTS: () => BUILTIN_FONTS,
|
|
34
|
+
FONT_ALIASES: () => FONT_ALIASES,
|
|
35
|
+
JSOFD_VERSION: () => JSOFD_VERSION,
|
|
36
|
+
OFD_NAMESPACE: () => OFD_NAMESPACE,
|
|
37
|
+
PAGE_FORMATS_MM: () => PAGE_FORMATS_MM,
|
|
38
|
+
UNIT_FACTORS: () => UNIT_FACTORS,
|
|
39
|
+
default: () => src_default,
|
|
40
|
+
jsOFD: () => jsOFD,
|
|
41
|
+
version: () => version
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// src/utils.ts
|
|
45
|
+
var MM_PER_PT = 25.4 / 72;
|
|
46
|
+
var PT_PER_MM = 72 / 25.4;
|
|
47
|
+
var IS_NODE = typeof process !== "undefined" && !!process.versions && !!process.versions.node;
|
|
48
|
+
function nodeRequire(name) {
|
|
49
|
+
try {
|
|
50
|
+
const req = new Function("m", "return require(m)");
|
|
51
|
+
return req(name);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function fmt(n) {
|
|
57
|
+
if (typeof n !== "number" || !isFinite(n)) n = 0;
|
|
58
|
+
let s = n.toFixed(4);
|
|
59
|
+
if (s.includes(".")) s = s.replace(/\.?0+$/, "");
|
|
60
|
+
if (s === "-0") s = "0";
|
|
61
|
+
return s;
|
|
62
|
+
}
|
|
63
|
+
function escapeXmlText(s) {
|
|
64
|
+
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
65
|
+
}
|
|
66
|
+
function escapeXmlAttr(s) {
|
|
67
|
+
return escapeXmlText(s).replace(/"/g, """).replace(/'/g, "'");
|
|
68
|
+
}
|
|
69
|
+
function randomHex32() {
|
|
70
|
+
let s = "";
|
|
71
|
+
for (let i = 0; i < 32; i++) {
|
|
72
|
+
s += "0123456789abcdef"[Math.random() * 16 | 0];
|
|
73
|
+
}
|
|
74
|
+
return s;
|
|
75
|
+
}
|
|
76
|
+
function todayStr(d) {
|
|
77
|
+
const date = d instanceof Date ? d : /* @__PURE__ */ new Date();
|
|
78
|
+
const m = date.getMonth() + 1;
|
|
79
|
+
const day = date.getDate();
|
|
80
|
+
const mm = m < 10 ? "0" + m : String(m);
|
|
81
|
+
const dd = day < 10 ? "0" + day : String(day);
|
|
82
|
+
return `${date.getFullYear()}-${mm}-${dd}`;
|
|
83
|
+
}
|
|
84
|
+
function utf8Encode(str) {
|
|
85
|
+
if (typeof TextEncoder !== "undefined") {
|
|
86
|
+
return new TextEncoder().encode(str);
|
|
87
|
+
}
|
|
88
|
+
const out = [];
|
|
89
|
+
for (let i = 0; i < str.length; i++) {
|
|
90
|
+
const c = str.charCodeAt(i);
|
|
91
|
+
if (c < 128) out.push(c);
|
|
92
|
+
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
93
|
+
else if (c >= 55296 && c < 56320 && i + 1 < str.length) {
|
|
94
|
+
const c2 = str.charCodeAt(++i);
|
|
95
|
+
const cp = 65536 + ((c & 1023) << 10) + (c2 & 1023);
|
|
96
|
+
out.push(
|
|
97
|
+
240 | cp >> 18,
|
|
98
|
+
128 | cp >> 12 & 63,
|
|
99
|
+
128 | cp >> 6 & 63,
|
|
100
|
+
128 | cp & 63
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return new Uint8Array(out);
|
|
107
|
+
}
|
|
108
|
+
function u8FromBuffer(buf) {
|
|
109
|
+
if (buf instanceof Uint8Array) return buf;
|
|
110
|
+
if (typeof ArrayBuffer !== "undefined" && buf instanceof ArrayBuffer) {
|
|
111
|
+
return new Uint8Array(buf);
|
|
112
|
+
}
|
|
113
|
+
throw new Error("jsofd: unrecognized binary data type");
|
|
114
|
+
}
|
|
115
|
+
function b64Encode(u8) {
|
|
116
|
+
let s = "";
|
|
117
|
+
for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]);
|
|
118
|
+
return btoa(s);
|
|
119
|
+
}
|
|
120
|
+
function b64Decode(str) {
|
|
121
|
+
const s = String(str).replace(/^data:.*?;base64,/, "").replace(/[=\s]+$/, "");
|
|
122
|
+
const bin = atob(s);
|
|
123
|
+
const u8 = new Uint8Array(bin.length);
|
|
124
|
+
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
|
|
125
|
+
return u8;
|
|
126
|
+
}
|
|
127
|
+
var CRC_TABLE = (() => {
|
|
128
|
+
const t = new Uint32Array(256);
|
|
129
|
+
for (let n = 0; n < 256; n++) {
|
|
130
|
+
let c = n;
|
|
131
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
132
|
+
t[n] = c >>> 0;
|
|
133
|
+
}
|
|
134
|
+
return t;
|
|
135
|
+
})();
|
|
136
|
+
function crc32(u8) {
|
|
137
|
+
let c = 4294967295;
|
|
138
|
+
for (let i = 0; i < u8.length; i++) {
|
|
139
|
+
c = CRC_TABLE[(c ^ u8[i]) & 255] ^ c >>> 8;
|
|
140
|
+
}
|
|
141
|
+
return (c ^ 4294967295) >>> 0;
|
|
142
|
+
}
|
|
143
|
+
function fnv1a(u8) {
|
|
144
|
+
let h = 2166136261;
|
|
145
|
+
for (let i = 0; i < u8.length; i++) {
|
|
146
|
+
h ^= u8[i];
|
|
147
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
148
|
+
}
|
|
149
|
+
return ("0000000" + h.toString(16)).slice(-8);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/formats.ts
|
|
153
|
+
var PAGE_FORMATS_MM = {
|
|
154
|
+
a0: [841, 1189],
|
|
155
|
+
a1: [594, 841],
|
|
156
|
+
a2: [420, 594],
|
|
157
|
+
a3: [297, 420],
|
|
158
|
+
a4: [210, 297],
|
|
159
|
+
a5: [148, 210],
|
|
160
|
+
a6: [105, 148],
|
|
161
|
+
a7: [74, 105],
|
|
162
|
+
a8: [52, 74],
|
|
163
|
+
a9: [37, 52],
|
|
164
|
+
a10: [26, 37],
|
|
165
|
+
b0: [1e3, 1414],
|
|
166
|
+
b1: [707, 1e3],
|
|
167
|
+
b2: [500, 707],
|
|
168
|
+
b3: [353, 500],
|
|
169
|
+
b4: [250, 353],
|
|
170
|
+
b5: [176, 250],
|
|
171
|
+
b6: [125, 176],
|
|
172
|
+
b7: [88, 125],
|
|
173
|
+
b8: [62, 88],
|
|
174
|
+
b9: [44, 62],
|
|
175
|
+
b10: [31, 44],
|
|
176
|
+
c0: [917, 1297],
|
|
177
|
+
c1: [648, 917],
|
|
178
|
+
c2: [458, 648],
|
|
179
|
+
c3: [324, 458],
|
|
180
|
+
c4: [229, 324],
|
|
181
|
+
c5: [162, 229],
|
|
182
|
+
c6: [114, 162],
|
|
183
|
+
c7: [81, 114],
|
|
184
|
+
c8: [57, 81],
|
|
185
|
+
c9: [40, 57],
|
|
186
|
+
c10: [28, 40],
|
|
187
|
+
dl: [110, 220],
|
|
188
|
+
letter: [215.9, 279.4],
|
|
189
|
+
"junior-legal": [203.2, 127],
|
|
190
|
+
"government-letter": [203.2, 266.7],
|
|
191
|
+
legal: [215.9, 355.6],
|
|
192
|
+
"government-legal": [215.9, 330.2],
|
|
193
|
+
ledger: [431.8, 279.4],
|
|
194
|
+
tabloid: [279.4, 431.8],
|
|
195
|
+
executive: [184.15, 266.7],
|
|
196
|
+
folio: [215.9, 330.2],
|
|
197
|
+
statement: [139.7, 215.9],
|
|
198
|
+
halfletter: [139.7, 215.9]
|
|
199
|
+
};
|
|
200
|
+
var UNIT_FACTORS = {
|
|
201
|
+
pt: 1,
|
|
202
|
+
mm: PT_PER_MM,
|
|
203
|
+
cm: PT_PER_MM * 10,
|
|
204
|
+
in: 72,
|
|
205
|
+
inch: 72,
|
|
206
|
+
px: 0.75,
|
|
207
|
+
pc: 12,
|
|
208
|
+
em: 12,
|
|
209
|
+
ex: 6
|
|
210
|
+
};
|
|
211
|
+
function isUnit(u) {
|
|
212
|
+
return Object.prototype.hasOwnProperty.call(UNIT_FACTORS, u);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/css-colors.ts
|
|
216
|
+
var RAW = "aliceblue:f0f8ff|antiquewhite:faebd7|aqua:00ffff|aquamarine:7fffd4|azure:f0ffff|beige:f5f5dc|bisque:ffe4c4|black:000000|blanchedalmond:ffebcd|blue:0000ff|blueviolet:8a2be2|brown:a52a2a|burlywood:deb887|cadetblue:5f9ea0|chartreuse:7fff00|chocolate:d2691e|coral:ff7f50|cornflowerblue:6495ed|cornsilk:fff8dc|crimson:dc143c|cyan:00ffff|darkblue:00008b|darkcyan:008b8b|darkgoldenrod:b8860b|darkgray:a9a9a9|darkgreen:006400|darkgrey:a9a9a9|darkkhaki:bdb76b|darkmagenta:8b008b|darkolivegreen:556b2f|darkorange:ff8c00|darkorchid:9932cc|darkred:8b0000|darksalmon:e9967a|darkseagreen:8fbc8f|darkslateblue:483d8b|darkslategray:2f4f4f|darkslategrey:2f4f4f|darkturquoise:00ced1|darkviolet:9400d3|deeppink:ff1493|deepskyblue:00bfff|dimgray:696969|dimgrey:696969|dodgerblue:1e90ff|firebrick:b22222|floralwhite:fffaf0|forestgreen:228b22|fuchsia:ff00ff|gainsboro:dcdcdc|ghostwhite:f8f8ff|gold:ffd700|goldenrod:daa520|gray:808080|green:008000|greenyellow:adff2f|grey:808080|honeydew:f0fff0|hotpink:ff69b4|indianred:cd5c5c|indigo:4b0082|ivory:fffff0|khaki:f0e68c|lavender:e6e6fa|lavenderblush:fff0f5|lawngreen:7cfc00|lemonchiffon:fffacd|lightblue:add8e6|lightcoral:f08080|lightcyan:e0ffff|lightgoldenrodyellow:fafad2|lightgray:d3d3d3|lightgreen:90ee90|lightgrey:d3d3d3|lightpink:ffb6c1|lightsalmon:ffa07a|lightseagreen:20b2aa|lightskyblue:87cefa|lightslategray:778899|lightslategrey:778899|lightsteelblue:b0c4de|lightyellow:ffffe0|lime:00ff00|limegreen:32cd32|linen:faf0e6|magenta:ff00ff|maroon:800000|mediumaquamarine:66cdaa|mediumblue:0000cd|mediumorchid:ba55d3|mediumpurple:9370db|mediumseagreen:3cb371|mediumslateblue:7b68ee|mediumspringgreen:00fa9a|mediumturquoise:48d1cc|mediumvioletred:c71585|midnightblue:191970|mintcream:f5fffa|mistyrose:ffe4e1|moccasin:ffe4b5|navajowhite:ffdead|navy:000080|oldlace:fdf5e6|olive:808000|olivedrab:6b8e23|orange:ffa500|orangered:ff4500|orchid:da70d6|palegoldenrod:eee8aa|palegreen:98fb98|paleturquoise:afeeee|palevioletred:db7093|papayawhip:ffefd5|peachpuff:ffdab9|peru:cd853f|pink:ffc0cb|plum:dda0dd|powderblue:b0e0e6|purple:800080|rebeccapurple:663399|red:ff0000|rosybrown:bc8f8f|royalblue:4169e1|saddlebrown:8b4513|salmon:fa8072|sandybrown:f4a460|seagreen:2e8b57|seashell:fff5ee|sienna:a0522d|silver:c0c0c0|skyblue:87ceeb|slateblue:6a5acd|slategray:708090|slategrey:708090|snow:fffafa|springgreen:00ff7f|steelblue:4682b4|tan:d2b48c|teal:008080|thistle:d8bfd8|tomato:ff6347|turquoise:40e0d0|violet:ee82ee|wheat:f5deb3|white:ffffff|whitesmoke:f5f5f5|yellow:ffff00|yellowgreen:9acd32";
|
|
217
|
+
var CSS_COLORS = {};
|
|
218
|
+
for (const pair of RAW.split("|")) {
|
|
219
|
+
const i = pair.indexOf(":");
|
|
220
|
+
CSS_COLORS[pair.slice(0, i)] = pair.slice(i + 1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/core/colors.ts
|
|
224
|
+
function parseColor(args) {
|
|
225
|
+
var _a;
|
|
226
|
+
const a0 = args[0];
|
|
227
|
+
if (typeof a0 === "string") {
|
|
228
|
+
const m = a0.trim().toLowerCase();
|
|
229
|
+
if (m.charAt(0) === "#") {
|
|
230
|
+
let hex = m.slice(1);
|
|
231
|
+
if (hex.length === 3) {
|
|
232
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
233
|
+
}
|
|
234
|
+
if (hex.length !== 6 || /[^0-9a-f]/.test(hex)) return [0, 0, 0];
|
|
235
|
+
return [
|
|
236
|
+
parseInt(hex.slice(0, 2), 16),
|
|
237
|
+
parseInt(hex.slice(2, 4), 16),
|
|
238
|
+
parseInt(hex.slice(4, 6), 16)
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
const named = CSS_COLORS[m];
|
|
242
|
+
if (named) {
|
|
243
|
+
return [
|
|
244
|
+
parseInt(named.slice(0, 2), 16),
|
|
245
|
+
parseInt(named.slice(2, 4), 16),
|
|
246
|
+
parseInt(named.slice(4, 6), 16)
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
const g2 = Number(m);
|
|
250
|
+
if (!isNaN(g2) && m !== "") {
|
|
251
|
+
const v = Math.max(0, Math.min(255, Math.round(g2 * 255)));
|
|
252
|
+
return [v, v, v];
|
|
253
|
+
}
|
|
254
|
+
return [0, 0, 0];
|
|
255
|
+
}
|
|
256
|
+
const defined = args.filter((v) => v !== void 0 && v !== null && v !== "");
|
|
257
|
+
const n = defined.map((v) => Number(v) | 0);
|
|
258
|
+
if (n.length >= 3) return [n[0], n[1], n[2]];
|
|
259
|
+
const g = (_a = n[0]) != null ? _a : 0;
|
|
260
|
+
return [g, g, g];
|
|
261
|
+
}
|
|
262
|
+
function rgbToHex(c) {
|
|
263
|
+
const h = (v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0");
|
|
264
|
+
return `#${h(c[0])}${h(c[1])}${h(c[2])}`;
|
|
265
|
+
}
|
|
266
|
+
function styleFlags(style) {
|
|
267
|
+
const s = String(style || "S").toUpperCase().replace(/\*/g, "");
|
|
268
|
+
const fill = s.includes("F");
|
|
269
|
+
const stroke = s.includes("S") || s.includes("D");
|
|
270
|
+
if (!fill && !stroke) return { fill: false, stroke: true };
|
|
271
|
+
return { fill, stroke };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/context2d.ts
|
|
275
|
+
var IDENT = [1, 0, 0, 1, 0, 0];
|
|
276
|
+
function mul(m, n) {
|
|
277
|
+
return [
|
|
278
|
+
m[0] * n[0] + m[2] * n[1],
|
|
279
|
+
m[1] * n[0] + m[3] * n[1],
|
|
280
|
+
m[0] * n[2] + m[2] * n[3],
|
|
281
|
+
m[1] * n[2] + m[3] * n[3],
|
|
282
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
283
|
+
m[1] * n[4] + m[3] * n[5] + m[5]
|
|
284
|
+
];
|
|
285
|
+
}
|
|
286
|
+
function apply(m, x, y) {
|
|
287
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
288
|
+
}
|
|
289
|
+
var K = 0.5523;
|
|
290
|
+
function parseFont(font) {
|
|
291
|
+
const m = /^\s*(italic|oblique)?\s*(bold)?\s*([\d.]+)(px|pt)\s*(.*)$/.exec(font) || /^\s*(italic|oblique)?\s*(bold)?\s*([\d.]+)(px|pt)\s*$/.exec(font);
|
|
292
|
+
if (!m) return { italic: false, bold: false, px: 16, family: "sans-serif" };
|
|
293
|
+
return {
|
|
294
|
+
italic: !!m[1],
|
|
295
|
+
bold: !!m[2],
|
|
296
|
+
px: parseFloat(m[3]),
|
|
297
|
+
family: (m[5] || "").trim()
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function createContext2d(doc) {
|
|
301
|
+
let matrix = IDENT.slice();
|
|
302
|
+
const stack = [];
|
|
303
|
+
let path = [];
|
|
304
|
+
let cur = [0, 0];
|
|
305
|
+
let lastMove = [0, 0];
|
|
306
|
+
const state = {
|
|
307
|
+
fillStyle: "#000000",
|
|
308
|
+
strokeStyle: "#000000",
|
|
309
|
+
lineWidth: 1,
|
|
310
|
+
globalAlpha: 1,
|
|
311
|
+
font: "10px sans-serif",
|
|
312
|
+
textAlign: "left",
|
|
313
|
+
textBaseline: "alphabetic"
|
|
314
|
+
};
|
|
315
|
+
const unsupported = (name) => {
|
|
316
|
+
throw new Error(`context2d: ${name} is not supported by the OFD vector model`);
|
|
317
|
+
};
|
|
318
|
+
const ctx = {
|
|
319
|
+
__jsofd: true,
|
|
320
|
+
// ---- state ----
|
|
321
|
+
save() {
|
|
322
|
+
stack.push({ matrix: matrix.slice(), state: { ...state } });
|
|
323
|
+
},
|
|
324
|
+
restore() {
|
|
325
|
+
const s = stack.pop();
|
|
326
|
+
if (s) {
|
|
327
|
+
matrix = s.matrix;
|
|
328
|
+
Object.assign(state, s.state);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
get fillStyle() {
|
|
332
|
+
return state.fillStyle;
|
|
333
|
+
},
|
|
334
|
+
set fillStyle(v) {
|
|
335
|
+
if (typeof v === "object") unsupported("gradient/pattern fill styles");
|
|
336
|
+
state.fillStyle = typeof v === "number" ? `#${v.toString(16).padStart(6, "0")}` : String(v);
|
|
337
|
+
},
|
|
338
|
+
get strokeStyle() {
|
|
339
|
+
return state.strokeStyle;
|
|
340
|
+
},
|
|
341
|
+
set strokeStyle(v) {
|
|
342
|
+
if (typeof v === "object") unsupported("gradient/pattern stroke styles");
|
|
343
|
+
state.strokeStyle = typeof v === "number" ? `#${v.toString(16).padStart(6, "0")}` : String(v);
|
|
344
|
+
},
|
|
345
|
+
get lineWidth() {
|
|
346
|
+
return state.lineWidth;
|
|
347
|
+
},
|
|
348
|
+
set lineWidth(v) {
|
|
349
|
+
state.lineWidth = v;
|
|
350
|
+
},
|
|
351
|
+
get globalAlpha() {
|
|
352
|
+
return state.globalAlpha;
|
|
353
|
+
},
|
|
354
|
+
set globalAlpha(v) {
|
|
355
|
+
state.globalAlpha = Math.max(0, Math.min(1, v));
|
|
356
|
+
},
|
|
357
|
+
get font() {
|
|
358
|
+
return state.font;
|
|
359
|
+
},
|
|
360
|
+
set font(v) {
|
|
361
|
+
state.font = v;
|
|
362
|
+
},
|
|
363
|
+
get textAlign() {
|
|
364
|
+
return state.textAlign;
|
|
365
|
+
},
|
|
366
|
+
set textAlign(v) {
|
|
367
|
+
state.textAlign = v;
|
|
368
|
+
},
|
|
369
|
+
get textBaseline() {
|
|
370
|
+
return state.textBaseline;
|
|
371
|
+
},
|
|
372
|
+
set textBaseline(v) {
|
|
373
|
+
state.textBaseline = v;
|
|
374
|
+
},
|
|
375
|
+
// ---- transforms ----
|
|
376
|
+
translate(x, y) {
|
|
377
|
+
matrix = mul(matrix, [1, 0, 0, 1, x, y]);
|
|
378
|
+
},
|
|
379
|
+
rotate(a) {
|
|
380
|
+
const c = Math.cos(a);
|
|
381
|
+
const s = Math.sin(a);
|
|
382
|
+
matrix = mul(matrix, [c, s, -s, c, 0, 0]);
|
|
383
|
+
},
|
|
384
|
+
scale(x, y) {
|
|
385
|
+
matrix = mul(matrix, [x, 0, 0, y, 0, 0]);
|
|
386
|
+
},
|
|
387
|
+
transform(a, b, c, d, e, f) {
|
|
388
|
+
matrix = mul(matrix, [a, b, c, d, e, f]);
|
|
389
|
+
},
|
|
390
|
+
setTransform(a, b, c, d, e, f) {
|
|
391
|
+
matrix = [a, b, c, d, e, f];
|
|
392
|
+
},
|
|
393
|
+
resetTransform() {
|
|
394
|
+
matrix = IDENT.slice();
|
|
395
|
+
},
|
|
396
|
+
// ---- paths ----
|
|
397
|
+
beginPath() {
|
|
398
|
+
path = [];
|
|
399
|
+
cur = [0, 0];
|
|
400
|
+
lastMove = [0, 0];
|
|
401
|
+
},
|
|
402
|
+
closePath() {
|
|
403
|
+
path.push({ kind: "L", pts: [...lastMove] });
|
|
404
|
+
cur = [...lastMove];
|
|
405
|
+
},
|
|
406
|
+
moveTo(x, y) {
|
|
407
|
+
const [tx, ty] = apply(matrix, x, y);
|
|
408
|
+
path.push({ kind: "M", pts: [tx, ty] });
|
|
409
|
+
cur = [x, y];
|
|
410
|
+
lastMove = [x, y];
|
|
411
|
+
},
|
|
412
|
+
lineTo(x, y) {
|
|
413
|
+
const [tx, ty] = apply(matrix, x, y);
|
|
414
|
+
if (path.length === 0) path.push({ kind: "M", pts: [tx, ty] });
|
|
415
|
+
else path.push({ kind: "L", pts: [tx, ty] });
|
|
416
|
+
cur = [x, y];
|
|
417
|
+
},
|
|
418
|
+
bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
|
|
419
|
+
const a1 = apply(matrix, c1x, c1y);
|
|
420
|
+
const a2 = apply(matrix, c2x, c2y);
|
|
421
|
+
const a3 = apply(matrix, x, y);
|
|
422
|
+
if (path.length === 0) path.push({ kind: "M", pts: a1 });
|
|
423
|
+
path.push({ kind: "C", pts: [...a1, ...a2, ...a3] });
|
|
424
|
+
cur = [x, y];
|
|
425
|
+
},
|
|
426
|
+
quadraticCurveTo(cx, cy, x, y) {
|
|
427
|
+
const c1x = cur[0] + 2 / 3 * (cx - cur[0]);
|
|
428
|
+
const c1y = cur[1] + 2 / 3 * (cy - cur[1]);
|
|
429
|
+
const c2x = x + 2 / 3 * (cx - x);
|
|
430
|
+
const c2y = y + 2 / 3 * (cy - y);
|
|
431
|
+
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, x, y);
|
|
432
|
+
},
|
|
433
|
+
arc(x, y, r, start, end, anticlockwise = false) {
|
|
434
|
+
let delta = end - start;
|
|
435
|
+
if (anticlockwise) {
|
|
436
|
+
while (delta > 0) delta -= Math.PI * 2;
|
|
437
|
+
} else {
|
|
438
|
+
while (delta < 0) delta += Math.PI * 2;
|
|
439
|
+
}
|
|
440
|
+
const abs = Math.abs(delta);
|
|
441
|
+
if (abs < 1e-9) return;
|
|
442
|
+
const steps = Math.max(1, Math.ceil(abs / (Math.PI / 2)));
|
|
443
|
+
const step = delta / steps;
|
|
444
|
+
let a = start;
|
|
445
|
+
if (path.length === 0) {
|
|
446
|
+
const t0 = apply(matrix, x + r * Math.cos(a), y + r * Math.sin(a));
|
|
447
|
+
path.push({ kind: "M", pts: t0 });
|
|
448
|
+
}
|
|
449
|
+
for (let i = 0; i < steps; i++) {
|
|
450
|
+
const k = 4 / 3 * Math.tan(step / 4);
|
|
451
|
+
const tx1 = -Math.sin(a);
|
|
452
|
+
const ty1 = Math.cos(a);
|
|
453
|
+
const a3 = a + step;
|
|
454
|
+
const tx3 = -Math.sin(a3);
|
|
455
|
+
const ty3 = Math.cos(a3);
|
|
456
|
+
const p1x = x + r * Math.cos(a);
|
|
457
|
+
const p1y = y + r * Math.sin(a);
|
|
458
|
+
const p3x = x + r * Math.cos(a3);
|
|
459
|
+
const p3y = y + r * Math.sin(a3);
|
|
460
|
+
const c1 = apply(matrix, p1x + k * r * tx1, p1y + k * r * ty1);
|
|
461
|
+
const c2 = apply(matrix, p3x - k * r * tx3, p3y - k * r * ty3);
|
|
462
|
+
const p3 = apply(matrix, p3x, p3y);
|
|
463
|
+
path.push({ kind: "C", pts: [...c1, ...c2, ...p3] });
|
|
464
|
+
cur = [p3x, p3y];
|
|
465
|
+
a = a3;
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
ellipse(x, y, rx, ry, rotation, start, end, anticlockwise = false) {
|
|
469
|
+
ctx.save();
|
|
470
|
+
ctx.translate(x, y);
|
|
471
|
+
ctx.rotate(rotation || 0);
|
|
472
|
+
ctx.scale(1, ry / (rx || 1));
|
|
473
|
+
ctx.arc(0, 0, rx, start, end, anticlockwise);
|
|
474
|
+
ctx.restore();
|
|
475
|
+
},
|
|
476
|
+
rect(x, y, w, h) {
|
|
477
|
+
ctx.moveTo(x, y);
|
|
478
|
+
ctx.lineTo(x + w, y);
|
|
479
|
+
ctx.lineTo(x + w, y + h);
|
|
480
|
+
ctx.lineTo(x, y + h);
|
|
481
|
+
ctx.closePath();
|
|
482
|
+
},
|
|
483
|
+
roundRect(x, y, w, h, r) {
|
|
484
|
+
var _a;
|
|
485
|
+
const rad = Math.min(typeof r === "number" ? r : (_a = r[0]) != null ? _a : 0, w / 2, h / 2);
|
|
486
|
+
const k = K;
|
|
487
|
+
ctx.moveTo(x + rad, y);
|
|
488
|
+
ctx.lineTo(x + w - rad, y);
|
|
489
|
+
ctx.bezierCurveTo(x + w - rad + rad * k, y, x + w, y + rad - rad * k, x + w, y + rad);
|
|
490
|
+
ctx.lineTo(x + w, y + h - rad);
|
|
491
|
+
ctx.bezierCurveTo(
|
|
492
|
+
x + w,
|
|
493
|
+
y + h - rad + rad * k,
|
|
494
|
+
x + w - rad + rad * k,
|
|
495
|
+
y + h,
|
|
496
|
+
x + w - rad,
|
|
497
|
+
y + h
|
|
498
|
+
);
|
|
499
|
+
ctx.lineTo(x + rad, y + h);
|
|
500
|
+
ctx.bezierCurveTo(x + rad - rad * k, y + h, x, y + h - rad + rad * k, x, y + h - rad);
|
|
501
|
+
ctx.lineTo(x, y + rad);
|
|
502
|
+
ctx.bezierCurveTo(x, y + rad - rad * k, x + rad - rad * k, y, x + rad, y);
|
|
503
|
+
ctx.closePath();
|
|
504
|
+
},
|
|
505
|
+
// ---- painting ----
|
|
506
|
+
fill(path2) {
|
|
507
|
+
if (path2) unsupported("Path2D arguments");
|
|
508
|
+
if (!path.length) return;
|
|
509
|
+
doc.pages[doc.page].objects.push({
|
|
510
|
+
t: "path",
|
|
511
|
+
ops: toOps(path, true),
|
|
512
|
+
fill: true,
|
|
513
|
+
stroke: false,
|
|
514
|
+
fillColor: rgb(state.fillStyle),
|
|
515
|
+
strokeColor: rgb(state.strokeStyle),
|
|
516
|
+
lineWidth: 0,
|
|
517
|
+
dash: null,
|
|
518
|
+
cap: 0,
|
|
519
|
+
join: 0,
|
|
520
|
+
miterLimit: 0,
|
|
521
|
+
opacity: state.globalAlpha < 1 ? state.globalAlpha : null
|
|
522
|
+
});
|
|
523
|
+
},
|
|
524
|
+
stroke(path2) {
|
|
525
|
+
if (path2) unsupported("Path2D arguments");
|
|
526
|
+
if (!path.length) return;
|
|
527
|
+
doc.pages[doc.page].objects.push({
|
|
528
|
+
t: "path",
|
|
529
|
+
ops: toOps(path, false),
|
|
530
|
+
fill: false,
|
|
531
|
+
stroke: true,
|
|
532
|
+
fillColor: rgb(state.fillStyle),
|
|
533
|
+
strokeColor: rgb(state.strokeStyle),
|
|
534
|
+
lineWidth: state.lineWidth * Math.hypot(matrix[0], matrix[1]),
|
|
535
|
+
dash: null,
|
|
536
|
+
cap: 0,
|
|
537
|
+
join: 0,
|
|
538
|
+
miterLimit: 0,
|
|
539
|
+
opacity: state.globalAlpha < 1 ? state.globalAlpha : null
|
|
540
|
+
});
|
|
541
|
+
},
|
|
542
|
+
clip() {
|
|
543
|
+
},
|
|
544
|
+
fillRect(x, y, w, h) {
|
|
545
|
+
ctx.beginPath();
|
|
546
|
+
ctx.rect(x, y, w, h);
|
|
547
|
+
ctx.fill();
|
|
548
|
+
},
|
|
549
|
+
strokeRect(x, y, w, h) {
|
|
550
|
+
ctx.beginPath();
|
|
551
|
+
ctx.rect(x, y, w, h);
|
|
552
|
+
ctx.stroke();
|
|
553
|
+
},
|
|
554
|
+
clearRect() {
|
|
555
|
+
},
|
|
556
|
+
fillText(text, x, y) {
|
|
557
|
+
emitText(String(text), x, y, true);
|
|
558
|
+
},
|
|
559
|
+
strokeText(text, x, y) {
|
|
560
|
+
emitText(String(text), x, y, false);
|
|
561
|
+
},
|
|
562
|
+
measureText(text) {
|
|
563
|
+
const f = parseFont(state.font);
|
|
564
|
+
const sizePt = f.px * 0.75;
|
|
565
|
+
const key = mapFamily(f.family);
|
|
566
|
+
const w = doc.measure(
|
|
567
|
+
String(text),
|
|
568
|
+
doc.getFontDef(key),
|
|
569
|
+
f.bold ? "bold" : f.italic ? "italic" : "normal",
|
|
570
|
+
sizePt,
|
|
571
|
+
0
|
|
572
|
+
) / doc.scaleFactor;
|
|
573
|
+
return {
|
|
574
|
+
width: w,
|
|
575
|
+
actualBoundingBoxAscent: sizePt * 0.8 / doc.scaleFactor,
|
|
576
|
+
actualBoundingBoxDescent: sizePt * 0.2 / doc.scaleFactor,
|
|
577
|
+
fontBoundingBoxAscent: sizePt * 0.9 / doc.scaleFactor,
|
|
578
|
+
fontBoundingBoxDescent: sizePt * 0.25 / doc.scaleFactor
|
|
579
|
+
};
|
|
580
|
+
},
|
|
581
|
+
drawImage(img, dx, dy, dw, dh) {
|
|
582
|
+
const w = dw != null ? dw : "width" in img ? Number(img.width) || 0 : 0;
|
|
583
|
+
const h = dh != null ? dh : "height" in img ? Number(img.height) || 0 : 0;
|
|
584
|
+
if (!w || !h) return;
|
|
585
|
+
const sx = Math.hypot(matrix[0], matrix[1]);
|
|
586
|
+
const sy = Math.hypot(matrix[2], matrix[3]);
|
|
587
|
+
const angle = Math.atan2(matrix[1], matrix[0]) * 180 / Math.PI;
|
|
588
|
+
const [cx, cy] = apply(matrix, dx + w / 2, dy + h / 2);
|
|
589
|
+
const prev = doc.opacity;
|
|
590
|
+
if (state.globalAlpha < 1) doc.opacity = state.globalAlpha;
|
|
591
|
+
doc.addImage(img, {
|
|
592
|
+
x: cx - w * sx / 2,
|
|
593
|
+
y: cy - h * sy / 2,
|
|
594
|
+
w: w * sx,
|
|
595
|
+
h: h * sy,
|
|
596
|
+
rotation: angle
|
|
597
|
+
});
|
|
598
|
+
doc.opacity = prev;
|
|
599
|
+
},
|
|
600
|
+
// ---- intentionally unsupported raster/gradient APIs ----
|
|
601
|
+
createLinearGradient: () => unsupported("createLinearGradient (use solid fills)"),
|
|
602
|
+
createRadialGradient: () => unsupported("createRadialGradient (use solid fills)"),
|
|
603
|
+
createPattern: () => unsupported("createPattern"),
|
|
604
|
+
getImageData: () => unsupported("getImageData"),
|
|
605
|
+
putImageData: () => unsupported("putImageData"),
|
|
606
|
+
isPointInPath: () => false
|
|
607
|
+
};
|
|
608
|
+
function rgb(css) {
|
|
609
|
+
return parseColor([css]);
|
|
610
|
+
}
|
|
611
|
+
function toOps(segs, close) {
|
|
612
|
+
const ops = [];
|
|
613
|
+
for (const s of segs) {
|
|
614
|
+
if (s.kind === "M") ops.push({ op: "M", x: s.pts[0], y: s.pts[1] });
|
|
615
|
+
else if (s.kind === "L") ops.push({ op: "L", x: s.pts[0], y: s.pts[1] });
|
|
616
|
+
else
|
|
617
|
+
ops.push({
|
|
618
|
+
op: "C",
|
|
619
|
+
x1: s.pts[0],
|
|
620
|
+
y1: s.pts[1],
|
|
621
|
+
x2: s.pts[2],
|
|
622
|
+
y2: s.pts[3],
|
|
623
|
+
x: s.pts[4],
|
|
624
|
+
y: s.pts[5]
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
if (close) ops.push({ op: "Z" });
|
|
628
|
+
return ops;
|
|
629
|
+
}
|
|
630
|
+
function mapFamily(family) {
|
|
631
|
+
const f = family.toLowerCase();
|
|
632
|
+
if (f.includes("song") || f.includes("\u5B8B") || f.includes("serif")) return "simsun";
|
|
633
|
+
if (f.includes("hei") || f.includes("\u9ED1")) return "simhei";
|
|
634
|
+
if (f.includes("kai") || f.includes("\u6977")) return "kaiti";
|
|
635
|
+
if (f.includes("fang") || f.includes("\u4EFF")) return "fangsong";
|
|
636
|
+
if (f.includes("mono")) return "courier";
|
|
637
|
+
if (f.includes("times")) return "times";
|
|
638
|
+
return "helvetica";
|
|
639
|
+
}
|
|
640
|
+
function emitText(text, x, y, fill) {
|
|
641
|
+
const f = parseFont(state.font);
|
|
642
|
+
const sizePt = f.px * 0.75;
|
|
643
|
+
const sf = doc.scaleFactor;
|
|
644
|
+
const angle = Math.atan2(matrix[1], matrix[0]) * 180 / Math.PI;
|
|
645
|
+
const [tx, ty] = apply(matrix, x, y);
|
|
646
|
+
const key = mapFamily(f.family);
|
|
647
|
+
const def = doc.getFontDef(key);
|
|
648
|
+
const ascPt = def.ascent / 1e3 * sizePt;
|
|
649
|
+
const descPt = -def.descent / 1e3 * sizePt;
|
|
650
|
+
const wU = doc.measure(text, def, f.bold ? "bold" : "normal", sizePt, 0) / sf;
|
|
651
|
+
let ax = 0;
|
|
652
|
+
if (state.textAlign === "center") ax = -wU / 2;
|
|
653
|
+
else if (state.textAlign === "right") ax = -wU;
|
|
654
|
+
let ay = 0;
|
|
655
|
+
if (state.textBaseline === "top") ay = ascPt / sf;
|
|
656
|
+
else if (state.textBaseline === "middle") ay = (ascPt - descPt) / 2 / sf;
|
|
657
|
+
else if (state.textBaseline === "bottom") ay = -descPt / sf;
|
|
658
|
+
else if (state.textBaseline === "hanging") ay = ascPt * 0.8 / sf;
|
|
659
|
+
const rad = angle * Math.PI / 180;
|
|
660
|
+
const cos = Math.cos(rad);
|
|
661
|
+
const sin = Math.sin(rad);
|
|
662
|
+
const anchor = [tx + ax * cos - ay * sin, ty + ax * sin + ay * cos];
|
|
663
|
+
const prevFont = doc.activeFontKey;
|
|
664
|
+
const prevStyle = doc.activeFontStyle;
|
|
665
|
+
const prevColor = doc.textColor.slice();
|
|
666
|
+
doc.activeFontKey = key;
|
|
667
|
+
doc.activeFontStyle = f.bold ? "bold" : f.italic ? "italic" : "normal";
|
|
668
|
+
doc.textColor = rgb(fill ? state.fillStyle : state.strokeStyle);
|
|
669
|
+
doc.text(text, anchor[0], anchor[1], {
|
|
670
|
+
angle,
|
|
671
|
+
fontSize: sizePt,
|
|
672
|
+
renderingMode: fill ? "fill" : "stroke",
|
|
673
|
+
opacity: state.globalAlpha < 1 ? state.globalAlpha : void 0
|
|
674
|
+
});
|
|
675
|
+
doc.activeFontKey = prevFont;
|
|
676
|
+
doc.activeFontStyle = prevStyle;
|
|
677
|
+
doc.textColor = prevColor;
|
|
678
|
+
}
|
|
679
|
+
return ctx;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/modules/annotations.ts
|
|
683
|
+
var AnnotationApi = {
|
|
684
|
+
/**
|
|
685
|
+
* Add a clickable rectangular area.
|
|
686
|
+
*
|
|
687
|
+
* Pass `url` for an external target or `pageNumber` (1-based) for an
|
|
688
|
+
* in-document jump. The area itself is invisible.
|
|
689
|
+
*/
|
|
690
|
+
link(x, y, w, h, options = {}) {
|
|
691
|
+
this.pages[this.page].objects.push({
|
|
692
|
+
t: "link",
|
|
693
|
+
x: this._u(Number(x)),
|
|
694
|
+
y: this._u(Number(y)),
|
|
695
|
+
w: this._u(Number(w)),
|
|
696
|
+
h: this._u(Number(h)),
|
|
697
|
+
url: options.url || null,
|
|
698
|
+
pageNumber: options.pageNumber || null,
|
|
699
|
+
magFactor: options.magFactor || null
|
|
700
|
+
});
|
|
701
|
+
return this;
|
|
702
|
+
},
|
|
703
|
+
/**
|
|
704
|
+
* Embed a file as an OFD attachment.
|
|
705
|
+
*
|
|
706
|
+
* @param data UTF-8 text, a base64 data URL, or binary bytes
|
|
707
|
+
*/
|
|
708
|
+
addFileAsAttachment(filename, data, options = {}) {
|
|
709
|
+
let u8;
|
|
710
|
+
if (typeof data === "string") {
|
|
711
|
+
if (/^data:.*?;base64,/i.test(data)) u8 = b64Decode(data);
|
|
712
|
+
else u8 = utf8Encode(data);
|
|
713
|
+
} else if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
714
|
+
u8 = u8FromBuffer(data);
|
|
715
|
+
} else {
|
|
716
|
+
throw new Error("addFileAsAttachment: unsupported data type");
|
|
717
|
+
}
|
|
718
|
+
this.attachments.push({
|
|
719
|
+
name: String(filename),
|
|
720
|
+
data: u8,
|
|
721
|
+
format: options.format || "",
|
|
722
|
+
description: options.description || "",
|
|
723
|
+
creationDate: /* @__PURE__ */ new Date()
|
|
724
|
+
});
|
|
725
|
+
return this;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/modules/annotate.ts
|
|
730
|
+
var AnnotationVisualApi = {
|
|
731
|
+
/**
|
|
732
|
+
* Draw a visual annotation over the rectangle `(x, y, w, h)` and return the
|
|
733
|
+
* doc for chaining.
|
|
734
|
+
*
|
|
735
|
+
* - `highlight` translucent marker behind/over text (default yellow)
|
|
736
|
+
* - `underline` straight line at the bottom edge
|
|
737
|
+
* - `strikeout` line through the vertical middle
|
|
738
|
+
* - `squiggly` hand-drawn style zigzag along the bottom edge
|
|
739
|
+
* - `box` rectangle outline (optionally filled via `options.fill`)
|
|
740
|
+
* - `ink` `options.points` as a freehand polyline (`[[x,y],…]`)
|
|
741
|
+
* - `freetext` `options.text` wrapped inside the rectangle
|
|
742
|
+
*/
|
|
743
|
+
addAnnotation(type, x, y, w, h, options = {}) {
|
|
744
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
|
|
745
|
+
const lw = (_a = options.lineWidth) != null ? _a : 0.6;
|
|
746
|
+
const prevLw = this.lineWidth;
|
|
747
|
+
const prevDraw = this.drawColor.slice();
|
|
748
|
+
const prevFill = this.fillColor.slice();
|
|
749
|
+
const prevOpacity = this.opacity;
|
|
750
|
+
const prevFontKey = this.activeFontKey;
|
|
751
|
+
const prevFontStyle = this.activeFontStyle;
|
|
752
|
+
const prevFontSize = this.fontSize;
|
|
753
|
+
const prevText = this.textColor.slice();
|
|
754
|
+
const setDraw = (c) => {
|
|
755
|
+
this.drawColor = c;
|
|
756
|
+
this.lineWidth = lw;
|
|
757
|
+
};
|
|
758
|
+
switch (type) {
|
|
759
|
+
case "highlight": {
|
|
760
|
+
this.fillColor = (_b = options.color) != null ? _b : [255, 221, 40];
|
|
761
|
+
this.opacity = (_c = options.opacity) != null ? _c : 0.35;
|
|
762
|
+
this.rect(x, y, w, h, "F");
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
case "underline": {
|
|
766
|
+
setDraw((_d = options.color) != null ? _d : [224, 62, 62]);
|
|
767
|
+
this.opacity = (_e = options.opacity) != null ? _e : 1;
|
|
768
|
+
this.line(x, y + h, x + w, y + h, "S");
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
case "strikeout": {
|
|
772
|
+
setDraw((_f = options.color) != null ? _f : [224, 62, 62]);
|
|
773
|
+
this.opacity = (_g = options.opacity) != null ? _g : 1;
|
|
774
|
+
this.line(x, y + h / 2, x + w, y + h / 2, "S");
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case "squiggly": {
|
|
778
|
+
setDraw((_h = options.color) != null ? _h : [224, 62, 62]);
|
|
779
|
+
this.opacity = (_i = options.opacity) != null ? _i : 1;
|
|
780
|
+
const seg = Math.max(w / 12, 1.2);
|
|
781
|
+
const amp = Math.max(lw * 1.2, 0.5);
|
|
782
|
+
const steps = Math.max(2, Math.round(w / seg));
|
|
783
|
+
const s = seg * 1;
|
|
784
|
+
const yy = y + h;
|
|
785
|
+
for (let i = 0; i < steps; i++) {
|
|
786
|
+
const x1 = x + i * s;
|
|
787
|
+
const x2 = Math.min(x1 + s, x + w);
|
|
788
|
+
const mid = (x1 + x2) / 2;
|
|
789
|
+
const up = i % 2 === 0 ? -amp : amp;
|
|
790
|
+
this.lines(
|
|
791
|
+
[
|
|
792
|
+
[mid - x1, up],
|
|
793
|
+
[x2 - mid, -up]
|
|
794
|
+
],
|
|
795
|
+
x1,
|
|
796
|
+
yy,
|
|
797
|
+
1,
|
|
798
|
+
"S"
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
case "box": {
|
|
804
|
+
if (options.fill) {
|
|
805
|
+
this.fillColor = options.fill;
|
|
806
|
+
this.opacity = (_j = options.opacity) != null ? _j : 1;
|
|
807
|
+
this.rect(x, y, w, h, "F");
|
|
808
|
+
}
|
|
809
|
+
setDraw((_k = options.color) != null ? _k : [53, 101, 224]);
|
|
810
|
+
this.rect(x, y, w, h, "S");
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
case "ink": {
|
|
814
|
+
const pts = options.points;
|
|
815
|
+
if (!pts || pts.length < 2)
|
|
816
|
+
throw new Error("addAnnotation('ink'): options.points required");
|
|
817
|
+
setDraw((_l = options.color) != null ? _l : [31, 41, 55]);
|
|
818
|
+
this.opacity = (_m = options.opacity) != null ? _m : 1;
|
|
819
|
+
let cx = pts[0][0];
|
|
820
|
+
let cy = pts[0][1];
|
|
821
|
+
const segs = [];
|
|
822
|
+
for (let i = 1; i < pts.length; i++) {
|
|
823
|
+
segs.push([pts[i][0] - cx, pts[i][1] - cy]);
|
|
824
|
+
cx = pts[i][0];
|
|
825
|
+
cy = pts[i][1];
|
|
826
|
+
}
|
|
827
|
+
this.lines(segs, pts[0][0], pts[0][1], 1, "S");
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
case "freetext": {
|
|
831
|
+
if (!options.text) throw new Error("addAnnotation('freetext'): options.text required");
|
|
832
|
+
if (options.fill) {
|
|
833
|
+
this.fillColor = options.fill;
|
|
834
|
+
this.opacity = (_n = options.opacity) != null ? _n : 1;
|
|
835
|
+
this.rect(x, y, w, h, "F");
|
|
836
|
+
}
|
|
837
|
+
if (options.fontKey) this.setFont(options.fontKey);
|
|
838
|
+
this.setFontSize((_o = options.fontSize) != null ? _o : 11);
|
|
839
|
+
this.textColor = (_p = options.color) != null ? _p : [224, 62, 62];
|
|
840
|
+
this.text(options.text, x + 1, y + ((_q = options.fontSize) != null ? _q : 11) * 0.9 / this.scaleFactor, {
|
|
841
|
+
maxWidth: w - 2
|
|
842
|
+
});
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
default:
|
|
846
|
+
throw new Error("addAnnotation: unknown type " + String(type));
|
|
847
|
+
}
|
|
848
|
+
this.lineWidth = prevLw;
|
|
849
|
+
this.drawColor = prevDraw;
|
|
850
|
+
this.fillColor = prevFill;
|
|
851
|
+
this.opacity = prevOpacity;
|
|
852
|
+
this.activeFontKey = prevFontKey;
|
|
853
|
+
this.activeFontStyle = prevFontStyle;
|
|
854
|
+
this.fontSize = prevFontSize;
|
|
855
|
+
this.textColor = prevText;
|
|
856
|
+
return this;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// src/modules/flow.ts
|
|
861
|
+
var FlowApi = {
|
|
862
|
+
/**
|
|
863
|
+
* Flow long text across as many pages as needed.
|
|
864
|
+
*
|
|
865
|
+
* Lines wrap at `maxWidth` (same CJK/Latin rules as `text()`); when the
|
|
866
|
+
* next line would cross the bottom margin a new page is appended and
|
|
867
|
+
* `onNewPage` fires (for headers, page numbers, …).
|
|
868
|
+
*
|
|
869
|
+
* @returns the Y coordinate (active unit) after the last line — chain more
|
|
870
|
+
* content from there
|
|
871
|
+
*/
|
|
872
|
+
autoPaging(text, options = {}) {
|
|
873
|
+
var _a, _b, _c, _d, _e, _f;
|
|
874
|
+
const pageW = this.getPageWidth();
|
|
875
|
+
const pageH = this.getPageHeight();
|
|
876
|
+
const x = (_a = options.x) != null ? _a : 20;
|
|
877
|
+
const topMargin = (_b = options.topMargin) != null ? _b : 20;
|
|
878
|
+
const bottomMargin = (_c = options.bottomMargin) != null ? _c : 20;
|
|
879
|
+
const rightMargin = (_d = options.rightMargin) != null ? _d : 20;
|
|
880
|
+
const maxWidth = (_e = options.maxWidth) != null ? _e : pageW - x - rightMargin;
|
|
881
|
+
const startPage = this.page;
|
|
882
|
+
if (typeof options.lineHeightFactor === "number")
|
|
883
|
+
this.setLineHeightFactor(options.lineHeightFactor);
|
|
884
|
+
const lines = this.splitTextToSize(String(text), maxWidth);
|
|
885
|
+
const leading = this.getLineHeight();
|
|
886
|
+
const maxBottom = pageH - bottomMargin;
|
|
887
|
+
let y = options.y !== void 0 ? options.y : topMargin + this.getFontSize() / this.scaleFactor * 0.85;
|
|
888
|
+
for (const line of lines) {
|
|
889
|
+
if (y > maxBottom) {
|
|
890
|
+
this.addPage();
|
|
891
|
+
y = topMargin + this.getFontSize() / this.scaleFactor * 0.85;
|
|
892
|
+
(_f = options.onNewPage) == null ? void 0 : _f.call(options, this, { pageNumber: this.pages.length, pageCount: this.pages.length });
|
|
893
|
+
}
|
|
894
|
+
this.text(line, x, y, { align: options.align });
|
|
895
|
+
y += leading;
|
|
896
|
+
}
|
|
897
|
+
this.page = startPage;
|
|
898
|
+
return y;
|
|
899
|
+
},
|
|
900
|
+
/**
|
|
901
|
+
* Draw headers and footers on a page range.
|
|
902
|
+
*
|
|
903
|
+
* Hooks receive the document (positioned on the target page — use
|
|
904
|
+
* `text`/`line`/… normally) and `{ pageNumber, pageCount }`. Common use:
|
|
905
|
+
*
|
|
906
|
+
* ```ts
|
|
907
|
+
* doc.headerFooter({
|
|
908
|
+
* header: (d, { pageNumber }) => d.text('季度报告', 20, 12),
|
|
909
|
+
* footer: (d, { pageNumber, pageCount }) =>
|
|
910
|
+
* d.text(`第 ${pageNumber} 页 / 共 ${pageCount} 页`, 105, 285, { align: 'center' }),
|
|
911
|
+
* });
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
headerFooter(options) {
|
|
915
|
+
var _a, _b, _c, _d;
|
|
916
|
+
const start = Math.max(1, (_a = options.startPage) != null ? _a : 1);
|
|
917
|
+
const end = Math.min(this.pages.length, (_b = options.endPage) != null ? _b : this.pages.length);
|
|
918
|
+
const cur = this.page;
|
|
919
|
+
for (let n = start; n <= end; n++) {
|
|
920
|
+
this.setPage(n);
|
|
921
|
+
const info = { pageNumber: n, pageCount: this.pages.length };
|
|
922
|
+
(_c = options.header) == null ? void 0 : _c.call(options, this, info);
|
|
923
|
+
(_d = options.footer) == null ? void 0 : _d.call(options, this, info);
|
|
924
|
+
}
|
|
925
|
+
this.page = cur;
|
|
926
|
+
return this;
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
// src/modules/html.ts
|
|
931
|
+
var ENTITIES = {
|
|
932
|
+
amp: "&",
|
|
933
|
+
lt: "<",
|
|
934
|
+
gt: ">",
|
|
935
|
+
quot: '"',
|
|
936
|
+
apos: "'",
|
|
937
|
+
nbsp: "\xA0",
|
|
938
|
+
copy: "\xA9",
|
|
939
|
+
reg: "\xAE",
|
|
940
|
+
trade: "\u2122",
|
|
941
|
+
mdash: "\u2014",
|
|
942
|
+
ndash: "\u2013",
|
|
943
|
+
hellip: "\u2026",
|
|
944
|
+
laquo: "\xAB",
|
|
945
|
+
raquo: "\xBB",
|
|
946
|
+
middot: "\xB7"
|
|
947
|
+
};
|
|
948
|
+
function decodeEntities(s) {
|
|
949
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (all, body) => {
|
|
950
|
+
var _a;
|
|
951
|
+
if (body.startsWith("#x") || body.startsWith("#X"))
|
|
952
|
+
return String.fromCodePoint(parseInt(body.slice(2), 16));
|
|
953
|
+
if (body.startsWith("#")) return String.fromCodePoint(parseInt(body.slice(1), 10));
|
|
954
|
+
return (_a = ENTITIES[body.toLowerCase()]) != null ? _a : all;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function parseBlocks(html) {
|
|
958
|
+
var _a;
|
|
959
|
+
const blocks = [];
|
|
960
|
+
let text = "";
|
|
961
|
+
let bold = false;
|
|
962
|
+
let italic = false;
|
|
963
|
+
let container = 0;
|
|
964
|
+
const containerStack = [];
|
|
965
|
+
const flush = (gapBefore = 0) => {
|
|
966
|
+
const t = decodeEntities(text.replace(/\s+/g, " ").trim());
|
|
967
|
+
if (t) blocks.push({ text: t, kind: container, bold, italic, gapBefore });
|
|
968
|
+
text = "";
|
|
969
|
+
};
|
|
970
|
+
const tagRe = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*?(\/?)>|<!--[\s\S]*?-->/g;
|
|
971
|
+
let last = 0;
|
|
972
|
+
let m;
|
|
973
|
+
while ((m = tagRe.exec(html)) !== null) {
|
|
974
|
+
text += html.slice(last, m.index);
|
|
975
|
+
last = tagRe.lastIndex;
|
|
976
|
+
if (!m[1]) continue;
|
|
977
|
+
const closing = m[0].startsWith("</");
|
|
978
|
+
const tag = m[1].toLowerCase();
|
|
979
|
+
if (!closing) {
|
|
980
|
+
switch (tag) {
|
|
981
|
+
case "b":
|
|
982
|
+
case "strong":
|
|
983
|
+
bold = true;
|
|
984
|
+
break;
|
|
985
|
+
case "i":
|
|
986
|
+
case "em":
|
|
987
|
+
italic = true;
|
|
988
|
+
break;
|
|
989
|
+
case "h1":
|
|
990
|
+
case "h2":
|
|
991
|
+
case "h3":
|
|
992
|
+
case "h4":
|
|
993
|
+
case "h5":
|
|
994
|
+
case "h6":
|
|
995
|
+
flush();
|
|
996
|
+
containerStack.push(container);
|
|
997
|
+
container = Number(tag.charAt(1));
|
|
998
|
+
break;
|
|
999
|
+
case "p":
|
|
1000
|
+
flush(6);
|
|
1001
|
+
containerStack.push(container);
|
|
1002
|
+
container = 0;
|
|
1003
|
+
break;
|
|
1004
|
+
case "li":
|
|
1005
|
+
flush(2);
|
|
1006
|
+
containerStack.push(container);
|
|
1007
|
+
container = 7;
|
|
1008
|
+
break;
|
|
1009
|
+
case "br":
|
|
1010
|
+
flush();
|
|
1011
|
+
break;
|
|
1012
|
+
case "ul":
|
|
1013
|
+
case "ol":
|
|
1014
|
+
case "div":
|
|
1015
|
+
case "blockquote":
|
|
1016
|
+
flush(4);
|
|
1017
|
+
break;
|
|
1018
|
+
default:
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
if (m[2] === "/") {
|
|
1022
|
+
if (tag === "br") {
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
switch (tag) {
|
|
1027
|
+
case "b":
|
|
1028
|
+
case "strong":
|
|
1029
|
+
bold = false;
|
|
1030
|
+
break;
|
|
1031
|
+
case "i":
|
|
1032
|
+
case "em":
|
|
1033
|
+
italic = false;
|
|
1034
|
+
break;
|
|
1035
|
+
case "h1":
|
|
1036
|
+
case "h2":
|
|
1037
|
+
case "h3":
|
|
1038
|
+
case "h4":
|
|
1039
|
+
case "h5":
|
|
1040
|
+
case "h6":
|
|
1041
|
+
case "p":
|
|
1042
|
+
case "li":
|
|
1043
|
+
flush();
|
|
1044
|
+
container = (_a = containerStack.pop()) != null ? _a : 0;
|
|
1045
|
+
break;
|
|
1046
|
+
default:
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
text += html.slice(last);
|
|
1052
|
+
flush();
|
|
1053
|
+
return blocks;
|
|
1054
|
+
}
|
|
1055
|
+
function blockStyle(b, base, headingScale) {
|
|
1056
|
+
if (b.kind >= 1 && b.kind <= 6) {
|
|
1057
|
+
const factor = [0, 2, 1.5, 1.25, 1.1, 1, 1][b.kind] * headingScale;
|
|
1058
|
+
return { size: base * factor, bold: true, gap: b.gapBefore + 8 };
|
|
1059
|
+
}
|
|
1060
|
+
if (b.kind === 7) return { size: base, bold: b.bold, gap: b.gapBefore };
|
|
1061
|
+
return { size: base, bold: b.bold, gap: b.gapBefore };
|
|
1062
|
+
}
|
|
1063
|
+
var HtmlApi = {
|
|
1064
|
+
/**
|
|
1065
|
+
* Render an HTML fragment and return the final Y (active unit).
|
|
1066
|
+
*
|
|
1067
|
+
* Supported: `h1–h6`, `p`, `div`, `br`, `ul/ol` + `li`, `b/strong`,
|
|
1068
|
+
* `i/em`, entities. Inline `span` text is preserved. Attributes and CSS
|
|
1069
|
+
* are ignored by design.
|
|
1070
|
+
*/
|
|
1071
|
+
html(html, options = {}) {
|
|
1072
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1073
|
+
const pageW = this.getPageWidth();
|
|
1074
|
+
const pageH = this.getPageHeight();
|
|
1075
|
+
const x = (_a = options.x) != null ? _a : 20;
|
|
1076
|
+
const topMargin = (_b = options.topMargin) != null ? _b : 20;
|
|
1077
|
+
const bottomMargin = (_c = options.bottomMargin) != null ? _c : 20;
|
|
1078
|
+
const width = (_d = options.width) != null ? _d : pageW - x - 20;
|
|
1079
|
+
const base = (_e = options.fontSize) != null ? _e : 11;
|
|
1080
|
+
const headingScale = (_f = options.headingScale) != null ? _f : 1;
|
|
1081
|
+
const lh = (_g = options.lineHeightFactor) != null ? _g : 1.5;
|
|
1082
|
+
const prevLh = this.lineHeightFactor;
|
|
1083
|
+
this.setLineHeightFactor(lh);
|
|
1084
|
+
let y = (_h = options.y) != null ? _h : topMargin + base * 0.85;
|
|
1085
|
+
const maxBottom = pageH - bottomMargin;
|
|
1086
|
+
const sf = this.scaleFactor;
|
|
1087
|
+
const ensureRoom = (neededPt, gapPt) => {
|
|
1088
|
+
var _a2;
|
|
1089
|
+
if (y + (neededPt + gapPt) / sf > maxBottom) {
|
|
1090
|
+
this.addPage();
|
|
1091
|
+
y = topMargin + base * 0.85;
|
|
1092
|
+
(_a2 = options.onNewPage) == null ? void 0 : _a2.call(options, this, { pageNumber: this.pages.length, pageCount: this.pages.length });
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
for (const block of parseBlocks(String(html))) {
|
|
1096
|
+
const { size, bold, gap } = blockStyle(block, base, headingScale);
|
|
1097
|
+
this.setFontSize(size);
|
|
1098
|
+
this.activeFontStyle = bold ? "bold" : block.italic ? "italic" : "normal";
|
|
1099
|
+
const lines = this.splitTextToSize(block.text, width);
|
|
1100
|
+
ensureRoom(size * lh, gap);
|
|
1101
|
+
y += gap / sf;
|
|
1102
|
+
const bullet = block.kind === 7 ? "\u2022 " : "";
|
|
1103
|
+
lines.forEach((line, i) => {
|
|
1104
|
+
var _a2;
|
|
1105
|
+
if (y + size / sf > maxBottom && i > 0) {
|
|
1106
|
+
this.addPage();
|
|
1107
|
+
y = topMargin + base * 0.85;
|
|
1108
|
+
(_a2 = options.onNewPage) == null ? void 0 : _a2.call(options, this, {
|
|
1109
|
+
pageNumber: this.pages.length,
|
|
1110
|
+
pageCount: this.pages.length
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
const prefix = i === 0 ? bullet : " ";
|
|
1114
|
+
this.text(prefix + line, x, y);
|
|
1115
|
+
y += size * lh / sf;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
this.setLineHeightFactor(prevLh);
|
|
1119
|
+
return y;
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
|
|
1123
|
+
// src/image.ts
|
|
1124
|
+
function jpegSize(u8) {
|
|
1125
|
+
let i = 2;
|
|
1126
|
+
while (i + 9 < u8.length) {
|
|
1127
|
+
if (u8[i] !== 255) {
|
|
1128
|
+
i++;
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
const marker = u8[i + 1];
|
|
1132
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
1133
|
+
const h = u8[i + 5] << 8 | u8[i + 6];
|
|
1134
|
+
const w = u8[i + 7] << 8 | u8[i + 8];
|
|
1135
|
+
return [w, h];
|
|
1136
|
+
}
|
|
1137
|
+
const len = u8[i + 2] << 8 | u8[i + 3];
|
|
1138
|
+
i += 2 + len;
|
|
1139
|
+
}
|
|
1140
|
+
return [null, null];
|
|
1141
|
+
}
|
|
1142
|
+
function sniffImage(u8) {
|
|
1143
|
+
if (u8.length < 8) return null;
|
|
1144
|
+
if (u8[0] === 137 && u8[1] === 80 && u8[2] === 78 && u8[3] === 71) {
|
|
1145
|
+
const w = (u8[16] << 24 | u8[17] << 16 | u8[18] << 8 | u8[19]) >>> 0;
|
|
1146
|
+
const h = (u8[20] << 24 | u8[21] << 16 | u8[22] << 8 | u8[23]) >>> 0;
|
|
1147
|
+
return { format: "PNG", ext: "png", width: w, height: h };
|
|
1148
|
+
}
|
|
1149
|
+
if (u8[0] === 255 && u8[1] === 216 && u8[2] === 255) {
|
|
1150
|
+
const [w, h] = jpegSize(u8);
|
|
1151
|
+
return { format: "JPEG", ext: "jpg", width: w, height: h };
|
|
1152
|
+
}
|
|
1153
|
+
if (u8[0] === 71 && u8[1] === 73 && u8[2] === 70) {
|
|
1154
|
+
return {
|
|
1155
|
+
format: "GIF",
|
|
1156
|
+
ext: "gif",
|
|
1157
|
+
width: u8[6] | u8[7] << 8,
|
|
1158
|
+
height: u8[8] | u8[9] << 8
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
if (u8[0] === 66 && u8[1] === 77) {
|
|
1162
|
+
return {
|
|
1163
|
+
format: "BMP",
|
|
1164
|
+
ext: "bmp",
|
|
1165
|
+
width: (u8[18] | u8[19] << 8 | u8[20] << 16 | u8[21] << 24) >>> 0,
|
|
1166
|
+
height: (u8[22] | u8[23] << 8 | u8[24] << 16 | u8[25] << 24) >>> 0
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
if (u8[0] === 73 && u8[1] === 73 && u8[2] === 42 || u8[0] === 77 && u8[1] === 77 && u8[2] === 0) {
|
|
1170
|
+
return { format: "TIFF", ext: "tif", width: null, height: null };
|
|
1171
|
+
}
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/modules/images.ts
|
|
1176
|
+
var ImageApi = {
|
|
1177
|
+
/**
|
|
1178
|
+
* Embed an image and place it on the current page.
|
|
1179
|
+
*
|
|
1180
|
+
* Binary data (PNG/JPEG/GIF/BMP/TIFF) is embedded byte-for-byte; data URLs,
|
|
1181
|
+
* bare base64 strings (with `format`), `HTMLImageElement` and
|
|
1182
|
+
* `HTMLCanvasElement` inputs are also accepted.
|
|
1183
|
+
*
|
|
1184
|
+
* When `w`/`h` are omitted the natural size at 72 dpi is used. Identical
|
|
1185
|
+
* data is stored once and referenced multiple times.
|
|
1186
|
+
*/
|
|
1187
|
+
addImage(a, b, c, d, e, f, g, h, i) {
|
|
1188
|
+
let format;
|
|
1189
|
+
let px;
|
|
1190
|
+
let py;
|
|
1191
|
+
let pw;
|
|
1192
|
+
let ph;
|
|
1193
|
+
let alias;
|
|
1194
|
+
let rotation;
|
|
1195
|
+
if (typeof b === "object" && b !== null) {
|
|
1196
|
+
const o = b;
|
|
1197
|
+
format = o.format;
|
|
1198
|
+
px = o.x;
|
|
1199
|
+
py = o.y;
|
|
1200
|
+
pw = o.w;
|
|
1201
|
+
ph = o.h;
|
|
1202
|
+
alias = o.alias;
|
|
1203
|
+
rotation = o.rotation || 0;
|
|
1204
|
+
} else if (typeof b === "number") {
|
|
1205
|
+
px = b;
|
|
1206
|
+
py = c;
|
|
1207
|
+
pw = d;
|
|
1208
|
+
ph = e;
|
|
1209
|
+
alias = typeof f === "string" ? f : void 0;
|
|
1210
|
+
rotation = (typeof g === "number" ? g : 0) || (typeof h === "number" ? h : 0);
|
|
1211
|
+
} else {
|
|
1212
|
+
format = b;
|
|
1213
|
+
px = c;
|
|
1214
|
+
py = d;
|
|
1215
|
+
pw = e;
|
|
1216
|
+
ph = f;
|
|
1217
|
+
alias = typeof g === "string" ? g : void 0;
|
|
1218
|
+
rotation = (typeof h === "number" ? h : 0) || (typeof i === "number" ? i : 0);
|
|
1219
|
+
}
|
|
1220
|
+
const imageData = a;
|
|
1221
|
+
let u8 = null;
|
|
1222
|
+
let detectedFormat = format;
|
|
1223
|
+
if (typeof imageData === "string") {
|
|
1224
|
+
if (/^data:/i.test(imageData)) {
|
|
1225
|
+
u8 = b64Decode(imageData);
|
|
1226
|
+
if (!detectedFormat) {
|
|
1227
|
+
const mime = /^data:image\/([a-z0-9]+?)(?:;|$)/i.exec(imageData);
|
|
1228
|
+
if (mime) detectedFormat = mime[1];
|
|
1229
|
+
}
|
|
1230
|
+
} else if (/^[A-Za-z0-9+/=\s]+$/.test(imageData) && imageData.replace(/\s/g, "").length >= 8) {
|
|
1231
|
+
u8 = b64Decode(imageData.replace(/\s/g, ""));
|
|
1232
|
+
} else {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
"addImage: strings must be data URLs or base64; pass a Uint8Array with an explicit format otherwise"
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
} else if (typeof HTMLImageElement !== "undefined" && imageData instanceof HTMLImageElement) {
|
|
1238
|
+
const cv = document.createElement("canvas");
|
|
1239
|
+
cv.width = imageData.naturalWidth || imageData.width || 1;
|
|
1240
|
+
cv.height = imageData.naturalHeight || imageData.height || 1;
|
|
1241
|
+
cv.getContext("2d").drawImage(imageData, 0, 0);
|
|
1242
|
+
u8 = b64Decode(cv.toDataURL("image/png"));
|
|
1243
|
+
detectedFormat = "PNG";
|
|
1244
|
+
} else if (typeof HTMLCanvasElement !== "undefined" && imageData instanceof HTMLCanvasElement) {
|
|
1245
|
+
u8 = b64Decode(imageData.toDataURL("image/png"));
|
|
1246
|
+
detectedFormat = "PNG";
|
|
1247
|
+
} else if (imageData instanceof Uint8Array || imageData instanceof ArrayBuffer) {
|
|
1248
|
+
u8 = u8FromBuffer(imageData);
|
|
1249
|
+
}
|
|
1250
|
+
if (!u8 || !u8.length) throw new Error("addImage: unable to parse image data");
|
|
1251
|
+
const sniff = sniffImage(u8);
|
|
1252
|
+
if (!sniff) {
|
|
1253
|
+
throw new Error("addImage: unsupported image format (PNG/JPEG/GIF/BMP/TIFF)");
|
|
1254
|
+
}
|
|
1255
|
+
if (detectedFormat) {
|
|
1256
|
+
let f2 = String(detectedFormat).toUpperCase();
|
|
1257
|
+
if (f2 === "JPG") f2 = "JPEG";
|
|
1258
|
+
if (f2 === "PNG" || f2 === "JPEG") {
|
|
1259
|
+
sniff.format = f2;
|
|
1260
|
+
sniff.ext = f2 === "JPEG" ? "jpg" : "png";
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
const key = alias || `${fnv1a(u8)}-${u8.length}`;
|
|
1264
|
+
let img = this.images[key];
|
|
1265
|
+
if (!img) {
|
|
1266
|
+
img = {
|
|
1267
|
+
alias: key,
|
|
1268
|
+
data: u8,
|
|
1269
|
+
format: sniff.format,
|
|
1270
|
+
ext: sniff.ext,
|
|
1271
|
+
width: sniff.width,
|
|
1272
|
+
height: sniff.height,
|
|
1273
|
+
resId: 0
|
|
1274
|
+
};
|
|
1275
|
+
this.images[key] = img;
|
|
1276
|
+
this.imageList.push(img);
|
|
1277
|
+
}
|
|
1278
|
+
let W;
|
|
1279
|
+
let H;
|
|
1280
|
+
if (pw === void 0 || pw === null || ph === void 0 || ph === null) {
|
|
1281
|
+
if (img.width && img.height) {
|
|
1282
|
+
W = img.width * 0.75;
|
|
1283
|
+
H = img.height * 0.75;
|
|
1284
|
+
} else {
|
|
1285
|
+
throw new Error("addImage: image dimensions unknown; provide w/h explicitly");
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
W = this._u(Number(pw));
|
|
1289
|
+
H = this._u(Number(ph));
|
|
1290
|
+
}
|
|
1291
|
+
this.pages[this.page].objects.push({
|
|
1292
|
+
t: "image",
|
|
1293
|
+
x: this._u(Number(px)),
|
|
1294
|
+
y: this._u(Number(py)),
|
|
1295
|
+
w: W,
|
|
1296
|
+
h: H,
|
|
1297
|
+
imageRef: img,
|
|
1298
|
+
angle: Number(rotation) || 0,
|
|
1299
|
+
opacity: this.opacity
|
|
1300
|
+
});
|
|
1301
|
+
return this;
|
|
1302
|
+
},
|
|
1303
|
+
/**
|
|
1304
|
+
* Inspect an image without embedding it.
|
|
1305
|
+
*
|
|
1306
|
+
* @returns format, pixel dimensions, byte count and the raw data
|
|
1307
|
+
*/
|
|
1308
|
+
getImageProperties(imageData) {
|
|
1309
|
+
let u8;
|
|
1310
|
+
if (typeof imageData === "string") {
|
|
1311
|
+
u8 = b64Decode(imageData);
|
|
1312
|
+
} else if (imageData instanceof Uint8Array || imageData instanceof ArrayBuffer) {
|
|
1313
|
+
u8 = u8FromBuffer(imageData);
|
|
1314
|
+
} else {
|
|
1315
|
+
throw new Error(
|
|
1316
|
+
"getImageProperties: only data URLs, Uint8Array and ArrayBuffer are supported"
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
const info = sniffImage(u8);
|
|
1320
|
+
if (!info) throw new Error("getImageProperties: unrecognized image");
|
|
1321
|
+
return {
|
|
1322
|
+
fileType: info.format,
|
|
1323
|
+
width: info.width,
|
|
1324
|
+
height: info.height,
|
|
1325
|
+
bytes: u8.length,
|
|
1326
|
+
data: u8
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
// src/modules/shapes.ts
|
|
1332
|
+
var KAPPA = 0.5523;
|
|
1333
|
+
var ShapeApi = {
|
|
1334
|
+
/**
|
|
1335
|
+
* @internal Push a path onto the current page using the active graphics
|
|
1336
|
+
* state. All public shape helpers funnel through this single entry point.
|
|
1337
|
+
*/
|
|
1338
|
+
_pushPath(ops, style) {
|
|
1339
|
+
const flags = styleFlags(style);
|
|
1340
|
+
this.pages[this.page].objects.push({
|
|
1341
|
+
t: "path",
|
|
1342
|
+
ops,
|
|
1343
|
+
fill: flags.fill,
|
|
1344
|
+
stroke: flags.stroke,
|
|
1345
|
+
fillColor: this.fillColor.slice(),
|
|
1346
|
+
strokeColor: this.drawColor.slice(),
|
|
1347
|
+
lineWidth: this.lineWidth,
|
|
1348
|
+
dash: this.lineDash ? { pattern: this.lineDash.pattern.slice(), phase: this.lineDash.phase } : null,
|
|
1349
|
+
cap: this.lineCap,
|
|
1350
|
+
join: this.lineJoin,
|
|
1351
|
+
miterLimit: this.miterLimit,
|
|
1352
|
+
opacity: this.opacity
|
|
1353
|
+
});
|
|
1354
|
+
return this;
|
|
1355
|
+
},
|
|
1356
|
+
/** Draw a straight line from `(x1, y1)` to `(x2, y2)`. */
|
|
1357
|
+
line(x1, y1, x2, y2, style) {
|
|
1358
|
+
return this._pushPath(
|
|
1359
|
+
[
|
|
1360
|
+
{ op: "M", x: this._u(x1), y: this._u(y1) },
|
|
1361
|
+
{ op: "L", x: this._u(x2), y: this._u(y2) }
|
|
1362
|
+
],
|
|
1363
|
+
style
|
|
1364
|
+
);
|
|
1365
|
+
},
|
|
1366
|
+
/**
|
|
1367
|
+
* Draw a polyline or curve through relative segments (jsPDF `lines`).
|
|
1368
|
+
*
|
|
1369
|
+
* The path starts with `M` at the anchor `(x, y)`. Each segment is a
|
|
1370
|
+
* cumulative delta relative to the current point:
|
|
1371
|
+
* `[dx, dy]` draws a straight line, `[x1, y1, x2, y2, x3, y3]` a cubic
|
|
1372
|
+
* Bézier through three delta points.
|
|
1373
|
+
*
|
|
1374
|
+
* Supports the legacy signature `lines(x, y, lines, scale, style, closed)`.
|
|
1375
|
+
*/
|
|
1376
|
+
lines(linesOrX, xOrY, yOrLines, scale1OrStyle, scale2, styleOrClosed, _closedArg) {
|
|
1377
|
+
let segs;
|
|
1378
|
+
let x;
|
|
1379
|
+
let y;
|
|
1380
|
+
let s1 = 1;
|
|
1381
|
+
let s2 = 1;
|
|
1382
|
+
let style;
|
|
1383
|
+
let closed;
|
|
1384
|
+
if (typeof linesOrX === "number") {
|
|
1385
|
+
segs = yOrLines;
|
|
1386
|
+
x = linesOrX;
|
|
1387
|
+
y = Number(xOrY);
|
|
1388
|
+
const sc = scale1OrStyle;
|
|
1389
|
+
if (Array.isArray(sc)) {
|
|
1390
|
+
s1 = Number(sc[0]) || 1;
|
|
1391
|
+
s2 = Number(sc[1]) || 1;
|
|
1392
|
+
} else if (typeof sc === "number") {
|
|
1393
|
+
s1 = sc;
|
|
1394
|
+
s2 = typeof scale2 === "number" ? scale2 : sc;
|
|
1395
|
+
}
|
|
1396
|
+
style = typeof scale2 === "string" ? scale2 : void 0;
|
|
1397
|
+
closed = Boolean(styleOrClosed);
|
|
1398
|
+
} else {
|
|
1399
|
+
segs = linesOrX;
|
|
1400
|
+
x = Number(xOrY);
|
|
1401
|
+
y = Number(yOrLines);
|
|
1402
|
+
const sc = scale1OrStyle;
|
|
1403
|
+
if (Array.isArray(sc)) {
|
|
1404
|
+
s1 = Number(sc[0]) || 1;
|
|
1405
|
+
s2 = Number(sc[1]) || 1;
|
|
1406
|
+
} else {
|
|
1407
|
+
s1 = typeof sc === "number" ? sc : 1;
|
|
1408
|
+
s2 = typeof scale2 === "number" ? scale2 : s1;
|
|
1409
|
+
}
|
|
1410
|
+
style = typeof scale2 === "string" ? scale2 : void 0;
|
|
1411
|
+
closed = Boolean(styleOrClosed);
|
|
1412
|
+
}
|
|
1413
|
+
const cx = this._u(x);
|
|
1414
|
+
const cy = this._u(y);
|
|
1415
|
+
const sf = this.scaleFactor;
|
|
1416
|
+
const ops = [{ op: "M", x: cx, y: cy }];
|
|
1417
|
+
let curX = cx;
|
|
1418
|
+
let curY = cy;
|
|
1419
|
+
for (const seg of segs) {
|
|
1420
|
+
if (seg.length === 2) {
|
|
1421
|
+
curX += seg[0] * sf * s1;
|
|
1422
|
+
curY += seg[1] * sf * s2;
|
|
1423
|
+
ops.push({ op: "L", x: curX, y: curY });
|
|
1424
|
+
} else if (seg.length === 6) {
|
|
1425
|
+
ops.push({
|
|
1426
|
+
op: "C",
|
|
1427
|
+
x1: curX + seg[0] * sf * s1,
|
|
1428
|
+
y1: curY + seg[1] * sf * s2,
|
|
1429
|
+
x2: curX + seg[2] * sf * s1,
|
|
1430
|
+
y2: curY + seg[3] * sf * s2,
|
|
1431
|
+
x: curX + seg[4] * sf * s1,
|
|
1432
|
+
y: curY + seg[5] * sf * s2
|
|
1433
|
+
});
|
|
1434
|
+
curX += seg[4] * sf * s1;
|
|
1435
|
+
curY += seg[5] * sf * s2;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
if (closed) ops.push({ op: "Z" });
|
|
1439
|
+
return this._pushPath(ops, style);
|
|
1440
|
+
},
|
|
1441
|
+
/** Draw a triangle through three vertices. */
|
|
1442
|
+
triangle(x1, y1, x2, y2, x3, y3, style) {
|
|
1443
|
+
return this._pushPath(
|
|
1444
|
+
[
|
|
1445
|
+
{ op: "M", x: this._u(x1), y: this._u(y1) },
|
|
1446
|
+
{ op: "L", x: this._u(x2), y: this._u(y2) },
|
|
1447
|
+
{ op: "L", x: this._u(x3), y: this._u(y3) },
|
|
1448
|
+
{ op: "Z" }
|
|
1449
|
+
],
|
|
1450
|
+
style
|
|
1451
|
+
);
|
|
1452
|
+
},
|
|
1453
|
+
/** Draw a rectangle; `(x, y)` is the top-left corner. */
|
|
1454
|
+
rect(x, y, w, h, style) {
|
|
1455
|
+
const X = this._u(x);
|
|
1456
|
+
const Y = this._u(y);
|
|
1457
|
+
const W = this._u(w);
|
|
1458
|
+
const H = this._u(h);
|
|
1459
|
+
return this._pushPath(
|
|
1460
|
+
[
|
|
1461
|
+
{ op: "M", x: X, y: Y },
|
|
1462
|
+
{ op: "L", x: X + W, y: Y },
|
|
1463
|
+
{ op: "L", x: X + W, y: Y + H },
|
|
1464
|
+
{ op: "L", x: X, y: Y + H },
|
|
1465
|
+
{ op: "Z" }
|
|
1466
|
+
],
|
|
1467
|
+
style
|
|
1468
|
+
);
|
|
1469
|
+
},
|
|
1470
|
+
/**
|
|
1471
|
+
* Draw a rounded rectangle; `(x, y)` is the top-left corner.
|
|
1472
|
+
*
|
|
1473
|
+
* `ry` defaults to `rx`. Corners are quarter circles approximated with
|
|
1474
|
+
* cubic Béziers (κ = 0.5523).
|
|
1475
|
+
*/
|
|
1476
|
+
roundedRect(x, y, w, h, rx, ry, style) {
|
|
1477
|
+
const X = this._u(x);
|
|
1478
|
+
const Y = this._u(y);
|
|
1479
|
+
const W = this._u(w);
|
|
1480
|
+
const H = this._u(h);
|
|
1481
|
+
const RX = Math.min(this._u(rx), W / 2);
|
|
1482
|
+
const RY = Math.min(ry === void 0 ? this._u(rx) : this._u(ry), H / 2);
|
|
1483
|
+
const k = KAPPA;
|
|
1484
|
+
return this._pushPath(
|
|
1485
|
+
[
|
|
1486
|
+
{ op: "M", x: X + RX, y: Y },
|
|
1487
|
+
{ op: "L", x: X + W - RX, y: Y },
|
|
1488
|
+
{
|
|
1489
|
+
op: "C",
|
|
1490
|
+
x1: X + W - RX + RX * k,
|
|
1491
|
+
y1: Y,
|
|
1492
|
+
x2: X + W,
|
|
1493
|
+
y2: Y + RY - RY * k,
|
|
1494
|
+
x: X + W,
|
|
1495
|
+
y: Y + RY
|
|
1496
|
+
},
|
|
1497
|
+
{ op: "L", x: X + W, y: Y + H - RY },
|
|
1498
|
+
{
|
|
1499
|
+
op: "C",
|
|
1500
|
+
x1: X + W,
|
|
1501
|
+
y1: Y + H - RY + RY * k,
|
|
1502
|
+
x2: X + W - RX + RX * k,
|
|
1503
|
+
y2: Y + H,
|
|
1504
|
+
x: X + W - RX,
|
|
1505
|
+
y: Y + H
|
|
1506
|
+
},
|
|
1507
|
+
{ op: "L", x: X + RX, y: Y + H },
|
|
1508
|
+
{
|
|
1509
|
+
op: "C",
|
|
1510
|
+
x1: X + RX - RX * k,
|
|
1511
|
+
y1: Y + H,
|
|
1512
|
+
x2: X,
|
|
1513
|
+
y2: Y + H - RY + RY * k,
|
|
1514
|
+
x: X,
|
|
1515
|
+
y: Y + H - RY
|
|
1516
|
+
},
|
|
1517
|
+
{ op: "L", x: X, y: Y + RY },
|
|
1518
|
+
{
|
|
1519
|
+
op: "C",
|
|
1520
|
+
x1: X,
|
|
1521
|
+
y1: Y + RY - RY * k,
|
|
1522
|
+
x2: X + RX - RX * k,
|
|
1523
|
+
y2: Y,
|
|
1524
|
+
x: X + RX,
|
|
1525
|
+
y: Y
|
|
1526
|
+
},
|
|
1527
|
+
{ op: "Z" }
|
|
1528
|
+
],
|
|
1529
|
+
style
|
|
1530
|
+
);
|
|
1531
|
+
},
|
|
1532
|
+
/** Draw an ellipse centered at `(x, y)`. */
|
|
1533
|
+
ellipse(x, y, rx, ry, style) {
|
|
1534
|
+
const cx = this._u(x);
|
|
1535
|
+
const cy = this._u(y);
|
|
1536
|
+
const RX = this._u(rx);
|
|
1537
|
+
const RY = this._u(ry);
|
|
1538
|
+
const k = KAPPA;
|
|
1539
|
+
return this._pushPath(
|
|
1540
|
+
[
|
|
1541
|
+
{ op: "M", x: cx - RX, y: cy },
|
|
1542
|
+
{
|
|
1543
|
+
op: "C",
|
|
1544
|
+
x1: cx - RX,
|
|
1545
|
+
y1: cy - RY * k,
|
|
1546
|
+
x2: cx - RX * k,
|
|
1547
|
+
y2: cy - RY,
|
|
1548
|
+
x: cx,
|
|
1549
|
+
y: cy - RY
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
op: "C",
|
|
1553
|
+
x1: cx + RX * k,
|
|
1554
|
+
y1: cy - RY,
|
|
1555
|
+
x2: cx + RX,
|
|
1556
|
+
y2: cy - RY * k,
|
|
1557
|
+
x: cx + RX,
|
|
1558
|
+
y: cy
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
op: "C",
|
|
1562
|
+
x1: cx + RX,
|
|
1563
|
+
y1: cy + RY * k,
|
|
1564
|
+
x2: cx + RX * k,
|
|
1565
|
+
y2: cy + RY,
|
|
1566
|
+
x: cx,
|
|
1567
|
+
y: cy + RY
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
op: "C",
|
|
1571
|
+
x1: cx - RX * k,
|
|
1572
|
+
y1: cy + RY,
|
|
1573
|
+
x2: cx - RX,
|
|
1574
|
+
y2: cy + RY * k,
|
|
1575
|
+
x: cx - RX,
|
|
1576
|
+
y: cy
|
|
1577
|
+
},
|
|
1578
|
+
{ op: "Z" }
|
|
1579
|
+
],
|
|
1580
|
+
style
|
|
1581
|
+
);
|
|
1582
|
+
},
|
|
1583
|
+
/** Draw a circle centered at `(x, y)` with radius `r`. */
|
|
1584
|
+
circle(x, y, r, style) {
|
|
1585
|
+
return this.ellipse(x, y, r, r, style);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
// src/font-parse.ts
|
|
1590
|
+
function readTables(dv, base) {
|
|
1591
|
+
const numTables = dv.getUint16(base + 4);
|
|
1592
|
+
const tables = [];
|
|
1593
|
+
for (let i = 0; i < numTables; i++) {
|
|
1594
|
+
const at = base + 12 + i * 16;
|
|
1595
|
+
tables.push({
|
|
1596
|
+
tag: String.fromCharCode(
|
|
1597
|
+
dv.getUint8(at),
|
|
1598
|
+
dv.getUint8(at + 1),
|
|
1599
|
+
dv.getUint8(at + 2),
|
|
1600
|
+
dv.getUint8(at + 3)
|
|
1601
|
+
),
|
|
1602
|
+
offset: base + dv.getUint32(at + 8),
|
|
1603
|
+
length: dv.getUint32(at + 12)
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
return tables;
|
|
1607
|
+
}
|
|
1608
|
+
function parseCmap(dv, table) {
|
|
1609
|
+
const map = /* @__PURE__ */ new Map();
|
|
1610
|
+
const base = table.offset;
|
|
1611
|
+
const numTables = dv.getUint16(base + 2);
|
|
1612
|
+
for (let i = 0; i < numTables; i++) {
|
|
1613
|
+
const subAt = base + 4 + i * 8;
|
|
1614
|
+
const platform = dv.getUint16(subAt);
|
|
1615
|
+
const encoding = dv.getUint16(subAt + 2);
|
|
1616
|
+
const offset = base + dv.getUint32(subAt + 4);
|
|
1617
|
+
const format = dv.getUint16(offset);
|
|
1618
|
+
const interesting = platform === 0 || platform === 3 && (encoding === 1 || encoding === 10);
|
|
1619
|
+
if (!interesting) continue;
|
|
1620
|
+
if (format === 4) {
|
|
1621
|
+
parseCmap4(dv, offset, map);
|
|
1622
|
+
} else if (format === 12) {
|
|
1623
|
+
parseCmap12(dv, offset, map);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return map;
|
|
1627
|
+
}
|
|
1628
|
+
function parseCmap4(dv, offset, map) {
|
|
1629
|
+
const segCountX2 = dv.getUint16(offset + 6);
|
|
1630
|
+
const segCount = segCountX2 / 2;
|
|
1631
|
+
const endAt = offset + 14;
|
|
1632
|
+
const startAt = endAt + segCountX2 + 2;
|
|
1633
|
+
const deltaAt = startAt + segCountX2;
|
|
1634
|
+
const rangeAt = deltaAt + segCountX2;
|
|
1635
|
+
for (let s = 0; s < segCount; s++) {
|
|
1636
|
+
const end = dv.getUint16(endAt + s * 2);
|
|
1637
|
+
const start = dv.getUint16(startAt + s * 2);
|
|
1638
|
+
const delta = dv.getInt16(deltaAt + s * 2);
|
|
1639
|
+
const rangeOffsetPos = rangeAt + s * 2;
|
|
1640
|
+
const rangeOffset = dv.getUint16(rangeOffsetPos);
|
|
1641
|
+
if (start === 65535) continue;
|
|
1642
|
+
for (let c = start; c <= end && c - start < 1024; c++) {
|
|
1643
|
+
let gid;
|
|
1644
|
+
if (rangeOffset === 0) {
|
|
1645
|
+
gid = c + delta & 65535;
|
|
1646
|
+
} else {
|
|
1647
|
+
const glyphAt = rangeOffsetPos + rangeOffset + (c - start) * 2;
|
|
1648
|
+
if (glyphAt + 1 >= dv.byteLength) continue;
|
|
1649
|
+
gid = dv.getUint16(glyphAt);
|
|
1650
|
+
if (gid !== 0) gid = gid + delta & 65535;
|
|
1651
|
+
}
|
|
1652
|
+
if (gid !== 0 && !map.has(c)) map.set(c, gid);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function parseCmap12(dv, offset, map) {
|
|
1657
|
+
const nGroups = dv.getUint32(offset + 12);
|
|
1658
|
+
for (let g = 0; g < nGroups; g++) {
|
|
1659
|
+
const at = offset + 16 + g * 12;
|
|
1660
|
+
const start = dv.getUint32(at);
|
|
1661
|
+
const end = dv.getUint32(at + 4);
|
|
1662
|
+
const startGid = dv.getUint32(at + 8);
|
|
1663
|
+
for (let c = start; c <= end && c - start < 65536; c++) {
|
|
1664
|
+
if (!map.has(c)) map.set(c, startGid + (c - start));
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
function parseFont2(data) {
|
|
1669
|
+
try {
|
|
1670
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1671
|
+
const tag = String.fromCharCode(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));
|
|
1672
|
+
const version2 = dv.getUint32(0);
|
|
1673
|
+
let base = 0;
|
|
1674
|
+
if (tag === "ttcf")
|
|
1675
|
+
base = dv.getUint32(12);
|
|
1676
|
+
else if (version2 !== 65536 && tag !== "OTTO" && tag !== "true") return null;
|
|
1677
|
+
const tables = readTables(dv, base);
|
|
1678
|
+
const byTag = (t) => tables.find((x) => x.tag === t);
|
|
1679
|
+
const head = byTag("head");
|
|
1680
|
+
const hhea = byTag("hhea");
|
|
1681
|
+
const hmtx = byTag("hmtx");
|
|
1682
|
+
const cmap = byTag("cmap");
|
|
1683
|
+
if (!head || !hhea || !hmtx || !cmap) return null;
|
|
1684
|
+
const unitsPerEm = dv.getUint16(head.offset + 18);
|
|
1685
|
+
let ascent = dv.getInt16(hhea.offset + 4);
|
|
1686
|
+
let descent = dv.getInt16(hhea.offset + 6);
|
|
1687
|
+
const os2 = byTag("OS/2");
|
|
1688
|
+
if (os2) {
|
|
1689
|
+
const winAscent = dv.getUint16(os2.offset + 74);
|
|
1690
|
+
const winDescent = dv.getUint16(os2.offset + 76);
|
|
1691
|
+
ascent = Math.max(ascent, winAscent);
|
|
1692
|
+
descent = Math.min(descent, -winDescent);
|
|
1693
|
+
}
|
|
1694
|
+
const numHMetrics = dv.getUint16(hhea.offset + 34);
|
|
1695
|
+
const advanceOf = (gid) => {
|
|
1696
|
+
const i = Math.min(gid, numHMetrics - 1);
|
|
1697
|
+
return dv.getUint16(hmtx.offset + i * 4);
|
|
1698
|
+
};
|
|
1699
|
+
const glyphMap = parseCmap(dv, cmap);
|
|
1700
|
+
const widths = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const [code, gid] of glyphMap) {
|
|
1702
|
+
widths.set(code, advanceOf(gid));
|
|
1703
|
+
}
|
|
1704
|
+
return { unitsPerEm, ascent, descent, widths, glyphs: glyphMap };
|
|
1705
|
+
} catch (e) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// src/metrics.ts
|
|
1711
|
+
var W_HELV = [
|
|
1712
|
+
278,
|
|
1713
|
+
278,
|
|
1714
|
+
355,
|
|
1715
|
+
556,
|
|
1716
|
+
556,
|
|
1717
|
+
889,
|
|
1718
|
+
667,
|
|
1719
|
+
191,
|
|
1720
|
+
333,
|
|
1721
|
+
333,
|
|
1722
|
+
389,
|
|
1723
|
+
584,
|
|
1724
|
+
278,
|
|
1725
|
+
333,
|
|
1726
|
+
278,
|
|
1727
|
+
278,
|
|
1728
|
+
556,
|
|
1729
|
+
556,
|
|
1730
|
+
556,
|
|
1731
|
+
556,
|
|
1732
|
+
556,
|
|
1733
|
+
556,
|
|
1734
|
+
556,
|
|
1735
|
+
556,
|
|
1736
|
+
556,
|
|
1737
|
+
556,
|
|
1738
|
+
278,
|
|
1739
|
+
278,
|
|
1740
|
+
584,
|
|
1741
|
+
584,
|
|
1742
|
+
584,
|
|
1743
|
+
556,
|
|
1744
|
+
1015,
|
|
1745
|
+
667,
|
|
1746
|
+
667,
|
|
1747
|
+
722,
|
|
1748
|
+
722,
|
|
1749
|
+
667,
|
|
1750
|
+
611,
|
|
1751
|
+
778,
|
|
1752
|
+
722,
|
|
1753
|
+
278,
|
|
1754
|
+
500,
|
|
1755
|
+
667,
|
|
1756
|
+
556,
|
|
1757
|
+
833,
|
|
1758
|
+
722,
|
|
1759
|
+
778,
|
|
1760
|
+
667,
|
|
1761
|
+
778,
|
|
1762
|
+
722,
|
|
1763
|
+
667,
|
|
1764
|
+
611,
|
|
1765
|
+
722,
|
|
1766
|
+
667,
|
|
1767
|
+
944,
|
|
1768
|
+
667,
|
|
1769
|
+
667,
|
|
1770
|
+
611,
|
|
1771
|
+
278,
|
|
1772
|
+
278,
|
|
1773
|
+
278,
|
|
1774
|
+
469,
|
|
1775
|
+
556,
|
|
1776
|
+
333,
|
|
1777
|
+
556,
|
|
1778
|
+
556,
|
|
1779
|
+
500,
|
|
1780
|
+
556,
|
|
1781
|
+
556,
|
|
1782
|
+
278,
|
|
1783
|
+
556,
|
|
1784
|
+
556,
|
|
1785
|
+
222,
|
|
1786
|
+
222,
|
|
1787
|
+
500,
|
|
1788
|
+
222,
|
|
1789
|
+
833,
|
|
1790
|
+
556,
|
|
1791
|
+
556,
|
|
1792
|
+
556,
|
|
1793
|
+
556,
|
|
1794
|
+
333,
|
|
1795
|
+
500,
|
|
1796
|
+
278,
|
|
1797
|
+
556,
|
|
1798
|
+
500,
|
|
1799
|
+
722,
|
|
1800
|
+
500,
|
|
1801
|
+
500,
|
|
1802
|
+
500,
|
|
1803
|
+
334,
|
|
1804
|
+
260,
|
|
1805
|
+
334,
|
|
1806
|
+
584
|
|
1807
|
+
];
|
|
1808
|
+
var W_HELV_BOLD = [
|
|
1809
|
+
278,
|
|
1810
|
+
333,
|
|
1811
|
+
474,
|
|
1812
|
+
556,
|
|
1813
|
+
556,
|
|
1814
|
+
889,
|
|
1815
|
+
722,
|
|
1816
|
+
238,
|
|
1817
|
+
333,
|
|
1818
|
+
333,
|
|
1819
|
+
389,
|
|
1820
|
+
584,
|
|
1821
|
+
278,
|
|
1822
|
+
333,
|
|
1823
|
+
278,
|
|
1824
|
+
278,
|
|
1825
|
+
556,
|
|
1826
|
+
556,
|
|
1827
|
+
556,
|
|
1828
|
+
556,
|
|
1829
|
+
556,
|
|
1830
|
+
556,
|
|
1831
|
+
556,
|
|
1832
|
+
556,
|
|
1833
|
+
556,
|
|
1834
|
+
556,
|
|
1835
|
+
333,
|
|
1836
|
+
333,
|
|
1837
|
+
584,
|
|
1838
|
+
584,
|
|
1839
|
+
584,
|
|
1840
|
+
611,
|
|
1841
|
+
975,
|
|
1842
|
+
722,
|
|
1843
|
+
722,
|
|
1844
|
+
722,
|
|
1845
|
+
722,
|
|
1846
|
+
667,
|
|
1847
|
+
611,
|
|
1848
|
+
778,
|
|
1849
|
+
722,
|
|
1850
|
+
278,
|
|
1851
|
+
556,
|
|
1852
|
+
722,
|
|
1853
|
+
611,
|
|
1854
|
+
833,
|
|
1855
|
+
722,
|
|
1856
|
+
778,
|
|
1857
|
+
667,
|
|
1858
|
+
778,
|
|
1859
|
+
722,
|
|
1860
|
+
667,
|
|
1861
|
+
611,
|
|
1862
|
+
722,
|
|
1863
|
+
667,
|
|
1864
|
+
944,
|
|
1865
|
+
667,
|
|
1866
|
+
667,
|
|
1867
|
+
611,
|
|
1868
|
+
333,
|
|
1869
|
+
278,
|
|
1870
|
+
333,
|
|
1871
|
+
584,
|
|
1872
|
+
556,
|
|
1873
|
+
333,
|
|
1874
|
+
556,
|
|
1875
|
+
611,
|
|
1876
|
+
556,
|
|
1877
|
+
611,
|
|
1878
|
+
556,
|
|
1879
|
+
333,
|
|
1880
|
+
611,
|
|
1881
|
+
611,
|
|
1882
|
+
278,
|
|
1883
|
+
278,
|
|
1884
|
+
556,
|
|
1885
|
+
278,
|
|
1886
|
+
889,
|
|
1887
|
+
611,
|
|
1888
|
+
611,
|
|
1889
|
+
611,
|
|
1890
|
+
611,
|
|
1891
|
+
389,
|
|
1892
|
+
556,
|
|
1893
|
+
333,
|
|
1894
|
+
611,
|
|
1895
|
+
556,
|
|
1896
|
+
778,
|
|
1897
|
+
556,
|
|
1898
|
+
556,
|
|
1899
|
+
500,
|
|
1900
|
+
389,
|
|
1901
|
+
280,
|
|
1902
|
+
389,
|
|
1903
|
+
584
|
|
1904
|
+
];
|
|
1905
|
+
var W_TIMES = [
|
|
1906
|
+
250,
|
|
1907
|
+
333,
|
|
1908
|
+
408,
|
|
1909
|
+
500,
|
|
1910
|
+
500,
|
|
1911
|
+
833,
|
|
1912
|
+
778,
|
|
1913
|
+
180,
|
|
1914
|
+
333,
|
|
1915
|
+
333,
|
|
1916
|
+
500,
|
|
1917
|
+
564,
|
|
1918
|
+
250,
|
|
1919
|
+
333,
|
|
1920
|
+
250,
|
|
1921
|
+
278,
|
|
1922
|
+
500,
|
|
1923
|
+
500,
|
|
1924
|
+
500,
|
|
1925
|
+
500,
|
|
1926
|
+
500,
|
|
1927
|
+
500,
|
|
1928
|
+
500,
|
|
1929
|
+
500,
|
|
1930
|
+
500,
|
|
1931
|
+
500,
|
|
1932
|
+
278,
|
|
1933
|
+
278,
|
|
1934
|
+
564,
|
|
1935
|
+
564,
|
|
1936
|
+
564,
|
|
1937
|
+
444,
|
|
1938
|
+
921,
|
|
1939
|
+
722,
|
|
1940
|
+
667,
|
|
1941
|
+
667,
|
|
1942
|
+
722,
|
|
1943
|
+
611,
|
|
1944
|
+
556,
|
|
1945
|
+
722,
|
|
1946
|
+
722,
|
|
1947
|
+
333,
|
|
1948
|
+
389,
|
|
1949
|
+
722,
|
|
1950
|
+
611,
|
|
1951
|
+
889,
|
|
1952
|
+
722,
|
|
1953
|
+
722,
|
|
1954
|
+
556,
|
|
1955
|
+
722,
|
|
1956
|
+
667,
|
|
1957
|
+
556,
|
|
1958
|
+
611,
|
|
1959
|
+
722,
|
|
1960
|
+
722,
|
|
1961
|
+
944,
|
|
1962
|
+
722,
|
|
1963
|
+
722,
|
|
1964
|
+
611,
|
|
1965
|
+
333,
|
|
1966
|
+
278,
|
|
1967
|
+
333,
|
|
1968
|
+
469,
|
|
1969
|
+
500,
|
|
1970
|
+
333,
|
|
1971
|
+
444,
|
|
1972
|
+
500,
|
|
1973
|
+
444,
|
|
1974
|
+
500,
|
|
1975
|
+
444,
|
|
1976
|
+
333,
|
|
1977
|
+
500,
|
|
1978
|
+
500,
|
|
1979
|
+
278,
|
|
1980
|
+
278,
|
|
1981
|
+
500,
|
|
1982
|
+
278,
|
|
1983
|
+
778,
|
|
1984
|
+
500,
|
|
1985
|
+
500,
|
|
1986
|
+
500,
|
|
1987
|
+
500,
|
|
1988
|
+
333,
|
|
1989
|
+
389,
|
|
1990
|
+
278,
|
|
1991
|
+
500,
|
|
1992
|
+
500,
|
|
1993
|
+
722,
|
|
1994
|
+
500,
|
|
1995
|
+
500,
|
|
1996
|
+
444,
|
|
1997
|
+
480,
|
|
1998
|
+
200,
|
|
1999
|
+
480,
|
|
2000
|
+
541
|
|
2001
|
+
];
|
|
2002
|
+
var W_TIMES_BOLD = [
|
|
2003
|
+
250,
|
|
2004
|
+
333,
|
|
2005
|
+
555,
|
|
2006
|
+
500,
|
|
2007
|
+
500,
|
|
2008
|
+
1e3,
|
|
2009
|
+
833,
|
|
2010
|
+
278,
|
|
2011
|
+
333,
|
|
2012
|
+
333,
|
|
2013
|
+
500,
|
|
2014
|
+
564,
|
|
2015
|
+
250,
|
|
2016
|
+
333,
|
|
2017
|
+
250,
|
|
2018
|
+
278,
|
|
2019
|
+
500,
|
|
2020
|
+
500,
|
|
2021
|
+
500,
|
|
2022
|
+
500,
|
|
2023
|
+
500,
|
|
2024
|
+
500,
|
|
2025
|
+
500,
|
|
2026
|
+
500,
|
|
2027
|
+
500,
|
|
2028
|
+
500,
|
|
2029
|
+
333,
|
|
2030
|
+
333,
|
|
2031
|
+
564,
|
|
2032
|
+
564,
|
|
2033
|
+
564,
|
|
2034
|
+
500,
|
|
2035
|
+
975,
|
|
2036
|
+
722,
|
|
2037
|
+
722,
|
|
2038
|
+
722,
|
|
2039
|
+
722,
|
|
2040
|
+
667,
|
|
2041
|
+
611,
|
|
2042
|
+
778,
|
|
2043
|
+
722,
|
|
2044
|
+
278,
|
|
2045
|
+
556,
|
|
2046
|
+
722,
|
|
2047
|
+
611,
|
|
2048
|
+
833,
|
|
2049
|
+
722,
|
|
2050
|
+
778,
|
|
2051
|
+
667,
|
|
2052
|
+
778,
|
|
2053
|
+
722,
|
|
2054
|
+
667,
|
|
2055
|
+
611,
|
|
2056
|
+
722,
|
|
2057
|
+
667,
|
|
2058
|
+
944,
|
|
2059
|
+
667,
|
|
2060
|
+
667,
|
|
2061
|
+
611,
|
|
2062
|
+
333,
|
|
2063
|
+
278,
|
|
2064
|
+
333,
|
|
2065
|
+
584,
|
|
2066
|
+
556,
|
|
2067
|
+
333,
|
|
2068
|
+
500,
|
|
2069
|
+
556,
|
|
2070
|
+
444,
|
|
2071
|
+
556,
|
|
2072
|
+
556,
|
|
2073
|
+
333,
|
|
2074
|
+
500,
|
|
2075
|
+
556,
|
|
2076
|
+
278,
|
|
2077
|
+
333,
|
|
2078
|
+
556,
|
|
2079
|
+
278,
|
|
2080
|
+
833,
|
|
2081
|
+
556,
|
|
2082
|
+
556,
|
|
2083
|
+
556,
|
|
2084
|
+
556,
|
|
2085
|
+
444,
|
|
2086
|
+
389,
|
|
2087
|
+
333,
|
|
2088
|
+
556,
|
|
2089
|
+
500,
|
|
2090
|
+
722,
|
|
2091
|
+
500,
|
|
2092
|
+
500,
|
|
2093
|
+
444,
|
|
2094
|
+
394,
|
|
2095
|
+
220,
|
|
2096
|
+
394,
|
|
2097
|
+
520
|
|
2098
|
+
];
|
|
2099
|
+
var W_TIMES_ITALIC = [
|
|
2100
|
+
250,
|
|
2101
|
+
333,
|
|
2102
|
+
420,
|
|
2103
|
+
500,
|
|
2104
|
+
500,
|
|
2105
|
+
833,
|
|
2106
|
+
778,
|
|
2107
|
+
214,
|
|
2108
|
+
333,
|
|
2109
|
+
333,
|
|
2110
|
+
500,
|
|
2111
|
+
675,
|
|
2112
|
+
250,
|
|
2113
|
+
333,
|
|
2114
|
+
250,
|
|
2115
|
+
278,
|
|
2116
|
+
500,
|
|
2117
|
+
500,
|
|
2118
|
+
500,
|
|
2119
|
+
500,
|
|
2120
|
+
500,
|
|
2121
|
+
500,
|
|
2122
|
+
500,
|
|
2123
|
+
500,
|
|
2124
|
+
500,
|
|
2125
|
+
500,
|
|
2126
|
+
333,
|
|
2127
|
+
333,
|
|
2128
|
+
675,
|
|
2129
|
+
675,
|
|
2130
|
+
675,
|
|
2131
|
+
500,
|
|
2132
|
+
920,
|
|
2133
|
+
611,
|
|
2134
|
+
611,
|
|
2135
|
+
667,
|
|
2136
|
+
722,
|
|
2137
|
+
611,
|
|
2138
|
+
611,
|
|
2139
|
+
722,
|
|
2140
|
+
722,
|
|
2141
|
+
333,
|
|
2142
|
+
444,
|
|
2143
|
+
667,
|
|
2144
|
+
556,
|
|
2145
|
+
833,
|
|
2146
|
+
667,
|
|
2147
|
+
722,
|
|
2148
|
+
611,
|
|
2149
|
+
722,
|
|
2150
|
+
611,
|
|
2151
|
+
500,
|
|
2152
|
+
556,
|
|
2153
|
+
722,
|
|
2154
|
+
611,
|
|
2155
|
+
833,
|
|
2156
|
+
611,
|
|
2157
|
+
556,
|
|
2158
|
+
556,
|
|
2159
|
+
389,
|
|
2160
|
+
278,
|
|
2161
|
+
389,
|
|
2162
|
+
422,
|
|
2163
|
+
500,
|
|
2164
|
+
333,
|
|
2165
|
+
500,
|
|
2166
|
+
500,
|
|
2167
|
+
444,
|
|
2168
|
+
500,
|
|
2169
|
+
444,
|
|
2170
|
+
278,
|
|
2171
|
+
500,
|
|
2172
|
+
500,
|
|
2173
|
+
278,
|
|
2174
|
+
278,
|
|
2175
|
+
444,
|
|
2176
|
+
278,
|
|
2177
|
+
722,
|
|
2178
|
+
500,
|
|
2179
|
+
500,
|
|
2180
|
+
500,
|
|
2181
|
+
500,
|
|
2182
|
+
389,
|
|
2183
|
+
389,
|
|
2184
|
+
278,
|
|
2185
|
+
500,
|
|
2186
|
+
444,
|
|
2187
|
+
667,
|
|
2188
|
+
444,
|
|
2189
|
+
444,
|
|
2190
|
+
389,
|
|
2191
|
+
400,
|
|
2192
|
+
275,
|
|
2193
|
+
400,
|
|
2194
|
+
541
|
|
2195
|
+
];
|
|
2196
|
+
var W_TIMES_BI = [
|
|
2197
|
+
250,
|
|
2198
|
+
389,
|
|
2199
|
+
555,
|
|
2200
|
+
500,
|
|
2201
|
+
500,
|
|
2202
|
+
833,
|
|
2203
|
+
778,
|
|
2204
|
+
278,
|
|
2205
|
+
333,
|
|
2206
|
+
333,
|
|
2207
|
+
500,
|
|
2208
|
+
570,
|
|
2209
|
+
250,
|
|
2210
|
+
333,
|
|
2211
|
+
250,
|
|
2212
|
+
278,
|
|
2213
|
+
500,
|
|
2214
|
+
500,
|
|
2215
|
+
500,
|
|
2216
|
+
500,
|
|
2217
|
+
500,
|
|
2218
|
+
500,
|
|
2219
|
+
500,
|
|
2220
|
+
500,
|
|
2221
|
+
500,
|
|
2222
|
+
500,
|
|
2223
|
+
333,
|
|
2224
|
+
333,
|
|
2225
|
+
570,
|
|
2226
|
+
570,
|
|
2227
|
+
570,
|
|
2228
|
+
500,
|
|
2229
|
+
832,
|
|
2230
|
+
667,
|
|
2231
|
+
667,
|
|
2232
|
+
667,
|
|
2233
|
+
722,
|
|
2234
|
+
611,
|
|
2235
|
+
611,
|
|
2236
|
+
778,
|
|
2237
|
+
722,
|
|
2238
|
+
278,
|
|
2239
|
+
500,
|
|
2240
|
+
667,
|
|
2241
|
+
556,
|
|
2242
|
+
833,
|
|
2243
|
+
722,
|
|
2244
|
+
778,
|
|
2245
|
+
667,
|
|
2246
|
+
778,
|
|
2247
|
+
722,
|
|
2248
|
+
667,
|
|
2249
|
+
667,
|
|
2250
|
+
722,
|
|
2251
|
+
667,
|
|
2252
|
+
1e3,
|
|
2253
|
+
667,
|
|
2254
|
+
667,
|
|
2255
|
+
667,
|
|
2256
|
+
333,
|
|
2257
|
+
278,
|
|
2258
|
+
333,
|
|
2259
|
+
584,
|
|
2260
|
+
556,
|
|
2261
|
+
333,
|
|
2262
|
+
500,
|
|
2263
|
+
500,
|
|
2264
|
+
444,
|
|
2265
|
+
500,
|
|
2266
|
+
444,
|
|
2267
|
+
333,
|
|
2268
|
+
500,
|
|
2269
|
+
556,
|
|
2270
|
+
278,
|
|
2271
|
+
278,
|
|
2272
|
+
500,
|
|
2273
|
+
278,
|
|
2274
|
+
778,
|
|
2275
|
+
556,
|
|
2276
|
+
500,
|
|
2277
|
+
500,
|
|
2278
|
+
500,
|
|
2279
|
+
389,
|
|
2280
|
+
389,
|
|
2281
|
+
278,
|
|
2282
|
+
556,
|
|
2283
|
+
444,
|
|
2284
|
+
667,
|
|
2285
|
+
500,
|
|
2286
|
+
444,
|
|
2287
|
+
389,
|
|
2288
|
+
348,
|
|
2289
|
+
220,
|
|
2290
|
+
348,
|
|
2291
|
+
570
|
|
2292
|
+
];
|
|
2293
|
+
var CJK_FONT_DEF = {
|
|
2294
|
+
serif: true,
|
|
2295
|
+
fixed: false,
|
|
2296
|
+
cjk: true,
|
|
2297
|
+
ascent: 880,
|
|
2298
|
+
descent: -120,
|
|
2299
|
+
widths: null
|
|
2300
|
+
};
|
|
2301
|
+
var BUILTIN_FONTS = {
|
|
2302
|
+
helvetica: {
|
|
2303
|
+
familyName: "Helvetica",
|
|
2304
|
+
displayName: "Helvetica",
|
|
2305
|
+
serif: false,
|
|
2306
|
+
fixed: false,
|
|
2307
|
+
cjk: false,
|
|
2308
|
+
ascent: 718,
|
|
2309
|
+
descent: -207,
|
|
2310
|
+
widths: {
|
|
2311
|
+
normal: W_HELV,
|
|
2312
|
+
bold: W_HELV_BOLD,
|
|
2313
|
+
italic: W_HELV,
|
|
2314
|
+
bolditalic: W_HELV_BOLD
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
times: {
|
|
2318
|
+
familyName: "Times New Roman",
|
|
2319
|
+
displayName: "Times New Roman",
|
|
2320
|
+
serif: true,
|
|
2321
|
+
fixed: false,
|
|
2322
|
+
cjk: false,
|
|
2323
|
+
ascent: 683,
|
|
2324
|
+
descent: -217,
|
|
2325
|
+
widths: {
|
|
2326
|
+
normal: W_TIMES,
|
|
2327
|
+
bold: W_TIMES_BOLD,
|
|
2328
|
+
italic: W_TIMES_ITALIC,
|
|
2329
|
+
bolditalic: W_TIMES_BI
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2332
|
+
courier: {
|
|
2333
|
+
familyName: "Courier New",
|
|
2334
|
+
displayName: "Courier New",
|
|
2335
|
+
serif: false,
|
|
2336
|
+
fixed: true,
|
|
2337
|
+
cjk: false,
|
|
2338
|
+
ascent: 629,
|
|
2339
|
+
descent: -157,
|
|
2340
|
+
widths: null
|
|
2341
|
+
// monospace 600
|
|
2342
|
+
},
|
|
2343
|
+
simsun: { familyName: "SimSun", displayName: "\u5B8B\u4F53", ...CJK_FONT_DEF },
|
|
2344
|
+
simhei: {
|
|
2345
|
+
familyName: "SimHei",
|
|
2346
|
+
displayName: "\u9ED1\u4F53",
|
|
2347
|
+
serif: false,
|
|
2348
|
+
cjk: true,
|
|
2349
|
+
fixed: false,
|
|
2350
|
+
ascent: 880,
|
|
2351
|
+
descent: -120,
|
|
2352
|
+
widths: null
|
|
2353
|
+
},
|
|
2354
|
+
kaiti: { familyName: "KaiTi", displayName: "\u6977\u4F53", ...CJK_FONT_DEF },
|
|
2355
|
+
fangsong: { familyName: "FangSong", displayName: "\u4EFF\u5B8B", ...CJK_FONT_DEF }
|
|
2356
|
+
};
|
|
2357
|
+
var FONT_ALIASES = {
|
|
2358
|
+
arial: "helvetica",
|
|
2359
|
+
"arial unicode ms": "helvetica",
|
|
2360
|
+
helvetica: "helvetica",
|
|
2361
|
+
"helvetica-bold": "helvetica",
|
|
2362
|
+
times: "times",
|
|
2363
|
+
"times new roman": "times",
|
|
2364
|
+
"times-roman": "times",
|
|
2365
|
+
roman: "times",
|
|
2366
|
+
courier: "courier",
|
|
2367
|
+
"courier new": "courier",
|
|
2368
|
+
"courier-new": "courier",
|
|
2369
|
+
simsun: "simsun",
|
|
2370
|
+
sbsimsun: "simsun",
|
|
2371
|
+
\u5B8B\u4F53: "simsun",
|
|
2372
|
+
songti: "simsun",
|
|
2373
|
+
"song ti": "simsun",
|
|
2374
|
+
simhei: "simhei",
|
|
2375
|
+
\u9ED1\u4F53: "simhei",
|
|
2376
|
+
hei: "simhei",
|
|
2377
|
+
kaiti: "kaiti",
|
|
2378
|
+
\u6977\u4F53: "kaiti",
|
|
2379
|
+
kai: "kaiti",
|
|
2380
|
+
fangsong: "fangsong",
|
|
2381
|
+
\u4EFF\u5B8B: "fangsong",
|
|
2382
|
+
fang: "fangsong"
|
|
2383
|
+
};
|
|
2384
|
+
var STYLE_ALIASES = {
|
|
2385
|
+
normal: "normal",
|
|
2386
|
+
regular: "normal",
|
|
2387
|
+
"": "normal",
|
|
2388
|
+
bold: "bold",
|
|
2389
|
+
b: "bold",
|
|
2390
|
+
italic: "italic",
|
|
2391
|
+
i: "italic",
|
|
2392
|
+
oblique: "italic",
|
|
2393
|
+
bolditalic: "bolditalic",
|
|
2394
|
+
bi: "bolditalic",
|
|
2395
|
+
boldoblique: "bolditalic",
|
|
2396
|
+
"bold italic": "bolditalic"
|
|
2397
|
+
};
|
|
2398
|
+
function normalizeStyle(style) {
|
|
2399
|
+
return STYLE_ALIASES[String(style || "").toLowerCase()] || "normal";
|
|
2400
|
+
}
|
|
2401
|
+
function resolveFontKey(name, customFonts) {
|
|
2402
|
+
const k = String(name || "").toLowerCase().trim();
|
|
2403
|
+
if (FONT_ALIASES[k]) return FONT_ALIASES[k];
|
|
2404
|
+
if (BUILTIN_FONTS[k]) return k;
|
|
2405
|
+
if (customFonts[k]) return k;
|
|
2406
|
+
return null;
|
|
2407
|
+
}
|
|
2408
|
+
function isCJKCode(cp) {
|
|
2409
|
+
return cp >= 11904 && cp <= 40959 || // CJK radicals / Han
|
|
2410
|
+
cp >= 12288 && cp <= 12351 || // CJK punctuation
|
|
2411
|
+
cp >= 65280 && cp <= 65519 || // fullwidth forms
|
|
2412
|
+
cp >= 13312 && cp <= 19903 || // Extension A
|
|
2413
|
+
cp >= 63744 && cp <= 64255 || // compatibility ideographs
|
|
2414
|
+
cp >= 131072 && cp <= 196607;
|
|
2415
|
+
}
|
|
2416
|
+
function hasCJK(text) {
|
|
2417
|
+
for (let i = 0; i < text.length; i++) {
|
|
2418
|
+
if (isCJKCode(text.charCodeAt(i))) return true;
|
|
2419
|
+
}
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
function glyphWidth(font, style, ch) {
|
|
2423
|
+
const cp = ch.charCodeAt(0);
|
|
2424
|
+
if (font.unicodeWidths) {
|
|
2425
|
+
const w = font.unicodeWidths.get(cp);
|
|
2426
|
+
if (w !== void 0) return w;
|
|
2427
|
+
if (font.cjk && isCJKCode(cp)) return 1e3;
|
|
2428
|
+
}
|
|
2429
|
+
if (font.cjk) {
|
|
2430
|
+
return isCJKCode(cp) ? 1e3 : 500;
|
|
2431
|
+
}
|
|
2432
|
+
if (!font.widths) return 600;
|
|
2433
|
+
const table = font.widths[style] || font.widths.normal;
|
|
2434
|
+
if (!table) return 556;
|
|
2435
|
+
if (cp >= 32 && cp <= 126) return table[cp - 32];
|
|
2436
|
+
if (cp === 160) return 278;
|
|
2437
|
+
if (cp < 8192) return 556;
|
|
2438
|
+
return 500;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/modules/state.ts
|
|
2442
|
+
var StateApi = {
|
|
2443
|
+
/** Resolve a font key to its definition (custom → builtin → Helvetica). */
|
|
2444
|
+
getFontDef(key) {
|
|
2445
|
+
return this.customFonts[key] || BUILTIN_FONTS[key] || BUILTIN_FONTS.helvetica;
|
|
2446
|
+
},
|
|
2447
|
+
/**
|
|
2448
|
+
* Register a custom font.
|
|
2449
|
+
*
|
|
2450
|
+
* Fonts are referenced by `FontName` and resolved by the reader's local
|
|
2451
|
+
* font table — no font file is embedded.
|
|
2452
|
+
*
|
|
2453
|
+
* @param postScriptName unique registry key
|
|
2454
|
+
* @param fontName family name written to the OFD font declaration
|
|
2455
|
+
* @param fontStyle `normal` / `bold` / `italic` / `bolditalic`
|
|
2456
|
+
* @param opts metrics (ascent/descent/widths) and aliases
|
|
2457
|
+
*/
|
|
2458
|
+
addFont(postScriptName, fontName, fontStyle, opts = {}) {
|
|
2459
|
+
var _a, _b;
|
|
2460
|
+
normalizeStyle(fontStyle);
|
|
2461
|
+
const key = String(postScriptName).toLowerCase();
|
|
2462
|
+
this.customFonts[key] = {
|
|
2463
|
+
familyName: fontName || postScriptName,
|
|
2464
|
+
displayName: opts.familyName || fontName || postScriptName,
|
|
2465
|
+
serif: !!opts.serif,
|
|
2466
|
+
fixed: !!opts.fixed,
|
|
2467
|
+
cjk: !!opts.cjk || hasCJK(String(fontName || "")),
|
|
2468
|
+
ascent: (_a = opts.ascent) != null ? _a : 800,
|
|
2469
|
+
descent: (_b = opts.descent) != null ? _b : -200,
|
|
2470
|
+
widths: opts.widths || null
|
|
2471
|
+
};
|
|
2472
|
+
if (opts.alias) {
|
|
2473
|
+
FONT_ALIASES[String(opts.alias).toLowerCase()] = key;
|
|
2474
|
+
}
|
|
2475
|
+
return key;
|
|
2476
|
+
},
|
|
2477
|
+
/**
|
|
2478
|
+
* Register a font with an embedded file (recommended for CJK documents).
|
|
2479
|
+
*
|
|
2480
|
+
* Parses the font for exact metrics (units per em, ascent/descent, per-glyph
|
|
2481
|
+
* advance widths via cmap/hmtx) and writes the bytes into the OFD package as
|
|
2482
|
+
* `FontFile`. Embedded fonts render identically in every reader — unembedded
|
|
2483
|
+
* fonts depend on the reader's local font table and may show garbage glyphs.
|
|
2484
|
+
*
|
|
2485
|
+
* Registering a key that matches a built-in (`'simsun'`, `'simhei'`, ...)
|
|
2486
|
+
* overrides it, so existing code keeps working while gaining embedding.
|
|
2487
|
+
*
|
|
2488
|
+
* @param postScriptName registry key, e.g. `'simsun'` or `'MyFont'`
|
|
2489
|
+
* @param fontName family name written to the OFD font declaration
|
|
2490
|
+
* @param ttfData TTF/OTF (or TTC) file bytes
|
|
2491
|
+
*/
|
|
2492
|
+
addFontTtf(postScriptName, fontName, ttfData, opts = {}) {
|
|
2493
|
+
var _a, _b, _c;
|
|
2494
|
+
const parsed = parseFont2(ttfData);
|
|
2495
|
+
const key = String(postScriptName).toLowerCase();
|
|
2496
|
+
const scale = (v) => Math.round(v / ((parsed == null ? void 0 : parsed.unitsPerEm) || 1e3) * 1e3);
|
|
2497
|
+
const ext = opts.fontExt || "ttf";
|
|
2498
|
+
const def = {
|
|
2499
|
+
familyName: fontName || postScriptName,
|
|
2500
|
+
displayName: opts.familyName || fontName || postScriptName,
|
|
2501
|
+
serif: !!opts.serif,
|
|
2502
|
+
fixed: !!opts.fixed,
|
|
2503
|
+
cjk: (_a = opts.cjk) != null ? _a : true,
|
|
2504
|
+
// embedded fonts are assumed to cover the glyphs they map
|
|
2505
|
+
ascent: (_b = opts.ascent) != null ? _b : parsed ? scale(parsed.ascent) : 800,
|
|
2506
|
+
descent: (_c = opts.descent) != null ? _c : parsed ? scale(parsed.descent) : -200,
|
|
2507
|
+
widths: opts.widths || null,
|
|
2508
|
+
unicodeWidths: (parsed == null ? void 0 : parsed.widths) ? new Map([...parsed.widths].map(([cp, w]) => [cp, scale(w)])) : void 0,
|
|
2509
|
+
fontFile: { data: ttfData, ext }
|
|
2510
|
+
};
|
|
2511
|
+
this.customFonts[key] = def;
|
|
2512
|
+
if (opts.alias) {
|
|
2513
|
+
FONT_ALIASES[String(opts.alias).toLowerCase()] = key;
|
|
2514
|
+
}
|
|
2515
|
+
return key;
|
|
2516
|
+
},
|
|
2517
|
+
/**
|
|
2518
|
+
* Select the active font.
|
|
2519
|
+
*
|
|
2520
|
+
* Accepts PostScript names (`Helvetica-Bold`), family names (`helvetica`,
|
|
2521
|
+
* `times`), common Chinese names (`宋体`, `黑体`, `楷体`, `仿宋`) and any
|
|
2522
|
+
* previously registered custom key or alias.
|
|
2523
|
+
*/
|
|
2524
|
+
setFont(fontName, fontStyle) {
|
|
2525
|
+
if (fontStyle !== void 0) {
|
|
2526
|
+
this.activeFontStyle = normalizeStyle(fontStyle);
|
|
2527
|
+
}
|
|
2528
|
+
const key = resolveFontKey(fontName, this.customFonts) || String(fontName).toLowerCase();
|
|
2529
|
+
if (!this.customFonts[key] && !BUILTIN_FONTS[key]) {
|
|
2530
|
+
this.addFont(fontName, fontName, this.activeFontStyle);
|
|
2531
|
+
}
|
|
2532
|
+
this.activeFontKey = key;
|
|
2533
|
+
return this;
|
|
2534
|
+
},
|
|
2535
|
+
/** Set the font size in points (independent of the document unit, like jsPDF). */
|
|
2536
|
+
setFontSize(size) {
|
|
2537
|
+
this.fontSize = Number(size);
|
|
2538
|
+
return this;
|
|
2539
|
+
},
|
|
2540
|
+
/** Active font size in points (default 16, like jsPDF). */
|
|
2541
|
+
getFontSize() {
|
|
2542
|
+
return this.fontSize;
|
|
2543
|
+
},
|
|
2544
|
+
/** Set extra spacing between glyphs, in the active unit. */
|
|
2545
|
+
setCharSpace(space) {
|
|
2546
|
+
this.charSpace = Number(space) * this.scaleFactor;
|
|
2547
|
+
return this;
|
|
2548
|
+
},
|
|
2549
|
+
/** Active character spacing in the active unit. */
|
|
2550
|
+
getCharSpace() {
|
|
2551
|
+
return this.charSpace / this.scaleFactor;
|
|
2552
|
+
},
|
|
2553
|
+
/** Set the line height factor (default 1.15, like jsPDF). */
|
|
2554
|
+
setLineHeightFactor(f) {
|
|
2555
|
+
this.lineHeightFactor = Number(f) || 1.15;
|
|
2556
|
+
return this;
|
|
2557
|
+
},
|
|
2558
|
+
/** Registered font keys, built-in first. */
|
|
2559
|
+
getFontList() {
|
|
2560
|
+
const list = Object.keys(BUILTIN_FONTS);
|
|
2561
|
+
for (const k of Object.keys(this.customFonts)) {
|
|
2562
|
+
list.push(this.customFonts[k].familyName);
|
|
2563
|
+
}
|
|
2564
|
+
return list;
|
|
2565
|
+
},
|
|
2566
|
+
/** Information about the active font (jsPDF `getFont()`). */
|
|
2567
|
+
getFont() {
|
|
2568
|
+
return {
|
|
2569
|
+
fontName: this.getFontDef(this.activeFontKey).familyName,
|
|
2570
|
+
fontStyle: this.activeFontStyle,
|
|
2571
|
+
key: this.activeFontKey
|
|
2572
|
+
};
|
|
2573
|
+
},
|
|
2574
|
+
/** Set the text fill color (`r` may also be a color name or hex string). */
|
|
2575
|
+
setTextColor(r, g, b) {
|
|
2576
|
+
this.textColor = parseColor([r, g, b]);
|
|
2577
|
+
return this;
|
|
2578
|
+
},
|
|
2579
|
+
/** Active text color as `#rrggbb` (jsPDF getter semantics). */
|
|
2580
|
+
getTextColor() {
|
|
2581
|
+
return rgbToHex(this.textColor);
|
|
2582
|
+
},
|
|
2583
|
+
/** Set the stroke color used by shape outlines. */
|
|
2584
|
+
setDrawColor(r, g, b) {
|
|
2585
|
+
this.drawColor = parseColor([r, g, b]);
|
|
2586
|
+
return this;
|
|
2587
|
+
},
|
|
2588
|
+
/** Active stroke color as `#rrggbb`. */
|
|
2589
|
+
getDrawColor() {
|
|
2590
|
+
return rgbToHex(this.drawColor);
|
|
2591
|
+
},
|
|
2592
|
+
/** Set the fill color used by filled shapes. */
|
|
2593
|
+
setFillColor(r, g, b) {
|
|
2594
|
+
this.fillColor = parseColor([r, g, b]);
|
|
2595
|
+
return this;
|
|
2596
|
+
},
|
|
2597
|
+
/** Active fill color as `#rrggbb`. */
|
|
2598
|
+
getFillColor() {
|
|
2599
|
+
return rgbToHex(this.fillColor);
|
|
2600
|
+
},
|
|
2601
|
+
/** Set the stroke width in the active unit. */
|
|
2602
|
+
setLineWidth(width) {
|
|
2603
|
+
this.lineWidth = Number(width) * this.scaleFactor;
|
|
2604
|
+
return this;
|
|
2605
|
+
},
|
|
2606
|
+
/** Active stroke width in the active unit. */
|
|
2607
|
+
getLineWidth() {
|
|
2608
|
+
return this.lineWidth / this.scaleFactor;
|
|
2609
|
+
},
|
|
2610
|
+
/** Set the dash pattern (active unit); an empty array clears dashing. */
|
|
2611
|
+
setLineDashPattern(dashArray, dashPhase) {
|
|
2612
|
+
const sf = this.scaleFactor;
|
|
2613
|
+
this.lineDash = {
|
|
2614
|
+
pattern: (dashArray || []).map((v) => Number(v) * sf),
|
|
2615
|
+
phase: (Number(dashPhase) || 0) * sf
|
|
2616
|
+
};
|
|
2617
|
+
return this;
|
|
2618
|
+
},
|
|
2619
|
+
/** Active dash pattern in the active unit. */
|
|
2620
|
+
getLineDashPattern() {
|
|
2621
|
+
return this.lineDash ? this.lineDash.pattern.map((v) => v / this.scaleFactor) : [];
|
|
2622
|
+
},
|
|
2623
|
+
/** Set the line cap: 0 butt, 1 round, 2 square (names accepted). */
|
|
2624
|
+
setLineCap(style) {
|
|
2625
|
+
if (typeof style === "number") this.lineCap = style;
|
|
2626
|
+
else this.lineCap = style === "round" ? 1 : style === "square" ? 2 : 0;
|
|
2627
|
+
return this;
|
|
2628
|
+
},
|
|
2629
|
+
/** Active line cap (0-2). */
|
|
2630
|
+
getLineCap() {
|
|
2631
|
+
return this.lineCap;
|
|
2632
|
+
},
|
|
2633
|
+
/** Set the line join: 0 miter, 1 round, 2 bevel (names accepted). */
|
|
2634
|
+
setLineJoin(style) {
|
|
2635
|
+
if (typeof style === "number") this.lineJoin = style;
|
|
2636
|
+
else this.lineJoin = style === "round" ? 1 : style === "bevel" ? 2 : 0;
|
|
2637
|
+
return this;
|
|
2638
|
+
},
|
|
2639
|
+
/** Active line join (0-2). */
|
|
2640
|
+
getLineJoin() {
|
|
2641
|
+
return this.lineJoin;
|
|
2642
|
+
},
|
|
2643
|
+
/** Set the miter limit (active unit); values ≤ 1 omit the attribute. */
|
|
2644
|
+
setLineMiterLimit(limit) {
|
|
2645
|
+
if (typeof limit !== "number" || isNaN(limit)) {
|
|
2646
|
+
throw new Error("Invalid argument passed to jsOFD.setLineMiterLimit");
|
|
2647
|
+
}
|
|
2648
|
+
this.miterLimit = limit * this.scaleFactor;
|
|
2649
|
+
return this;
|
|
2650
|
+
},
|
|
2651
|
+
/** Active miter limit in the active unit. */
|
|
2652
|
+
getLineMiterLimit() {
|
|
2653
|
+
return this.miterLimit / this.scaleFactor;
|
|
2654
|
+
},
|
|
2655
|
+
/** Line height in points: font size × line height factor. */
|
|
2656
|
+
getLineHeight() {
|
|
2657
|
+
return this.fontSize * this.lineHeightFactor;
|
|
2658
|
+
},
|
|
2659
|
+
/** Enable or disable right-to-left rendering. */
|
|
2660
|
+
setR2L(value) {
|
|
2661
|
+
this.r2l = Boolean(value);
|
|
2662
|
+
return this;
|
|
2663
|
+
},
|
|
2664
|
+
/** Whether right-to-left rendering is active. */
|
|
2665
|
+
getR2L() {
|
|
2666
|
+
return this.r2l;
|
|
2667
|
+
},
|
|
2668
|
+
/**
|
|
2669
|
+
* Apply a graphics state.
|
|
2670
|
+
*
|
|
2671
|
+
* Only `opacity` is supported; other GState keys are ignored (OFD models
|
|
2672
|
+
* blend modes differently from PDF).
|
|
2673
|
+
*/
|
|
2674
|
+
setGState(gs) {
|
|
2675
|
+
if (gs && typeof gs.opacity === "number") {
|
|
2676
|
+
this.opacity = Math.max(0, Math.min(1, gs.opacity));
|
|
2677
|
+
}
|
|
2678
|
+
return this;
|
|
2679
|
+
},
|
|
2680
|
+
/** Set the rendering opacity for subsequent objects (0-1). */
|
|
2681
|
+
setOpacity(opacity) {
|
|
2682
|
+
this.opacity = Math.max(0, Math.min(1, Number(opacity)));
|
|
2683
|
+
return this;
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
|
|
2687
|
+
// src/modules/svg.ts
|
|
2688
|
+
async function svgToPng(svg, widthPx, heightPx, scale, background) {
|
|
2689
|
+
const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
|
|
2690
|
+
const url = URL.createObjectURL(blob);
|
|
2691
|
+
try {
|
|
2692
|
+
const img = new Image();
|
|
2693
|
+
img.decoding = "sync";
|
|
2694
|
+
await new Promise((resolve, reject) => {
|
|
2695
|
+
img.onload = () => resolve();
|
|
2696
|
+
img.onerror = () => reject(new Error("svg(): failed to parse the SVG markup"));
|
|
2697
|
+
img.src = url;
|
|
2698
|
+
});
|
|
2699
|
+
const canvas = document.createElement("canvas");
|
|
2700
|
+
canvas.width = Math.max(1, Math.round(widthPx * scale));
|
|
2701
|
+
canvas.height = Math.max(1, Math.round(heightPx * scale));
|
|
2702
|
+
const g = canvas.getContext("2d");
|
|
2703
|
+
if (background) {
|
|
2704
|
+
g.fillStyle = background;
|
|
2705
|
+
g.fillRect(0, 0, canvas.width, canvas.height);
|
|
2706
|
+
}
|
|
2707
|
+
g.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
2708
|
+
const blob2 = await new Promise((res) => canvas.toBlob(res, "image/png"));
|
|
2709
|
+
if (!blob2) throw new Error("svg(): canvas.toBlob returned null");
|
|
2710
|
+
return new Uint8Array(await blob2.arrayBuffer());
|
|
2711
|
+
} finally {
|
|
2712
|
+
URL.revokeObjectURL(url);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
function intrinsicSize(svg) {
|
|
2716
|
+
const root = /<svg\b([^>]*)>/i.exec(svg);
|
|
2717
|
+
if (!root) return null;
|
|
2718
|
+
const attrs = root[1];
|
|
2719
|
+
const num = (re) => {
|
|
2720
|
+
const m = re.exec(attrs);
|
|
2721
|
+
return m ? parseFloat(m[1]) : null;
|
|
2722
|
+
};
|
|
2723
|
+
let w = num(/\bwidth\s*=\s*["']([\d.]+)/i);
|
|
2724
|
+
let h = num(/\bheight\s*=\s*["']([\d.]+)/i);
|
|
2725
|
+
const vb = /\bviewBox\s*=\s*["']\s*[\d.-]+\s+[\d.-]+\s+([\d.-]+)\s+([\d.-]+)/i.exec(attrs);
|
|
2726
|
+
if (vb) {
|
|
2727
|
+
if (!w) w = parseFloat(vb[1]);
|
|
2728
|
+
if (!h) h = parseFloat(vb[2]);
|
|
2729
|
+
}
|
|
2730
|
+
return w && h ? { w, h } : null;
|
|
2731
|
+
}
|
|
2732
|
+
var SvgApi = {
|
|
2733
|
+
/**
|
|
2734
|
+
* Rasterize an SVG markup string and embed it as a PNG image.
|
|
2735
|
+
*
|
|
2736
|
+
* Browser only (uses `Image` + canvas). The SVG is rendered at `scale`
|
|
2737
|
+
* (default 2×) for crisp output. Use `addImage` when you already have
|
|
2738
|
+
* raster bytes.
|
|
2739
|
+
*/
|
|
2740
|
+
async svg(svgMarkup, options) {
|
|
2741
|
+
var _a, _b, _c, _d;
|
|
2742
|
+
if (typeof document === "undefined" || typeof Image === "undefined") {
|
|
2743
|
+
throw new Error(
|
|
2744
|
+
"svg() requires a browser (Image + canvas). In Node, rasterize first and pass bytes to addImage()."
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
const size = (_a = intrinsicSize(svgMarkup)) != null ? _a : { w: 300, h: 150 };
|
|
2748
|
+
const wPx = options.w !== void 0 ? options.w * this.scaleFactor : size.w;
|
|
2749
|
+
const hPx = options.h !== void 0 ? options.h * this.scaleFactor : wPx * size.h / size.w;
|
|
2750
|
+
const png = await svgToPng(svgMarkup, wPx, hPx, (_b = options.scale) != null ? _b : 2, options.background);
|
|
2751
|
+
const wU = (_c = options.w) != null ? _c : wPx / this.scaleFactor;
|
|
2752
|
+
const hU = (_d = options.h) != null ? _d : hPx / this.scaleFactor;
|
|
2753
|
+
this.addImage(png, {
|
|
2754
|
+
x: options.x,
|
|
2755
|
+
y: options.y,
|
|
2756
|
+
w: wU,
|
|
2757
|
+
h: hU,
|
|
2758
|
+
format: "PNG",
|
|
2759
|
+
rotation: options.rotation,
|
|
2760
|
+
opacity: options.opacity
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
// src/modules/table.ts
|
|
2766
|
+
var TableApi = {
|
|
2767
|
+
/**
|
|
2768
|
+
* Render a table at `(x, y)` and return the bottom Y (active unit).
|
|
2769
|
+
*
|
|
2770
|
+
* Column widths: explicit `width`, otherwise the remaining page width is
|
|
2771
|
+
* shared equally. With `autoPage` (default) rows that would cross the
|
|
2772
|
+
* bottom margin continue on a fresh page under a repeated header.
|
|
2773
|
+
*/
|
|
2774
|
+
table(x, y, options) {
|
|
2775
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2776
|
+
const st = options.style || {};
|
|
2777
|
+
const cols = options.columns;
|
|
2778
|
+
const sf = this.scaleFactor;
|
|
2779
|
+
const xU = this._u(x);
|
|
2780
|
+
const yU = this._u(y);
|
|
2781
|
+
const padPt = this._u((_a = st.cellPadding) != null ? _a : 2);
|
|
2782
|
+
const bodySize = (_b = st.fontSize) != null ? _b : this.fontSize;
|
|
2783
|
+
const headSize = (_c = st.headFontSize) != null ? _c : bodySize;
|
|
2784
|
+
const autoPage = options.autoPage !== false;
|
|
2785
|
+
const marginU = (_d = options.bottomMargin) != null ? _d : 20;
|
|
2786
|
+
const hasHeader = cols.some((c) => c.header !== void 0);
|
|
2787
|
+
const zebra = (_e = st.zebra) != null ? _e : null;
|
|
2788
|
+
const gridColor = st.borderColor === null ? null : (_f = st.borderColor) != null ? _f : [191, 197, 205];
|
|
2789
|
+
const gridW = this._u((_g = st.lineWidth) != null ? _g : 0.2);
|
|
2790
|
+
const pageW = this.pages[this.page].width;
|
|
2791
|
+
const widths = cols.map((c) => c.width !== void 0 ? this._u(c.width) : null);
|
|
2792
|
+
const used = widths.reduce((s, w) => s + (w != null ? w : 0), 0);
|
|
2793
|
+
const flexCount = widths.filter((w) => w === null).length;
|
|
2794
|
+
const flexW = flexCount ? Math.max((pageW - xU - used) / flexCount, 20) : 0;
|
|
2795
|
+
const W = widths.map((w) => w != null ? w : flexW);
|
|
2796
|
+
const totalW = W.reduce((s, w) => s + w, 0);
|
|
2797
|
+
const save = {
|
|
2798
|
+
fontSize: this.fontSize,
|
|
2799
|
+
fill: this.fillColor.slice(),
|
|
2800
|
+
draw: this.drawColor.slice(),
|
|
2801
|
+
text: this.textColor.slice(),
|
|
2802
|
+
lineW: this.lineWidth,
|
|
2803
|
+
fontKey: this.activeFontKey,
|
|
2804
|
+
fontSt: this.activeFontStyle
|
|
2805
|
+
};
|
|
2806
|
+
const restore = () => {
|
|
2807
|
+
this.fontSize = save.fontSize;
|
|
2808
|
+
this.fillColor = save.fill;
|
|
2809
|
+
this.drawColor = save.draw;
|
|
2810
|
+
this.textColor = save.text;
|
|
2811
|
+
this.lineWidth = save.lineW;
|
|
2812
|
+
this.activeFontKey = save.fontKey;
|
|
2813
|
+
this.activeFontStyle = save.fontSt;
|
|
2814
|
+
};
|
|
2815
|
+
const cellLines = (text, wPt, size, bold) => {
|
|
2816
|
+
this.fontSize = size;
|
|
2817
|
+
this.activeFontStyle = bold ? "bold" : "normal";
|
|
2818
|
+
return this._splitCore(
|
|
2819
|
+
text,
|
|
2820
|
+
wPt - padPt * 2,
|
|
2821
|
+
this.getFontDef(this.activeFontKey),
|
|
2822
|
+
this.activeFontStyle
|
|
2823
|
+
);
|
|
2824
|
+
};
|
|
2825
|
+
const rowHeight = (row) => {
|
|
2826
|
+
if (st.rowHeight !== void 0) return this._u(st.rowHeight);
|
|
2827
|
+
let lines = 1;
|
|
2828
|
+
row.forEach((cell, ci) => {
|
|
2829
|
+
var _a2;
|
|
2830
|
+
const spec = typeof cell === "string" ? { text: cell } : cell;
|
|
2831
|
+
const span = Math.max(1, (_a2 = spec.colSpan) != null ? _a2 : 1);
|
|
2832
|
+
const wPt = W.slice(ci, ci + span).reduce((s, ww) => s + ww, 0);
|
|
2833
|
+
lines = Math.max(lines, cellLines(spec.text, wPt, bodySize, !!spec.bold).length);
|
|
2834
|
+
});
|
|
2835
|
+
return padPt * 2 + lines * bodySize * this.lineHeightFactor;
|
|
2836
|
+
};
|
|
2837
|
+
const drawCellText = (cell, cxPt, cyPt, wPt, hPt, header, align, fill) => {
|
|
2838
|
+
var _a2, _b2;
|
|
2839
|
+
const spec = typeof cell === "string" ? { text: cell } : cell;
|
|
2840
|
+
const size = header ? headSize : bodySize;
|
|
2841
|
+
const bold = header || !!spec.bold;
|
|
2842
|
+
if (fill) {
|
|
2843
|
+
this.fillColor = fill;
|
|
2844
|
+
this.rect(cxPt / sf, cyPt / sf, wPt / sf, hPt / sf, "F");
|
|
2845
|
+
}
|
|
2846
|
+
this.fontSize = size;
|
|
2847
|
+
this.activeFontStyle = bold ? "bold" : "normal";
|
|
2848
|
+
this.textColor = header ? (_a2 = st.headColor) != null ? _a2 : [255, 255, 255] : (_b2 = st.textColor) != null ? _b2 : save.text;
|
|
2849
|
+
const def = this.getFontDef(this.activeFontKey);
|
|
2850
|
+
const lines = cellLines(spec.text, wPt, size, bold);
|
|
2851
|
+
const ascPt = def.ascent / 1e3 * size;
|
|
2852
|
+
let ty = cyPt + padPt + ascPt;
|
|
2853
|
+
for (const line of lines) {
|
|
2854
|
+
const w = this.measure(line, def, this.activeFontStyle, size, 0);
|
|
2855
|
+
let tx = cxPt + padPt;
|
|
2856
|
+
if (align === "center") tx = cxPt + (wPt - w) / 2;
|
|
2857
|
+
else if (align === "right") tx = cxPt + wPt - padPt - w;
|
|
2858
|
+
this.text(line, tx / sf, ty / sf);
|
|
2859
|
+
ty += size * this.lineHeightFactor;
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
const hLine = (x1, y1, x2, y2) => {
|
|
2863
|
+
if (!gridColor) return;
|
|
2864
|
+
this.drawColor = gridColor;
|
|
2865
|
+
this.lineWidth = gridW;
|
|
2866
|
+
this.line(x1 / sf, y1 / sf, x2 / sf, y2 / sf, "S");
|
|
2867
|
+
};
|
|
2868
|
+
const headerHeightPt = hasHeader ? st.rowHeight !== void 0 ? this._u(st.rowHeight) : padPt * 2 + headSize * this.lineHeightFactor : 0;
|
|
2869
|
+
const drawHeader = (topPt) => {
|
|
2870
|
+
if (!hasHeader) return;
|
|
2871
|
+
let cx = xU;
|
|
2872
|
+
cols.forEach((col, i) => {
|
|
2873
|
+
var _a2, _b2;
|
|
2874
|
+
drawCellText(
|
|
2875
|
+
(_a2 = col.header) != null ? _a2 : "",
|
|
2876
|
+
cx,
|
|
2877
|
+
topPt,
|
|
2878
|
+
W[i],
|
|
2879
|
+
headerHeightPt,
|
|
2880
|
+
true,
|
|
2881
|
+
col.align,
|
|
2882
|
+
st.headFill === null ? null : (_b2 = st.headFill) != null ? _b2 : [53, 101, 224]
|
|
2883
|
+
);
|
|
2884
|
+
cx += W[i];
|
|
2885
|
+
});
|
|
2886
|
+
let gx = xU;
|
|
2887
|
+
for (let i = 0; i < W.length; i++) {
|
|
2888
|
+
hLine(gx, topPt, gx, topPt + headerHeightPt);
|
|
2889
|
+
gx += W[i];
|
|
2890
|
+
}
|
|
2891
|
+
hLine(gx, topPt, gx, topPt + headerHeightPt);
|
|
2892
|
+
hLine(xU, topPt, xU + totalW, topPt);
|
|
2893
|
+
hLine(xU, topPt + headerHeightPt, xU + totalW, topPt + headerHeightPt);
|
|
2894
|
+
};
|
|
2895
|
+
if (hasHeader) drawHeader(yU);
|
|
2896
|
+
let curY = yU + headerHeightPt;
|
|
2897
|
+
for (let r = 0; r < ((_h = options.rows) != null ? _h : []).length; r++) {
|
|
2898
|
+
const row = ((_i = options.rows) != null ? _i : [])[r];
|
|
2899
|
+
const rowH = rowHeight(row);
|
|
2900
|
+
const pageBottom = this.pages[this.page].height - this._u(marginU);
|
|
2901
|
+
if (autoPage && curY + rowH > pageBottom && this.pages[this.page].height > 0) {
|
|
2902
|
+
this.addPage();
|
|
2903
|
+
(_j = options.onNewPage) == null ? void 0 : _j.call(options, this, { pageNumber: this.pages.length, pageCount: this.pages.length });
|
|
2904
|
+
const topPt = this._u(marginU);
|
|
2905
|
+
drawHeader(topPt);
|
|
2906
|
+
curY = topPt + headerHeightPt;
|
|
2907
|
+
}
|
|
2908
|
+
const fill = zebra ? zebra[r % 2] : null;
|
|
2909
|
+
let cx = xU;
|
|
2910
|
+
row.forEach((cell, ci) => {
|
|
2911
|
+
var _a2, _b2, _c2;
|
|
2912
|
+
const spec = typeof cell === "string" ? { text: cell } : cell;
|
|
2913
|
+
const span = Math.max(1, (_a2 = spec.colSpan) != null ? _a2 : 1);
|
|
2914
|
+
const cw = W.slice(ci, ci + span).reduce((s, ww) => s + ww, 0);
|
|
2915
|
+
drawCellText(cell, cx, curY, cw, rowH, false, (_c2 = spec.align) != null ? _c2 : (_b2 = cols[ci]) == null ? void 0 : _b2.align, fill);
|
|
2916
|
+
cx += cw;
|
|
2917
|
+
});
|
|
2918
|
+
let gx = xU;
|
|
2919
|
+
for (let i = 0; i < W.length; i++) {
|
|
2920
|
+
hLine(gx, curY, gx, curY + rowH);
|
|
2921
|
+
gx += W[i];
|
|
2922
|
+
}
|
|
2923
|
+
hLine(gx, curY, gx, curY + rowH);
|
|
2924
|
+
hLine(xU, curY, xU + totalW, curY);
|
|
2925
|
+
curY += rowH;
|
|
2926
|
+
}
|
|
2927
|
+
if (gridColor) hLine(xU, curY, xU + totalW, curY);
|
|
2928
|
+
restore();
|
|
2929
|
+
return curY / sf;
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
// src/modules/text.ts
|
|
2934
|
+
var TextApi = {
|
|
2935
|
+
/**
|
|
2936
|
+
* Measure the width of `text` in points for an explicit font and size.
|
|
2937
|
+
*
|
|
2938
|
+
* Exposed publicly because jsPDF relies on `doc.measure`-style hooks for
|
|
2939
|
+
* plugins such as autotable.
|
|
2940
|
+
*/
|
|
2941
|
+
measure(text, fontDef, style, sizePt, charSpacePt) {
|
|
2942
|
+
let w = 0;
|
|
2943
|
+
for (let i = 0; i < text.length; i++) {
|
|
2944
|
+
w += glyphWidth(fontDef, style, text.charAt(i));
|
|
2945
|
+
}
|
|
2946
|
+
w = w / 1e3 * sizePt;
|
|
2947
|
+
if (charSpacePt) w += charSpacePt * text.length;
|
|
2948
|
+
return w;
|
|
2949
|
+
},
|
|
2950
|
+
/** String width in font units (jsPDF `getStringUnitWidth`). */
|
|
2951
|
+
getStringUnitWidth(text) {
|
|
2952
|
+
return this.measure(text, this.getFontDef(this.activeFontKey), this.activeFontStyle, 1, 0);
|
|
2953
|
+
},
|
|
2954
|
+
/**
|
|
2955
|
+
* Per-character widths in points (jsPDF `getCharWidthsArray`).
|
|
2956
|
+
*
|
|
2957
|
+
* @param options override `fontSize` (default: active) and `charSpace`
|
|
2958
|
+
*/
|
|
2959
|
+
getCharWidthsArray(text, options) {
|
|
2960
|
+
const size = typeof (options == null ? void 0 : options.fontSize) === "number" ? options.fontSize : this.fontSize;
|
|
2961
|
+
const cs = typeof (options == null ? void 0 : options.charSpace) === "number" ? this._u(options.charSpace) : this.charSpace;
|
|
2962
|
+
const def = this.getFontDef(this.activeFontKey);
|
|
2963
|
+
const widths = [];
|
|
2964
|
+
for (let i = 0; i < text.length; i++) {
|
|
2965
|
+
widths.push(glyphWidth(def, this.activeFontStyle, text.charAt(i)) / 1e3 * size + cs);
|
|
2966
|
+
}
|
|
2967
|
+
return widths;
|
|
2968
|
+
},
|
|
2969
|
+
/** Width of `text` at the active font and size, in the active unit. */
|
|
2970
|
+
getTextWidth(text) {
|
|
2971
|
+
return this.measure(
|
|
2972
|
+
text,
|
|
2973
|
+
this.getFontDef(this.activeFontKey),
|
|
2974
|
+
this.activeFontStyle,
|
|
2975
|
+
this.fontSize,
|
|
2976
|
+
this.charSpace
|
|
2977
|
+
) / this.scaleFactor;
|
|
2978
|
+
},
|
|
2979
|
+
/** Split `text` into lines that fit `maxWidth` (active unit). */
|
|
2980
|
+
splitTextToSize(text, maxWidth) {
|
|
2981
|
+
return this._splitCore(
|
|
2982
|
+
text,
|
|
2983
|
+
this._u(Number(maxWidth)),
|
|
2984
|
+
this.getFontDef(this.activeFontKey),
|
|
2985
|
+
this.activeFontStyle
|
|
2986
|
+
);
|
|
2987
|
+
},
|
|
2988
|
+
/**
|
|
2989
|
+
* Line-breaking core.
|
|
2990
|
+
*
|
|
2991
|
+
* Breaks Latin text at spaces, CJK text between any two characters, and
|
|
2992
|
+
* hard-splits words longer than the line width (jsPDF behaviour).
|
|
2993
|
+
*
|
|
2994
|
+
* @internal maxWidthPt is in points
|
|
2995
|
+
*/
|
|
2996
|
+
_splitCore(text, maxWidthPt, fontDef, style) {
|
|
2997
|
+
const size = this.fontSize;
|
|
2998
|
+
const cs = this.charSpace;
|
|
2999
|
+
const width = (s) => this.measure(s, fontDef, style, size, cs);
|
|
3000
|
+
const out = [];
|
|
3001
|
+
for (const para of String(text).split(/\r\n|\r|\n/)) {
|
|
3002
|
+
if (para === "" || width(para) <= maxWidthPt) {
|
|
3003
|
+
out.push(para);
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
const tokens = [];
|
|
3007
|
+
let buf = "";
|
|
3008
|
+
for (const ch of para) {
|
|
3009
|
+
const cp = ch.charCodeAt(0);
|
|
3010
|
+
if (ch === " ") {
|
|
3011
|
+
if (buf) {
|
|
3012
|
+
tokens.push(buf);
|
|
3013
|
+
buf = "";
|
|
3014
|
+
}
|
|
3015
|
+
tokens.push(" ");
|
|
3016
|
+
} else if (cp >= 11904 && cp <= 40959 || cp >= 12288 && cp <= 12351 || cp >= 65280 && cp <= 65519 || cp >= 13312 && cp <= 19903 || cp >= 63744 && cp <= 64255 || cp >= 131072 && cp <= 196607) {
|
|
3017
|
+
if (buf) {
|
|
3018
|
+
tokens.push(buf);
|
|
3019
|
+
buf = "";
|
|
3020
|
+
}
|
|
3021
|
+
tokens.push(ch);
|
|
3022
|
+
} else {
|
|
3023
|
+
buf += ch;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
if (buf) tokens.push(buf);
|
|
3027
|
+
let line = "";
|
|
3028
|
+
for (const tk of tokens) {
|
|
3029
|
+
const candidate = line + tk;
|
|
3030
|
+
if (width(candidate) <= maxWidthPt) {
|
|
3031
|
+
line = candidate;
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
if (line !== "") {
|
|
3035
|
+
out.push(line.replace(/ +$/, ""));
|
|
3036
|
+
line = "";
|
|
3037
|
+
}
|
|
3038
|
+
if (tk === " ") continue;
|
|
3039
|
+
if (width(tk) > maxWidthPt) {
|
|
3040
|
+
let piece = "";
|
|
3041
|
+
for (const c of tk) {
|
|
3042
|
+
if (width(piece + c) <= maxWidthPt) {
|
|
3043
|
+
piece += c;
|
|
3044
|
+
} else {
|
|
3045
|
+
out.push(piece);
|
|
3046
|
+
piece = c;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
line = piece;
|
|
3050
|
+
} else {
|
|
3051
|
+
line = tk;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
if (line !== "") out.push(line.replace(/ +$/, ""));
|
|
3055
|
+
}
|
|
3056
|
+
return out;
|
|
3057
|
+
},
|
|
3058
|
+
/**
|
|
3059
|
+
* Write text at `(x, y)`.
|
|
3060
|
+
*
|
|
3061
|
+
* The anchor is the left end of the baseline unless changed through the
|
|
3062
|
+
* `baseline` option. Multi-line input is accepted as `\n` separated text
|
|
3063
|
+
* or an array of lines; `maxWidth` enables automatic wrapping.
|
|
3064
|
+
*
|
|
3065
|
+
* Supports the legacy jsPDF argument order `text(x, y, text, options)`.
|
|
3066
|
+
*/
|
|
3067
|
+
text(text, x, y, optionsOrNothing) {
|
|
3068
|
+
let textStr;
|
|
3069
|
+
let xNum;
|
|
3070
|
+
let yNum;
|
|
3071
|
+
let options;
|
|
3072
|
+
if (typeof text === "number") {
|
|
3073
|
+
xNum = text;
|
|
3074
|
+
yNum = Number(x);
|
|
3075
|
+
textStr = y;
|
|
3076
|
+
options = optionsOrNothing || {};
|
|
3077
|
+
} else {
|
|
3078
|
+
textStr = text;
|
|
3079
|
+
xNum = Number(x);
|
|
3080
|
+
yNum = Number(y);
|
|
3081
|
+
options = y || {};
|
|
3082
|
+
if (optionsOrNothing) options = optionsOrNothing;
|
|
3083
|
+
}
|
|
3084
|
+
if (typeof textStr !== "string" && !Array.isArray(textStr)) {
|
|
3085
|
+
throw new Error("Invalid arguments passed to jsOFD.text");
|
|
3086
|
+
}
|
|
3087
|
+
x = this._u(xNum);
|
|
3088
|
+
y = this._u(yNum);
|
|
3089
|
+
const angle = Number(options.angle || 0);
|
|
3090
|
+
const align = String(options.align || "left").toLowerCase();
|
|
3091
|
+
if (!["left", "center", "right", "justify"].includes(align)) {
|
|
3092
|
+
throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".');
|
|
3093
|
+
}
|
|
3094
|
+
const baseline = String(options.baseline || "alphabetic").toLowerCase();
|
|
3095
|
+
const charSpace = typeof options.charSpace === "number" ? this._u(options.charSpace) : this.charSpace;
|
|
3096
|
+
const lineHeightFactor = typeof options.lineHeightFactor === "number" ? options.lineHeightFactor : this.lineHeightFactor;
|
|
3097
|
+
const size = typeof options.fontSize === "number" ? options.fontSize : this.fontSize;
|
|
3098
|
+
const style = this.activeFontStyle;
|
|
3099
|
+
let fontKey = this.activeFontKey;
|
|
3100
|
+
let fontDef = this.getFontDef(fontKey);
|
|
3101
|
+
const raw = Array.isArray(textStr) ? textStr.join("\n") : String(textStr);
|
|
3102
|
+
if (!fontDef.cjk && !fontDef.fontFile && hasCJK(raw)) {
|
|
3103
|
+
fontKey = "simsun";
|
|
3104
|
+
fontDef = BUILTIN_FONTS.simsun;
|
|
3105
|
+
}
|
|
3106
|
+
const renderingMode = String(options.renderingMode || "fill");
|
|
3107
|
+
const opacity = typeof options.opacity === "number" ? Math.max(0, Math.min(1, options.opacity)) : this.opacity;
|
|
3108
|
+
const hScale = typeof options.horizontalScale === "number" && options.horizontalScale > 0 ? options.horizontalScale : 1;
|
|
3109
|
+
const r2l = typeof options.R2L === "boolean" ? options.R2L : this.r2l;
|
|
3110
|
+
let lines;
|
|
3111
|
+
if (typeof options.maxWidth === "number" && isFinite(options.maxWidth) && options.maxWidth > 0) {
|
|
3112
|
+
lines = this._splitCore(raw, this._u(options.maxWidth) * hScale, fontDef, style);
|
|
3113
|
+
} else {
|
|
3114
|
+
lines = Array.isArray(textStr) ? textStr.slice() : raw.split(/\r\n|\r|\n/);
|
|
3115
|
+
}
|
|
3116
|
+
if (r2l) {
|
|
3117
|
+
lines = lines.map((l) => Array.from(l).reverse().join(""));
|
|
3118
|
+
}
|
|
3119
|
+
const ascentPt = fontDef.ascent / 1e3 * size;
|
|
3120
|
+
const descentPt = -fontDef.descent / 1e3 * size;
|
|
3121
|
+
const leading = size * lineHeightFactor;
|
|
3122
|
+
const justify = align === "justify";
|
|
3123
|
+
const alignEff = justify ? "left" : align;
|
|
3124
|
+
const rad = angle * Math.PI / 180;
|
|
3125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3126
|
+
const lineText = lines[i] === void 0 || lines[i] === null ? "" : String(lines[i]);
|
|
3127
|
+
if (lineText.length === 0 && lines.length > 1) continue;
|
|
3128
|
+
const w = this.measure(lineText, fontDef, style, size, charSpace) * hScale;
|
|
3129
|
+
let lineX = x;
|
|
3130
|
+
if (alignEff === "center") lineX = x - w / 2;
|
|
3131
|
+
else if (alignEff === "right") lineX = x - w;
|
|
3132
|
+
let baseY = y;
|
|
3133
|
+
if (baseline === "top") baseY = y + ascentPt;
|
|
3134
|
+
else if (baseline === "middle") baseY = y + (ascentPt - descentPt) / 2;
|
|
3135
|
+
else if (baseline === "bottom") baseY = y + descentPt;
|
|
3136
|
+
else if (baseline === "hanging") baseY = y + ascentPt * 0.8;
|
|
3137
|
+
baseY += i * leading * Math.cos(rad);
|
|
3138
|
+
lineX += -i * leading * Math.sin(rad);
|
|
3139
|
+
this.pages[this.page].objects.push({
|
|
3140
|
+
t: "text",
|
|
3141
|
+
x: lineX,
|
|
3142
|
+
y: baseY,
|
|
3143
|
+
text: lineText,
|
|
3144
|
+
size,
|
|
3145
|
+
fontKey,
|
|
3146
|
+
style,
|
|
3147
|
+
color: this.textColor.slice(),
|
|
3148
|
+
charSpace,
|
|
3149
|
+
angle,
|
|
3150
|
+
ascent: ascentPt,
|
|
3151
|
+
descent: descentPt,
|
|
3152
|
+
renderingMode,
|
|
3153
|
+
opacity,
|
|
3154
|
+
justifyWidth: justify && i < lines.length - 1 ? this._u(Number(options.maxWidth)) * hScale : null,
|
|
3155
|
+
hScale
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
return this;
|
|
3159
|
+
},
|
|
3160
|
+
/**
|
|
3161
|
+
* Write text wrapped in a clickable link annotation.
|
|
3162
|
+
*
|
|
3163
|
+
* Pass `url` for an external target or `pageNumber` for an in-document jump.
|
|
3164
|
+
*/
|
|
3165
|
+
textWithLink(text, x, y, options = {}) {
|
|
3166
|
+
const sizePt = typeof options.fontSize === "number" ? options.fontSize : this.fontSize;
|
|
3167
|
+
this.text(text, x, y, options);
|
|
3168
|
+
const width = this.getTextWidth(String(text));
|
|
3169
|
+
const sizeU = sizePt / this.scaleFactor;
|
|
3170
|
+
this.link(x, y - sizeU * 0.85, width, sizeU, options);
|
|
3171
|
+
return this;
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
|
|
3175
|
+
// src/xml.ts
|
|
3176
|
+
var OFD_NAMESPACE = "http://www.ofdspec.org/2016";
|
|
3177
|
+
function xmlHeader() {
|
|
3178
|
+
return '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
3179
|
+
}
|
|
3180
|
+
function colorValue(rgb) {
|
|
3181
|
+
return `${rgb[0]} ${rgb[1]} ${rgb[2]}`;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/builder.ts
|
|
3185
|
+
var CAP_NAMES = ["Butt", "Round", "Square"];
|
|
3186
|
+
var JOIN_NAMES = ["Miter", "Round", "Bevel"];
|
|
3187
|
+
function buildOFDPackage(ctx) {
|
|
3188
|
+
var _a, _b;
|
|
3189
|
+
const files = [];
|
|
3190
|
+
const xmlFiles = {};
|
|
3191
|
+
const fontsUsed = {};
|
|
3192
|
+
for (const page of ctx.pages) {
|
|
3193
|
+
for (const obj of page.objects) {
|
|
3194
|
+
if (obj.t === "text") {
|
|
3195
|
+
const k = `${obj.fontKey}#${obj.style}`;
|
|
3196
|
+
if (!fontsUsed[k]) {
|
|
3197
|
+
fontsUsed[k] = {
|
|
3198
|
+
key: obj.fontKey,
|
|
3199
|
+
style: obj.style,
|
|
3200
|
+
def: ctx.getFontDef(obj.fontKey),
|
|
3201
|
+
id: 0
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
let nextId = 1;
|
|
3208
|
+
const colorSpaceId = nextId++;
|
|
3209
|
+
const embeddedFiles = /* @__PURE__ */ new Map();
|
|
3210
|
+
let embeddedIdx = 0;
|
|
3211
|
+
const hashOf = (data) => `${data.length}:${fnv1a(data)}`;
|
|
3212
|
+
for (const k of Object.keys(fontsUsed)) {
|
|
3213
|
+
fontsUsed[k].id = nextId++;
|
|
3214
|
+
const file = fontsUsed[k].def.fontFile;
|
|
3215
|
+
if (file) {
|
|
3216
|
+
const hash = hashOf(file.data);
|
|
3217
|
+
let entry = embeddedFiles.get(hash);
|
|
3218
|
+
if (!entry) {
|
|
3219
|
+
entry = { name: `Font_${embeddedIdx++}.${file.ext}`, data: file.data };
|
|
3220
|
+
embeddedFiles.set(hash, entry);
|
|
3221
|
+
}
|
|
3222
|
+
fontsUsed[k].file = entry;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
for (const img of ctx.imageList) {
|
|
3226
|
+
img.resId = nextId++;
|
|
3227
|
+
}
|
|
3228
|
+
const pageIds = ctx.pages.map(() => nextId++);
|
|
3229
|
+
{
|
|
3230
|
+
let pub = xmlHeader();
|
|
3231
|
+
pub += `<ofd:Res xmlns:ofd="${OFD_NAMESPACE}" BaseLoc="Res">
|
|
3232
|
+
`;
|
|
3233
|
+
pub += ` <ofd:ColorSpaces>
|
|
3234
|
+
`;
|
|
3235
|
+
pub += ` <ofd:ColorSpace ID="${colorSpaceId}" Type="RGB" BitsPerComponent="8"/>
|
|
3236
|
+
`;
|
|
3237
|
+
pub += ` </ofd:ColorSpaces>
|
|
3238
|
+
`;
|
|
3239
|
+
if (Object.keys(fontsUsed).length) {
|
|
3240
|
+
pub += ` <ofd:Fonts>
|
|
3241
|
+
`;
|
|
3242
|
+
for (const fu of Object.values(fontsUsed)) {
|
|
3243
|
+
if (fu.file) {
|
|
3244
|
+
pub += ` <ofd:Font ID="${fu.id}" FontName="${escapeXmlAttr(fu.def.familyName)}">
|
|
3245
|
+
`;
|
|
3246
|
+
if (fu.style === "bold" || fu.style === "bolditalic") pub += ` Bold="true"`;
|
|
3247
|
+
if (fu.style === "italic" || fu.style === "bolditalic") pub += ` Italic="true"`;
|
|
3248
|
+
pub += ` <ofd:FontFile>${fu.file.name}</ofd:FontFile>
|
|
3249
|
+
`;
|
|
3250
|
+
pub += ` </ofd:Font>
|
|
3251
|
+
`;
|
|
3252
|
+
} else {
|
|
3253
|
+
pub += ` <ofd:Font ID="${fu.id}" FontName="${escapeXmlAttr(fu.def.familyName)}" FamilyName="${escapeXmlAttr(fu.def.displayName)}"`;
|
|
3254
|
+
pub += ` Serif="${fu.def.serif ? "true" : "false"}"`;
|
|
3255
|
+
if (fu.def.fixed) pub += ` FixedWidth="true"`;
|
|
3256
|
+
pub += ` Charset="unicode"`;
|
|
3257
|
+
if (fu.style === "bold" || fu.style === "bolditalic") pub += ` Bold="true"`;
|
|
3258
|
+
if (fu.style === "italic" || fu.style === "bolditalic") pub += ` Italic="true"`;
|
|
3259
|
+
pub += `/>
|
|
3260
|
+
`;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
pub += ` </ofd:Fonts>
|
|
3264
|
+
`;
|
|
3265
|
+
}
|
|
3266
|
+
pub += `</ofd:Res>
|
|
3267
|
+
`;
|
|
3268
|
+
xmlFiles["Doc_0/PublicRes.xml"] = pub;
|
|
3269
|
+
}
|
|
3270
|
+
if (ctx.imageList.length) {
|
|
3271
|
+
let dres = xmlHeader();
|
|
3272
|
+
dres += `<ofd:Res xmlns:ofd="${OFD_NAMESPACE}" BaseLoc="Res">
|
|
3273
|
+
`;
|
|
3274
|
+
dres += ` <ofd:MultiMedias>
|
|
3275
|
+
`;
|
|
3276
|
+
ctx.imageList.forEach((img, idx) => {
|
|
3277
|
+
dres += ` <ofd:MultiMedia ID="${img.resId}" Type="Image" Format="${img.format}">
|
|
3278
|
+
`;
|
|
3279
|
+
dres += ` <ofd:MediaFile>Image_${idx}.${img.ext}</ofd:MediaFile>
|
|
3280
|
+
`;
|
|
3281
|
+
dres += ` </ofd:MultiMedia>
|
|
3282
|
+
`;
|
|
3283
|
+
});
|
|
3284
|
+
dres += ` </ofd:MultiMedias>
|
|
3285
|
+
`;
|
|
3286
|
+
dres += `</ofd:Res>
|
|
3287
|
+
`;
|
|
3288
|
+
xmlFiles["Doc_0/DocumentRes.xml"] = dres;
|
|
3289
|
+
}
|
|
3290
|
+
let maxUnitId = nextId - 1;
|
|
3291
|
+
ctx.pages.forEach((page, idx) => {
|
|
3292
|
+
const layerId = nextId++;
|
|
3293
|
+
maxUnitId = Math.max(maxUnitId, layerId);
|
|
3294
|
+
let content = xmlHeader();
|
|
3295
|
+
content += `<ofd:Page xmlns:ofd="${OFD_NAMESPACE}">
|
|
3296
|
+
`;
|
|
3297
|
+
if (page.width !== ctx.pages[0].width || page.height !== ctx.pages[0].height) {
|
|
3298
|
+
content += ` <ofd:Area>
|
|
3299
|
+
`;
|
|
3300
|
+
content += ` <ofd:PhysicalBox>0 0 ${fmt(page.width * MM_PER_PT)} ${fmt(page.height * MM_PER_PT)}</ofd:PhysicalBox>
|
|
3301
|
+
`;
|
|
3302
|
+
content += ` </ofd:Area>
|
|
3303
|
+
`;
|
|
3304
|
+
}
|
|
3305
|
+
content += ` <ofd:Content>
|
|
3306
|
+
`;
|
|
3307
|
+
content += ` <ofd:Layer ID="${layerId}">
|
|
3308
|
+
`;
|
|
3309
|
+
for (const obj of page.objects) {
|
|
3310
|
+
content += objectXml(ctx, fontsUsed, obj, colorSpaceId, pageIds, () => nextId++);
|
|
3311
|
+
}
|
|
3312
|
+
content += ` </ofd:Layer>
|
|
3313
|
+
`;
|
|
3314
|
+
content += ` </ofd:Content>
|
|
3315
|
+
`;
|
|
3316
|
+
content += `</ofd:Page>
|
|
3317
|
+
`;
|
|
3318
|
+
xmlFiles[`Doc_0/Pages/Page_${idx}/Content.xml`] = content;
|
|
3319
|
+
});
|
|
3320
|
+
maxUnitId = Math.max(maxUnitId, nextId - 1);
|
|
3321
|
+
{
|
|
3322
|
+
const w0 = fmt(ctx.pages[0].width * MM_PER_PT);
|
|
3323
|
+
const h0 = fmt(ctx.pages[0].height * MM_PER_PT);
|
|
3324
|
+
let doc = xmlHeader();
|
|
3325
|
+
doc += `<ofd:Document xmlns:ofd="${OFD_NAMESPACE}">
|
|
3326
|
+
`;
|
|
3327
|
+
doc += ` <ofd:CommonData>
|
|
3328
|
+
`;
|
|
3329
|
+
doc += ` <ofd:MaxUnitID>${maxUnitId}</ofd:MaxUnitID>
|
|
3330
|
+
`;
|
|
3331
|
+
doc += ` <ofd:PageArea>
|
|
3332
|
+
`;
|
|
3333
|
+
doc += ` <ofd:PhysicalBox>0 0 ${w0} ${h0}</ofd:PhysicalBox>
|
|
3334
|
+
`;
|
|
3335
|
+
doc += ` <ofd:ApplicationBox>0 0 ${w0} ${h0}</ofd:ApplicationBox>
|
|
3336
|
+
`;
|
|
3337
|
+
doc += ` </ofd:PageArea>
|
|
3338
|
+
`;
|
|
3339
|
+
doc += ` <ofd:PublicRes>PublicRes.xml</ofd:PublicRes>
|
|
3340
|
+
`;
|
|
3341
|
+
if (ctx.imageList.length) {
|
|
3342
|
+
doc += ` <ofd:DocumentRes>DocumentRes.xml</ofd:DocumentRes>
|
|
3343
|
+
`;
|
|
3344
|
+
}
|
|
3345
|
+
doc += ` </ofd:CommonData>
|
|
3346
|
+
`;
|
|
3347
|
+
doc += ` <ofd:Pages>
|
|
3348
|
+
`;
|
|
3349
|
+
ctx.pages.forEach((_page, idx) => {
|
|
3350
|
+
doc += ` <ofd:Page ID="${pageIds[idx]}" BaseLoc="Pages/Page_${idx}/Content.xml"/>
|
|
3351
|
+
`;
|
|
3352
|
+
});
|
|
3353
|
+
doc += ` </ofd:Pages>
|
|
3354
|
+
`;
|
|
3355
|
+
if (ctx.outlineRoot.children.length) {
|
|
3356
|
+
doc += ` <ofd:Outlines>
|
|
3357
|
+
`;
|
|
3358
|
+
doc += outlineXml(ctx.outlineRoot.children, pageIds, " ", () => nextId++);
|
|
3359
|
+
doc += ` </ofd:Outlines>
|
|
3360
|
+
`;
|
|
3361
|
+
maxUnitId = Math.max(maxUnitId, nextId - 1);
|
|
3362
|
+
}
|
|
3363
|
+
if (ctx.displayMode || Object.keys(ctx.viewerPrefs || {}).length) {
|
|
3364
|
+
doc += ` <ofd:VPreferences>
|
|
3365
|
+
`;
|
|
3366
|
+
const pm = (_a = ctx.displayMode) == null ? void 0 : _a.pageMode;
|
|
3367
|
+
const pageMode = pm ? pm === "UseNone" ? "None" : pm : ctx.outlineRoot.children.length ? "UseOutlines" : "None";
|
|
3368
|
+
doc += ` <ofd:PageMode>${pageMode}</ofd:PageMode>
|
|
3369
|
+
`;
|
|
3370
|
+
const layoutMap = {
|
|
3371
|
+
continuous: "OneColumn",
|
|
3372
|
+
single: "OnePage",
|
|
3373
|
+
twoleft: "TwoPageL",
|
|
3374
|
+
tworight: "TwoPageR",
|
|
3375
|
+
two: "TwoPageL"
|
|
3376
|
+
};
|
|
3377
|
+
if (ctx.displayMode) {
|
|
3378
|
+
doc += ` <ofd:PageLayout>${layoutMap[ctx.displayMode.layout] || "OneColumn"}</ofd:PageLayout>
|
|
3379
|
+
`;
|
|
3380
|
+
const zoomMap = {
|
|
3381
|
+
fullheight: "FitHeight",
|
|
3382
|
+
fullwidth: "FitWidth",
|
|
3383
|
+
fullpage: "FitRect",
|
|
3384
|
+
original: "Default"
|
|
3385
|
+
};
|
|
3386
|
+
const z = ctx.displayMode.zoom;
|
|
3387
|
+
if (typeof z === "number") {
|
|
3388
|
+
doc += ` <ofd:Zoom>${fmt(z)}</ofd:Zoom>
|
|
3389
|
+
`;
|
|
3390
|
+
} else if (zoomMap[z]) {
|
|
3391
|
+
doc += ` <ofd:ZoomMode>${zoomMap[z]}</ofd:ZoomMode>
|
|
3392
|
+
`;
|
|
3393
|
+
} else if (typeof z === "string" && /^\d+(\.\d+)?%$/.test(z)) {
|
|
3394
|
+
doc += ` <ofd:Zoom>${fmt(parseFloat(z) / 100)}</ofd:Zoom>
|
|
3395
|
+
`;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
for (const k of ["HideToolbar", "HideMenubar", "HideWindowUI", "FitWindow"]) {
|
|
3399
|
+
if ((_b = ctx.viewerPrefs) == null ? void 0 : _b[k]) doc += ` <ofd:${k}>true</ofd:${k}>
|
|
3400
|
+
`;
|
|
3401
|
+
}
|
|
3402
|
+
doc += ` </ofd:VPreferences>
|
|
3403
|
+
`;
|
|
3404
|
+
}
|
|
3405
|
+
if (ctx.attachments.length) {
|
|
3406
|
+
doc += ` <ofd:Attachments>Attachments.xml</ofd:Attachments>
|
|
3407
|
+
`;
|
|
3408
|
+
}
|
|
3409
|
+
doc += `</ofd:Document>
|
|
3410
|
+
`;
|
|
3411
|
+
xmlFiles["Doc_0/Document.xml"] = doc;
|
|
3412
|
+
}
|
|
3413
|
+
{
|
|
3414
|
+
const p = ctx.properties;
|
|
3415
|
+
let ofd = xmlHeader();
|
|
3416
|
+
ofd += `<ofd:OFD xmlns:ofd="${OFD_NAMESPACE}" Version="1.0" DocType="OFD">
|
|
3417
|
+
`;
|
|
3418
|
+
ofd += ` <ofd:DocBody>
|
|
3419
|
+
`;
|
|
3420
|
+
ofd += ` <ofd:DocInfo>
|
|
3421
|
+
`;
|
|
3422
|
+
ofd += ` <ofd:DocID>${ctx.docID}</ofd:DocID>
|
|
3423
|
+
`;
|
|
3424
|
+
if (p.title) ofd += ` <ofd:Title>${escapeXmlText(p.title)}</ofd:Title>
|
|
3425
|
+
`;
|
|
3426
|
+
if (p.author) ofd += ` <ofd:Author>${escapeXmlText(p.author)}</ofd:Author>
|
|
3427
|
+
`;
|
|
3428
|
+
if (p.subject) ofd += ` <ofd:Subject>${escapeXmlText(p.subject)}</ofd:Subject>
|
|
3429
|
+
`;
|
|
3430
|
+
if (p.keywords) ofd += ` <ofd:Keywords>${escapeXmlText(p.keywords)}</ofd:Keywords>
|
|
3431
|
+
`;
|
|
3432
|
+
ofd += ` <ofd:Creator>${escapeXmlText(p.creator)}</ofd:Creator>
|
|
3433
|
+
`;
|
|
3434
|
+
if (p.creatorVersion)
|
|
3435
|
+
ofd += ` <ofd:CreatorVersion>${escapeXmlText(p.creatorVersion)}</ofd:CreatorVersion>
|
|
3436
|
+
`;
|
|
3437
|
+
ofd += ` <ofd:CreationDate>${todayStr(ctx.creationDate)}</ofd:CreationDate>
|
|
3438
|
+
`;
|
|
3439
|
+
ofd += ` <ofd:ModDate>${todayStr(ctx.modDate || ctx.creationDate)}</ofd:ModDate>
|
|
3440
|
+
`;
|
|
3441
|
+
ofd += ` </ofd:DocInfo>
|
|
3442
|
+
`;
|
|
3443
|
+
ofd += ` <ofd:DocRoot>Doc_0/Document.xml</ofd:DocRoot>
|
|
3444
|
+
`;
|
|
3445
|
+
ofd += ` </ofd:DocBody>
|
|
3446
|
+
`;
|
|
3447
|
+
ofd += `</ofd:OFD>
|
|
3448
|
+
`;
|
|
3449
|
+
xmlFiles["OFD.xml"] = ofd;
|
|
3450
|
+
}
|
|
3451
|
+
if (ctx.attachments.length) {
|
|
3452
|
+
let att = xmlHeader();
|
|
3453
|
+
att += `<ofd:Attachments xmlns:ofd="${OFD_NAMESPACE}">
|
|
3454
|
+
`;
|
|
3455
|
+
ctx.attachments.forEach((a, i) => {
|
|
3456
|
+
att += ` <ofd:Attachment ID="${i + 1}" Name="${escapeXmlAttr(a.name)}"`;
|
|
3457
|
+
if (a.format) att += ` Format="${escapeXmlAttr(a.format)}"`;
|
|
3458
|
+
att += ` CreationDate="${todayStr(a.creationDate)}" Size="${(a.data.length / 1024).toFixed(6)}">
|
|
3459
|
+
`;
|
|
3460
|
+
att += ` <ofd:FileLoc>/Doc_0/Attachments/Attach_${i}/${escapeXmlText(a.name)}</ofd:FileLoc>
|
|
3461
|
+
`;
|
|
3462
|
+
att += ` </ofd:Attachment>
|
|
3463
|
+
`;
|
|
3464
|
+
files.push({
|
|
3465
|
+
name: `Doc_0/Attachments/Attach_${i}/${a.name}`,
|
|
3466
|
+
data: a.data
|
|
3467
|
+
});
|
|
3468
|
+
});
|
|
3469
|
+
att += `</ofd:Attachments>
|
|
3470
|
+
`;
|
|
3471
|
+
xmlFiles["Doc_0/Attachments.xml"] = att;
|
|
3472
|
+
}
|
|
3473
|
+
ctx.imageList.forEach((img, idx) => {
|
|
3474
|
+
files.push({ name: `Doc_0/Res/Image_${idx}.${img.ext}`, data: img.data });
|
|
3475
|
+
});
|
|
3476
|
+
for (const entry of embeddedFiles.values()) {
|
|
3477
|
+
files.push({ name: `Doc_0/Res/${entry.name}`, data: entry.data });
|
|
3478
|
+
}
|
|
3479
|
+
const preferredOrder = [
|
|
3480
|
+
"OFD.xml",
|
|
3481
|
+
"Doc_0/Document.xml",
|
|
3482
|
+
"Doc_0/PublicRes.xml",
|
|
3483
|
+
"Doc_0/DocumentRes.xml",
|
|
3484
|
+
"Doc_0/Attachments.xml"
|
|
3485
|
+
];
|
|
3486
|
+
const names = Object.keys(xmlFiles);
|
|
3487
|
+
names.sort((a, b) => {
|
|
3488
|
+
const ia = preferredOrder.indexOf(a);
|
|
3489
|
+
const ib = preferredOrder.indexOf(b);
|
|
3490
|
+
if (ia !== -1 && ib !== -1) return ia - ib;
|
|
3491
|
+
if (ia !== -1) return -1;
|
|
3492
|
+
if (ib !== -1) return 1;
|
|
3493
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
3494
|
+
});
|
|
3495
|
+
const zipFiles = [];
|
|
3496
|
+
for (const name of names) {
|
|
3497
|
+
zipFiles.push({ name, data: utf8Encode(xmlFiles[name]) });
|
|
3498
|
+
}
|
|
3499
|
+
for (const f of files) zipFiles.push(f);
|
|
3500
|
+
return zipFiles;
|
|
3501
|
+
}
|
|
3502
|
+
function colorEl(name, rgb, csId) {
|
|
3503
|
+
return ` <ofd:${name} Value="${colorValue(rgb)}" ColorSpace="${csId}"/>
|
|
3504
|
+
`;
|
|
3505
|
+
}
|
|
3506
|
+
function objectXml(ctx, fontsUsed, obj, colorSpaceId, pageIds, allocId) {
|
|
3507
|
+
switch (obj.t) {
|
|
3508
|
+
case "text":
|
|
3509
|
+
return textObjectXml(ctx, fontsUsed, obj, colorSpaceId, allocId);
|
|
3510
|
+
case "path":
|
|
3511
|
+
return pathObjectXml(obj, colorSpaceId, allocId);
|
|
3512
|
+
case "image":
|
|
3513
|
+
return imageObjectXml(obj, colorSpaceId, allocId);
|
|
3514
|
+
case "link":
|
|
3515
|
+
return linkObjectXml(obj, colorSpaceId, allocId, pageIds);
|
|
3516
|
+
}
|
|
3517
|
+
return "";
|
|
3518
|
+
}
|
|
3519
|
+
function textObjectXml(ctx, fontsUsed, obj, colorSpaceId, allocId) {
|
|
3520
|
+
const M = MM_PER_PT;
|
|
3521
|
+
const id = allocId();
|
|
3522
|
+
const fu = fontsUsed[`${obj.fontKey}#${obj.style}`];
|
|
3523
|
+
if (!fu) return "";
|
|
3524
|
+
const { x, y } = obj;
|
|
3525
|
+
const hS = obj.hScale && obj.hScale !== 1 ? obj.hScale : 1;
|
|
3526
|
+
const naturalWidth = obj.glyphWs ? obj.glyphWs.reduce((s, w) => s + w, 0) + obj.charSpace * Math.max(obj.text.length - 1, 0) : ctx.measure(obj.text, fu.def, obj.style, obj.size, obj.charSpace);
|
|
3527
|
+
const wPt = Math.max(
|
|
3528
|
+
naturalWidth,
|
|
3529
|
+
ctx.measure(obj.text, fu.def, obj.style, obj.size, obj.charSpace)
|
|
3530
|
+
);
|
|
3531
|
+
const wEff = wPt * hS;
|
|
3532
|
+
const asc = fu.def.ascent / 1e3 * obj.size;
|
|
3533
|
+
const desc = -fu.def.descent / 1e3 * obj.size;
|
|
3534
|
+
let bx, by, bw, bh;
|
|
3535
|
+
let ctm = null;
|
|
3536
|
+
let textCodeX = 0;
|
|
3537
|
+
let textCodeY;
|
|
3538
|
+
if (obj.angle || hS !== 1) {
|
|
3539
|
+
const rad = obj.angle * Math.PI / 180;
|
|
3540
|
+
const cos = Math.cos(rad);
|
|
3541
|
+
const sin = Math.sin(rad);
|
|
3542
|
+
const corners = [
|
|
3543
|
+
[0, -asc],
|
|
3544
|
+
[wEff, -asc],
|
|
3545
|
+
[wEff, desc],
|
|
3546
|
+
[0, desc]
|
|
3547
|
+
];
|
|
3548
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3549
|
+
for (const [px0, py0] of corners) {
|
|
3550
|
+
const px = x + hS * (px0 * cos) - py0 * sin;
|
|
3551
|
+
const py = y + hS * (px0 * sin) + py0 * cos;
|
|
3552
|
+
minX = Math.min(minX, px);
|
|
3553
|
+
minY = Math.min(minY, py);
|
|
3554
|
+
maxX = Math.max(maxX, px);
|
|
3555
|
+
maxY = Math.max(maxY, py);
|
|
3556
|
+
}
|
|
3557
|
+
const pad = (asc + desc) * 0.18;
|
|
3558
|
+
minX -= pad;
|
|
3559
|
+
maxX += pad;
|
|
3560
|
+
minY -= pad;
|
|
3561
|
+
maxY += pad;
|
|
3562
|
+
bx = minX;
|
|
3563
|
+
by = minY;
|
|
3564
|
+
bw = Math.max(maxX - minX, 0.01);
|
|
3565
|
+
bh = Math.max(maxY - minY, 0.01);
|
|
3566
|
+
ctm = [hS * cos, hS * sin, -sin, cos, (x - bx) * M, (y - by) * M];
|
|
3567
|
+
textCodeX = 0;
|
|
3568
|
+
textCodeY = 0;
|
|
3569
|
+
} else {
|
|
3570
|
+
bx = x;
|
|
3571
|
+
const ascPadded = asc * 1.08;
|
|
3572
|
+
by = y - ascPadded;
|
|
3573
|
+
bw = Math.max(wPt, 0.01);
|
|
3574
|
+
bh = Math.max(ascPadded + desc * 2.2, obj.size * 1.4);
|
|
3575
|
+
textCodeY = ascPadded;
|
|
3576
|
+
}
|
|
3577
|
+
let xml = ` <ofd:TextObject ID="${id}"`;
|
|
3578
|
+
xml += ` Boundary="${fmt(bx * M)} ${fmt(by * M)} ${fmt(bw * M)} ${fmt(bh * M)}"`;
|
|
3579
|
+
xml += ` Font="${fu.id}"`;
|
|
3580
|
+
xml += ` Size="${fmt(obj.size * M)}"`;
|
|
3581
|
+
if (ctm) xml += ` CTM="${ctm.map((v) => fmt(v)).join(" ")}"`;
|
|
3582
|
+
if (obj.style === "bold" || obj.style === "bolditalic") xml += ` Weight="700"`;
|
|
3583
|
+
if (obj.style === "italic" || obj.style === "bolditalic") xml += ` Italic="true"`;
|
|
3584
|
+
if (obj.renderingMode === "stroke") xml += ` Fill="false" Stroke="true"`;
|
|
3585
|
+
else if (obj.renderingMode === "fillThenStroke") xml += ` Fill="true" Stroke="true"`;
|
|
3586
|
+
else if (obj.renderingMode === "invisible") xml += ` Visible="false"`;
|
|
3587
|
+
else xml += ` Fill="true" Stroke="false"`;
|
|
3588
|
+
if (obj.opacity !== null && obj.opacity < 1) {
|
|
3589
|
+
xml += ` Alpha="${Math.round(obj.opacity * 255)}"`;
|
|
3590
|
+
}
|
|
3591
|
+
xml += `>
|
|
3592
|
+
`;
|
|
3593
|
+
if (obj.renderingMode === "stroke" || obj.renderingMode === "fillThenStroke") {
|
|
3594
|
+
xml += colorEl("StrokeColor", obj.color, colorSpaceId);
|
|
3595
|
+
}
|
|
3596
|
+
xml += colorEl("FillColor", obj.color, colorSpaceId);
|
|
3597
|
+
let deltaAttr = "";
|
|
3598
|
+
if (obj.text.length > 1) {
|
|
3599
|
+
const deltas = [];
|
|
3600
|
+
let extra = 0;
|
|
3601
|
+
if (obj.justifyWidth !== null) {
|
|
3602
|
+
const natural = obj.glyphWs ? obj.glyphWs.reduce((s, w) => s + w, 0) + obj.charSpace * Math.max(obj.text.length - 1, 0) : ctx.measure(obj.text, fu.def, obj.style, obj.size, obj.charSpace);
|
|
3603
|
+
extra = (obj.justifyWidth - natural) / (obj.text.length - 1);
|
|
3604
|
+
}
|
|
3605
|
+
for (let i = 0; i < obj.text.length - 1; i++) {
|
|
3606
|
+
const adv = obj.glyphWs ? obj.glyphWs[i] + obj.charSpace + extra : glyphWidth(fu.def, obj.style, obj.text.charAt(i)) / 1e3 * obj.size + obj.charSpace + extra;
|
|
3607
|
+
deltas.push(fmt(adv * M));
|
|
3608
|
+
}
|
|
3609
|
+
deltaAttr = ` DeltaX="${deltas.join(" ")}"`;
|
|
3610
|
+
}
|
|
3611
|
+
xml += ` <ofd:TextCode X="${fmt(textCodeX * M)}" Y="${fmt(textCodeY * M)}"${deltaAttr}>`;
|
|
3612
|
+
xml += `${escapeXmlText(obj.text)}</ofd:TextCode>
|
|
3613
|
+
`;
|
|
3614
|
+
xml += ` </ofd:TextObject>
|
|
3615
|
+
`;
|
|
3616
|
+
return xml;
|
|
3617
|
+
}
|
|
3618
|
+
function pathBoundary(ops, stroke, lineWidth) {
|
|
3619
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3620
|
+
const pad = stroke ? lineWidth / 2 : 0;
|
|
3621
|
+
const consider = (x, y) => {
|
|
3622
|
+
minX = Math.min(minX, x - pad);
|
|
3623
|
+
minY = Math.min(minY, y - pad);
|
|
3624
|
+
maxX = Math.max(maxX, x + pad);
|
|
3625
|
+
maxY = Math.max(maxY, y + pad);
|
|
3626
|
+
};
|
|
3627
|
+
for (const op of ops) {
|
|
3628
|
+
if (op.op === "M" || op.op === "L") consider(op.x, op.y);
|
|
3629
|
+
else if (op.op === "C") {
|
|
3630
|
+
consider(op.x1, op.y1);
|
|
3631
|
+
consider(op.x2, op.y2);
|
|
3632
|
+
consider(op.x, op.y);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return { bx: minX, by: minY, bw: maxX - minX, bh: maxY - minY };
|
|
3636
|
+
}
|
|
3637
|
+
function pathObjectXml(obj, colorSpaceId, allocId) {
|
|
3638
|
+
const M = MM_PER_PT;
|
|
3639
|
+
const id = allocId();
|
|
3640
|
+
const { bx, by, bw, bh } = pathBoundary(obj.ops, obj.stroke, obj.lineWidth);
|
|
3641
|
+
if (!isFinite(bx)) return "";
|
|
3642
|
+
let xml = ` <ofd:PathObject ID="${id}"`;
|
|
3643
|
+
xml += ` Boundary="${fmt(bx * M)} ${fmt(by * M)} ${fmt(Math.max(bw, 0.01) * M)} ${fmt(Math.max(bh, 0.01) * M)}"`;
|
|
3644
|
+
xml += ` Fill="${obj.fill ? "true" : "false"}"`;
|
|
3645
|
+
xml += ` Stroke="${obj.stroke ? "true" : "false"}"`;
|
|
3646
|
+
if (obj.stroke) {
|
|
3647
|
+
xml += ` LineWidth="${fmt(obj.lineWidth * M)}"`;
|
|
3648
|
+
if (obj.cap) xml += ` Cap="${CAP_NAMES[obj.cap] || "Butt"}"`;
|
|
3649
|
+
if (obj.join) xml += ` Join="${JOIN_NAMES[obj.join] || "Miter"}"`;
|
|
3650
|
+
if (obj.miterLimit > 1) xml += ` MiterLimit="${fmt(obj.miterLimit * M)}"`;
|
|
3651
|
+
if (obj.dash && obj.dash.pattern.length) {
|
|
3652
|
+
xml += ` DashPattern="${obj.dash.pattern.map((v) => fmt(v * M)).join(" ")}"`;
|
|
3653
|
+
if (obj.dash.phase) xml += ` DashOffset="${fmt(obj.dash.phase * M)}"`;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
if (obj.opacity !== null && obj.opacity < 1) {
|
|
3657
|
+
xml += ` Alpha="${Math.round(obj.opacity * 255)}"`;
|
|
3658
|
+
}
|
|
3659
|
+
xml += `>
|
|
3660
|
+
`;
|
|
3661
|
+
if (obj.fill) xml += colorEl("FillColor", obj.fillColor, colorSpaceId);
|
|
3662
|
+
if (obj.stroke) xml += colorEl("StrokeColor", obj.strokeColor, colorSpaceId);
|
|
3663
|
+
const d = [];
|
|
3664
|
+
for (const op of obj.ops) {
|
|
3665
|
+
switch (op.op) {
|
|
3666
|
+
case "M":
|
|
3667
|
+
d.push(`M ${fmt((op.x - bx) * M)} ${fmt((op.y - by) * M)}`);
|
|
3668
|
+
break;
|
|
3669
|
+
case "L":
|
|
3670
|
+
d.push(`L ${fmt((op.x - bx) * M)} ${fmt((op.y - by) * M)}`);
|
|
3671
|
+
break;
|
|
3672
|
+
case "C":
|
|
3673
|
+
d.push(
|
|
3674
|
+
`B ${fmt((op.x1 - bx) * M)} ${fmt((op.y1 - by) * M)} ${fmt((op.x2 - bx) * M)} ${fmt((op.y2 - by) * M)} ${fmt((op.x - bx) * M)} ${fmt((op.y - by) * M)}`
|
|
3675
|
+
);
|
|
3676
|
+
break;
|
|
3677
|
+
case "Z":
|
|
3678
|
+
d.push("C");
|
|
3679
|
+
break;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
xml += ` <ofd:AbbreviatedData>${d.join(" ")} </ofd:AbbreviatedData>
|
|
3683
|
+
`;
|
|
3684
|
+
xml += ` </ofd:PathObject>
|
|
3685
|
+
`;
|
|
3686
|
+
return xml;
|
|
3687
|
+
}
|
|
3688
|
+
function imageObjectXml(obj, _colorSpaceId, allocId) {
|
|
3689
|
+
const M = MM_PER_PT;
|
|
3690
|
+
const id = allocId();
|
|
3691
|
+
const img = obj.imageRef;
|
|
3692
|
+
if (!img) return "";
|
|
3693
|
+
const w = obj.w * M;
|
|
3694
|
+
const h = obj.h * M;
|
|
3695
|
+
const rad = (obj.angle || 0) * Math.PI / 180;
|
|
3696
|
+
const cos = Math.cos(rad);
|
|
3697
|
+
const sin = Math.sin(rad);
|
|
3698
|
+
if (!obj.angle) {
|
|
3699
|
+
const bx2 = obj.x * M;
|
|
3700
|
+
const by2 = obj.y * M;
|
|
3701
|
+
let xml2 = ` <ofd:ImageObject ID="${id}"`;
|
|
3702
|
+
xml2 += ` Boundary="${fmt(bx2)} ${fmt(by2)} ${fmt(Math.max(w, 0.01))} ${fmt(Math.max(h, 0.01))}"`;
|
|
3703
|
+
xml2 += ` ResourceID="${img.resId}"`;
|
|
3704
|
+
xml2 += ` CTM="${fmt(w)} 0 0 ${fmt(h)} 0 0"`;
|
|
3705
|
+
if (obj.opacity !== null && obj.opacity < 1) {
|
|
3706
|
+
xml2 += ` Alpha="${Math.round(obj.opacity * 255)}"`;
|
|
3707
|
+
}
|
|
3708
|
+
xml2 += `/>
|
|
3709
|
+
`;
|
|
3710
|
+
return xml2;
|
|
3711
|
+
}
|
|
3712
|
+
const cx = (obj.x + obj.w / 2) * M;
|
|
3713
|
+
const cy = (obj.y + obj.h / 2) * M;
|
|
3714
|
+
const corners = [
|
|
3715
|
+
[obj.x, obj.y],
|
|
3716
|
+
[obj.x + obj.w, obj.y],
|
|
3717
|
+
[obj.x + obj.w, obj.y + obj.h],
|
|
3718
|
+
[obj.x, obj.y + obj.h]
|
|
3719
|
+
];
|
|
3720
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3721
|
+
for (const [px0, py0] of corners) {
|
|
3722
|
+
const px = cx + (px0 * M - cx) * cos - (py0 * M - cy) * sin;
|
|
3723
|
+
const py = cy + (px0 * M - cx) * sin + (py0 * M - cy) * cos;
|
|
3724
|
+
minX = Math.min(minX, px);
|
|
3725
|
+
minY = Math.min(minY, py);
|
|
3726
|
+
maxX = Math.max(maxX, px);
|
|
3727
|
+
maxY = Math.max(maxY, py);
|
|
3728
|
+
}
|
|
3729
|
+
const bx = minX;
|
|
3730
|
+
const by = minY;
|
|
3731
|
+
const bw = Math.max(maxX - minX, 0.01);
|
|
3732
|
+
const bh = Math.max(maxY - minY, 0.01);
|
|
3733
|
+
const e = cx - cos * w / 2 + sin * h / 2 - bx;
|
|
3734
|
+
const f = cy - sin * w / 2 - cos * h / 2 - by;
|
|
3735
|
+
const ctm = [cos * w, sin * w, -sin * h, cos * h, e, f];
|
|
3736
|
+
let xml = ` <ofd:ImageObject ID="${id}"`;
|
|
3737
|
+
xml += ` Boundary="${fmt(bx)} ${fmt(by)} ${fmt(bw)} ${fmt(bh)}"`;
|
|
3738
|
+
xml += ` ResourceID="${img.resId}"`;
|
|
3739
|
+
xml += ` CTM="${ctm.map((v) => fmt(v)).join(" ")}"`;
|
|
3740
|
+
if (obj.opacity !== null && obj.opacity < 1) {
|
|
3741
|
+
xml += ` Alpha="${Math.round(obj.opacity * 255)}"`;
|
|
3742
|
+
}
|
|
3743
|
+
xml += `/>
|
|
3744
|
+
`;
|
|
3745
|
+
return xml;
|
|
3746
|
+
}
|
|
3747
|
+
function gotoActionXml(pageId, indent) {
|
|
3748
|
+
return `${indent}<ofd:Actions>
|
|
3749
|
+
${indent} <ofd:Action Event="Click">
|
|
3750
|
+
${indent} <ofd:Goto>
|
|
3751
|
+
${indent} <ofd:Dest Type="XYZ" PageID="${pageId}">
|
|
3752
|
+
${indent} <ofd:Left>0</ofd:Left>
|
|
3753
|
+
${indent} <ofd:Top>0</ofd:Top>
|
|
3754
|
+
${indent} <ofd:Zoom>0</ofd:Zoom>
|
|
3755
|
+
${indent} </ofd:Dest>
|
|
3756
|
+
${indent} </ofd:Goto>
|
|
3757
|
+
${indent} </ofd:Action>
|
|
3758
|
+
${indent}</ofd:Actions>
|
|
3759
|
+
`;
|
|
3760
|
+
}
|
|
3761
|
+
function linkObjectXml(obj, colorSpaceId, allocId, pageIds) {
|
|
3762
|
+
let action = "";
|
|
3763
|
+
if (obj.url) {
|
|
3764
|
+
action = ` <ofd:Actions>
|
|
3765
|
+
<ofd:Action Event="Click">
|
|
3766
|
+
<ofd:URI URI="${escapeXmlAttr(obj.url)}"/>
|
|
3767
|
+
</ofd:Action>
|
|
3768
|
+
</ofd:Actions>
|
|
3769
|
+
`;
|
|
3770
|
+
} else if (obj.pageNumber && pageIds[obj.pageNumber - 1]) {
|
|
3771
|
+
action = gotoActionXml(pageIds[obj.pageNumber - 1], " ");
|
|
3772
|
+
}
|
|
3773
|
+
if (!action) return "";
|
|
3774
|
+
const pathObj = {
|
|
3775
|
+
t: "path",
|
|
3776
|
+
miterLimit: 0,
|
|
3777
|
+
ops: [
|
|
3778
|
+
{ op: "M", x: obj.x, y: obj.y },
|
|
3779
|
+
{ op: "L", x: obj.x + obj.w, y: obj.y },
|
|
3780
|
+
{ op: "L", x: obj.x + obj.w, y: obj.y + obj.h },
|
|
3781
|
+
{ op: "L", x: obj.x, y: obj.y + obj.h },
|
|
3782
|
+
{ op: "Z" }
|
|
3783
|
+
],
|
|
3784
|
+
fill: false,
|
|
3785
|
+
stroke: false,
|
|
3786
|
+
fillColor: [0, 0, 0],
|
|
3787
|
+
strokeColor: [0, 0, 0],
|
|
3788
|
+
lineWidth: 0,
|
|
3789
|
+
dash: null,
|
|
3790
|
+
cap: 0,
|
|
3791
|
+
join: 0,
|
|
3792
|
+
opacity: null
|
|
3793
|
+
};
|
|
3794
|
+
const xml = pathObjectXml(pathObj, colorSpaceId, allocId);
|
|
3795
|
+
return xml.replace(" </ofd:PathObject>\n", action + " </ofd:PathObject>\n");
|
|
3796
|
+
}
|
|
3797
|
+
function outlineXml(nodes, pageIds, indent, allocId) {
|
|
3798
|
+
let out = "";
|
|
3799
|
+
for (const n of nodes) {
|
|
3800
|
+
const pageNo = n.options.pageNumber || 1;
|
|
3801
|
+
const pageId = pageIds[pageNo - 1] || pageIds[0];
|
|
3802
|
+
out += `${indent}<ofd:Outline ID="${allocId()}" Title="${escapeXmlAttr(n.title)}">
|
|
3803
|
+
`;
|
|
3804
|
+
out += gotoActionXml(pageId, indent + " ");
|
|
3805
|
+
if (n.children.length) {
|
|
3806
|
+
out += outlineXml(n.children, pageIds, indent + " ", allocId);
|
|
3807
|
+
}
|
|
3808
|
+
out += `${indent}</ofd:Outline>
|
|
3809
|
+
`;
|
|
3810
|
+
}
|
|
3811
|
+
return out;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
// src/zip.ts
|
|
3815
|
+
function dosDateTime(date) {
|
|
3816
|
+
const d = date instanceof Date ? date : /* @__PURE__ */ new Date();
|
|
3817
|
+
const time = (d.getHours() & 31) << 11 | (d.getMinutes() & 63) << 5 | Math.floor(d.getSeconds() / 2) & 31;
|
|
3818
|
+
const day = (d.getFullYear() - 1980 & 127) << 9 | (d.getMonth() + 1 & 15) << 5 | d.getDate() & 31;
|
|
3819
|
+
return { time: time >>> 0, date: day >>> 0 };
|
|
3820
|
+
}
|
|
3821
|
+
var ByteWriter = class {
|
|
3822
|
+
constructor() {
|
|
3823
|
+
__publicField(this, "buf", new Uint8Array(1024));
|
|
3824
|
+
__publicField(this, "len", 0);
|
|
3825
|
+
}
|
|
3826
|
+
ensure(extra) {
|
|
3827
|
+
if (this.len + extra <= this.buf.length) return;
|
|
3828
|
+
let cap = this.buf.length * 2;
|
|
3829
|
+
while (cap < this.len + extra) cap *= 2;
|
|
3830
|
+
const nb = new Uint8Array(cap);
|
|
3831
|
+
nb.set(this.buf.subarray(0, this.len));
|
|
3832
|
+
this.buf = nb;
|
|
3833
|
+
}
|
|
3834
|
+
byte(v) {
|
|
3835
|
+
this.ensure(1);
|
|
3836
|
+
this.buf[this.len++] = v & 255;
|
|
3837
|
+
}
|
|
3838
|
+
u16(v) {
|
|
3839
|
+
this.byte(v);
|
|
3840
|
+
this.byte(v >>> 8);
|
|
3841
|
+
}
|
|
3842
|
+
u32(v) {
|
|
3843
|
+
this.byte(v);
|
|
3844
|
+
this.byte(v >>> 8);
|
|
3845
|
+
this.byte(v >>> 16);
|
|
3846
|
+
this.byte(v >>> 24);
|
|
3847
|
+
}
|
|
3848
|
+
bytes(b) {
|
|
3849
|
+
this.ensure(b.length);
|
|
3850
|
+
this.buf.set(b, this.len);
|
|
3851
|
+
this.len += b.length;
|
|
3852
|
+
}
|
|
3853
|
+
toUint8Array() {
|
|
3854
|
+
return this.buf.slice(0, this.len);
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
function zipStore(files, date) {
|
|
3858
|
+
const dos = dosDateTime(date || /* @__PURE__ */ new Date());
|
|
3859
|
+
const local = new ByteWriter();
|
|
3860
|
+
const central = new ByteWriter();
|
|
3861
|
+
const cdOffsets = [];
|
|
3862
|
+
let offset = 0;
|
|
3863
|
+
for (const f of files) {
|
|
3864
|
+
const nameBytes = utf8Encode(f.name);
|
|
3865
|
+
const data = f.data || new Uint8Array(0);
|
|
3866
|
+
const crc = crc32(data);
|
|
3867
|
+
local.u32(67324752);
|
|
3868
|
+
local.u16(20);
|
|
3869
|
+
local.u16(2048);
|
|
3870
|
+
local.u16(0);
|
|
3871
|
+
local.u16(dos.time);
|
|
3872
|
+
local.u16(dos.date);
|
|
3873
|
+
local.u32(crc);
|
|
3874
|
+
local.u32(data.length);
|
|
3875
|
+
local.u32(data.length);
|
|
3876
|
+
local.u16(nameBytes.length);
|
|
3877
|
+
local.u16(0);
|
|
3878
|
+
local.bytes(nameBytes);
|
|
3879
|
+
local.bytes(data);
|
|
3880
|
+
cdOffsets.push(offset);
|
|
3881
|
+
central.u32(33639248);
|
|
3882
|
+
central.u16(20);
|
|
3883
|
+
central.u16(20);
|
|
3884
|
+
central.u16(2048);
|
|
3885
|
+
central.u16(0);
|
|
3886
|
+
central.u16(dos.time);
|
|
3887
|
+
central.u16(dos.date);
|
|
3888
|
+
central.u32(crc);
|
|
3889
|
+
central.u32(data.length);
|
|
3890
|
+
central.u32(data.length);
|
|
3891
|
+
central.u16(nameBytes.length);
|
|
3892
|
+
central.u16(0);
|
|
3893
|
+
central.u16(0);
|
|
3894
|
+
central.u16(0);
|
|
3895
|
+
central.u16(0);
|
|
3896
|
+
central.u32(0);
|
|
3897
|
+
central.u32(offset);
|
|
3898
|
+
central.bytes(nameBytes);
|
|
3899
|
+
offset += 30 + nameBytes.length + data.length;
|
|
3900
|
+
}
|
|
3901
|
+
const cdStart = offset;
|
|
3902
|
+
const cdSize = central.toUint8Array().length;
|
|
3903
|
+
const eocd = new ByteWriter();
|
|
3904
|
+
eocd.u32(101010256);
|
|
3905
|
+
eocd.u16(0);
|
|
3906
|
+
eocd.u16(0);
|
|
3907
|
+
eocd.u16(files.length);
|
|
3908
|
+
eocd.u16(files.length);
|
|
3909
|
+
eocd.u32(cdSize);
|
|
3910
|
+
eocd.u32(cdStart);
|
|
3911
|
+
eocd.u16(0);
|
|
3912
|
+
const parts = [local.toUint8Array(), central.toUint8Array(), eocd.toUint8Array()];
|
|
3913
|
+
let total = 0;
|
|
3914
|
+
for (const p of parts) total += p.length;
|
|
3915
|
+
const out = new Uint8Array(total);
|
|
3916
|
+
let pos = 0;
|
|
3917
|
+
for (const p of parts) {
|
|
3918
|
+
out.set(p, pos);
|
|
3919
|
+
pos += p.length;
|
|
3920
|
+
}
|
|
3921
|
+
return out;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
// src/modules/view.ts
|
|
3925
|
+
var ViewApi = {
|
|
3926
|
+
/** Set document metadata (title, subject, author, keywords, creator). */
|
|
3927
|
+
setProperties(properties) {
|
|
3928
|
+
for (const k of Object.keys(properties)) {
|
|
3929
|
+
const v = properties[k];
|
|
3930
|
+
if (v !== void 0 && k in this.properties) {
|
|
3931
|
+
this.properties[k] = String(v);
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
return this;
|
|
3935
|
+
},
|
|
3936
|
+
/** Override the document creation date. */
|
|
3937
|
+
setCreationDate(date) {
|
|
3938
|
+
this.creationDate = date instanceof Date ? date : new Date(date);
|
|
3939
|
+
return this;
|
|
3940
|
+
},
|
|
3941
|
+
/**
|
|
3942
|
+
* Read the creation date.
|
|
3943
|
+
*
|
|
3944
|
+
* @param type `'array'` returns `[y, m, d, h, min, s]`; anything else
|
|
3945
|
+
* returns a `Date` (jsPDF `'jsDate'` semantics)
|
|
3946
|
+
*/
|
|
3947
|
+
getCreationDate(type) {
|
|
3948
|
+
if (type === "array") {
|
|
3949
|
+
const d = this.creationDate;
|
|
3950
|
+
return [
|
|
3951
|
+
d.getFullYear(),
|
|
3952
|
+
d.getMonth() + 1,
|
|
3953
|
+
d.getDate(),
|
|
3954
|
+
d.getHours(),
|
|
3955
|
+
d.getMinutes(),
|
|
3956
|
+
d.getSeconds()
|
|
3957
|
+
];
|
|
3958
|
+
}
|
|
3959
|
+
return this.creationDate;
|
|
3960
|
+
},
|
|
3961
|
+
/**
|
|
3962
|
+
* Configure the initial viewer presentation.
|
|
3963
|
+
*
|
|
3964
|
+
* @param zoom numeric multiplier, `'N%'`, or `'fullwidth'` /
|
|
3965
|
+
* `'fullheight'` / `'fullpage'` / `'original'`
|
|
3966
|
+
* @param layout `'continuous'` / `'single'` / `'twoleft'` / `'tworight'` / `'two'`
|
|
3967
|
+
* @param pageMode `'UseNone'` / `'UseOutlines'` / `'UseThumbs'` / `'FullScreen'`
|
|
3968
|
+
* @throws on unrecognized values, mirroring jsPDF
|
|
3969
|
+
*/
|
|
3970
|
+
setDisplayMode(zoom, layout, pageMode) {
|
|
3971
|
+
if (zoom !== void 0 && zoom !== null) {
|
|
3972
|
+
const z = String(zoom).toLowerCase();
|
|
3973
|
+
const num = Number(zoom);
|
|
3974
|
+
const valid = ["fullheight", "fullwidth", "fullpage", "original"].includes(z) || typeof zoom === "number" && !isNaN(num) && num > 0 || typeof zoom === "string" && /^\d+(\.\d+)?%$/.test(zoom);
|
|
3975
|
+
if (!valid) {
|
|
3976
|
+
throw new Error(
|
|
3977
|
+
`zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "${zoom}" is not recognized.`
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
const layouts = ["continuous", "single", "twoleft", "tworight", "two"];
|
|
3982
|
+
if (layout !== void 0 && layout !== null && !layouts.includes(String(layout))) {
|
|
3983
|
+
throw new Error(
|
|
3984
|
+
`Layout mode must be one of ${layouts.join(", ")}. "${layout}" is not recognized.`
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
const pageModes = ["UseNone", "UseOutlines", "UseThumbs", "FullScreen"];
|
|
3988
|
+
if (pageMode !== void 0 && pageMode !== null && !pageModes.includes(String(pageMode))) {
|
|
3989
|
+
throw new Error(
|
|
3990
|
+
`Page mode must be one of ${pageModes.join(", ")}. "${pageMode}" is not recognized.`
|
|
3991
|
+
);
|
|
3992
|
+
}
|
|
3993
|
+
this.displayMode = {
|
|
3994
|
+
zoom: zoom != null ? zoom : 1,
|
|
3995
|
+
layout: layout || "continuous",
|
|
3996
|
+
pageMode: pageMode || ""
|
|
3997
|
+
};
|
|
3998
|
+
return this;
|
|
3999
|
+
},
|
|
4000
|
+
/**
|
|
4001
|
+
* Set reader interface preferences.
|
|
4002
|
+
*
|
|
4003
|
+
* Supported flags: `HideToolbar`, `HideMenubar`, `HideWindowUI`, `FitWindow`.
|
|
4004
|
+
* Pass `doReset` to clear previously set flags first.
|
|
4005
|
+
*/
|
|
4006
|
+
viewerPreferences(options, doReset) {
|
|
4007
|
+
if (doReset) {
|
|
4008
|
+
this.viewerPrefs = {};
|
|
4009
|
+
}
|
|
4010
|
+
if (options && typeof options === "object") {
|
|
4011
|
+
for (const k of ["HideToolbar", "HideMenubar", "HideWindowUI", "FitWindow"]) {
|
|
4012
|
+
if (typeof options[k] === "boolean") {
|
|
4013
|
+
this.viewerPrefs[k] = Boolean(options[k]);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
return this;
|
|
4018
|
+
},
|
|
4019
|
+
/**
|
|
4020
|
+
* Register a total-page-count placeholder (jsPDF `putTotalPages`).
|
|
4021
|
+
*
|
|
4022
|
+
* At output time every occurrence of `pageIndicator` in text objects is
|
|
4023
|
+
* replaced with the page count.
|
|
4024
|
+
*/
|
|
4025
|
+
putTotalPages(pageIndicator = "{total}") {
|
|
4026
|
+
this.totalPagesPattern = String(pageIndicator);
|
|
4027
|
+
return this;
|
|
4028
|
+
},
|
|
4029
|
+
/** API-compatibility no-op (OFD carries no language field). */
|
|
4030
|
+
setLanguage() {
|
|
4031
|
+
return this;
|
|
4032
|
+
},
|
|
4033
|
+
/**
|
|
4034
|
+
* @internal Serialize to OFD ZIP entries (`OFD.xml` first).
|
|
4035
|
+
*
|
|
4036
|
+
* Applies the `putTotalPages` substitution on a copy, leaving the live
|
|
4037
|
+
* document untouched so `output()` stays repeatable.
|
|
4038
|
+
*/
|
|
4039
|
+
_buildFiles() {
|
|
4040
|
+
let pages = this.pages;
|
|
4041
|
+
if (this.totalPagesPattern) {
|
|
4042
|
+
const total = String(this.pages.length);
|
|
4043
|
+
pages = this.pages.map((p) => ({
|
|
4044
|
+
...p,
|
|
4045
|
+
objects: p.objects.map(
|
|
4046
|
+
(o) => o.t === "text" && o.text.includes(this.totalPagesPattern) ? { ...o, text: o.text.split(this.totalPagesPattern).join(total) } : o
|
|
4047
|
+
)
|
|
4048
|
+
}));
|
|
4049
|
+
}
|
|
4050
|
+
return buildOFDPackage({
|
|
4051
|
+
pages,
|
|
4052
|
+
customFonts: this.customFonts,
|
|
4053
|
+
imageList: this.imageList,
|
|
4054
|
+
attachments: this.attachments,
|
|
4055
|
+
outlineRoot: this.outlineRoot,
|
|
4056
|
+
properties: this.properties,
|
|
4057
|
+
docID: this.docID,
|
|
4058
|
+
creationDate: this.creationDate,
|
|
4059
|
+
modDate: this.modDate,
|
|
4060
|
+
displayMode: this.displayMode,
|
|
4061
|
+
viewerPrefs: this.viewerPrefs,
|
|
4062
|
+
measure: (text, fontDef, style, sizePt, charSpacePt) => this.measure(text, fontDef, style, sizePt, charSpacePt),
|
|
4063
|
+
getFontDef: (key) => this.getFontDef(key)
|
|
4064
|
+
});
|
|
4065
|
+
},
|
|
4066
|
+
/**
|
|
4067
|
+
* Serialize the document.
|
|
4068
|
+
*
|
|
4069
|
+
* @param type `'arraybuffer'` (default) / `'uint8array'` / `'blob'` /
|
|
4070
|
+
* `'dataurlstring'` / `'dataurlnewwindow'` / `'binarystring'` /
|
|
4071
|
+
* `'bloburl'`
|
|
4072
|
+
*/
|
|
4073
|
+
output(type = "arraybuffer") {
|
|
4074
|
+
const t = String(type).toLowerCase();
|
|
4075
|
+
const zipBytes = zipStore(this._buildFiles(), this.creationDate);
|
|
4076
|
+
switch (t) {
|
|
4077
|
+
case "arraybuffer":
|
|
4078
|
+
return zipBytes.buffer.slice(
|
|
4079
|
+
zipBytes.byteOffset,
|
|
4080
|
+
zipBytes.byteOffset + zipBytes.byteLength
|
|
4081
|
+
);
|
|
4082
|
+
case "uint8array":
|
|
4083
|
+
return zipBytes;
|
|
4084
|
+
case "binarystring":
|
|
4085
|
+
case "binary": {
|
|
4086
|
+
let s = "";
|
|
4087
|
+
for (let i = 0; i < zipBytes.length; i++) s += String.fromCharCode(zipBytes[i]);
|
|
4088
|
+
return s;
|
|
4089
|
+
}
|
|
4090
|
+
case "dataurlstring":
|
|
4091
|
+
case "datauristring":
|
|
4092
|
+
case "dataurl":
|
|
4093
|
+
case "datauri":
|
|
4094
|
+
return "data:application/ofd;base64," + b64Encode(zipBytes);
|
|
4095
|
+
case "blob":
|
|
4096
|
+
if (typeof Blob === "undefined") {
|
|
4097
|
+
throw new Error('output("blob") requires Blob support; use output("arraybuffer")');
|
|
4098
|
+
}
|
|
4099
|
+
return new Blob([zipBytes], { type: "application/ofd" });
|
|
4100
|
+
case "bloburl":
|
|
4101
|
+
case "blobobject":
|
|
4102
|
+
if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) {
|
|
4103
|
+
throw new Error('output("bloburl") requires Blob URL support');
|
|
4104
|
+
}
|
|
4105
|
+
return URL.createObjectURL(
|
|
4106
|
+
new Blob([zipBytes], { type: "application/ofd" })
|
|
4107
|
+
);
|
|
4108
|
+
case "dataurlnewwindow":
|
|
4109
|
+
if (typeof window === "undefined" || !window.open) {
|
|
4110
|
+
throw new Error('output("dataurlnewwindow") requires window.open');
|
|
4111
|
+
}
|
|
4112
|
+
window.open("data:application/ofd;base64," + b64Encode(zipBytes), "_blank");
|
|
4113
|
+
return null;
|
|
4114
|
+
default:
|
|
4115
|
+
throw new Error("output: unknown output type " + type);
|
|
4116
|
+
}
|
|
4117
|
+
},
|
|
4118
|
+
/** Shorthand for `output('dataurlstring')`. */
|
|
4119
|
+
getDataUrl() {
|
|
4120
|
+
return this.output("dataurlstring");
|
|
4121
|
+
},
|
|
4122
|
+
/**
|
|
4123
|
+
* Save the document to a file.
|
|
4124
|
+
*
|
|
4125
|
+
* Browser: triggers a download. Node (CJS): writes into the working
|
|
4126
|
+
* directory. Node ESM has no synchronous `require` — use
|
|
4127
|
+
* `output('arraybuffer')` and write the bytes yourself.
|
|
4128
|
+
*/
|
|
4129
|
+
save(filename) {
|
|
4130
|
+
let name = filename || "document.ofd";
|
|
4131
|
+
if (!/\.ofd$/i.test(name)) name += ".ofd";
|
|
4132
|
+
if (IS_NODE) {
|
|
4133
|
+
const fs = nodeRequire("fs");
|
|
4134
|
+
const path = nodeRequire("path");
|
|
4135
|
+
if (fs) {
|
|
4136
|
+
const p = path ? path.join(process.cwd(), name) : name;
|
|
4137
|
+
fs.writeFileSync(p, this.output("uint8array"));
|
|
4138
|
+
return this;
|
|
4139
|
+
}
|
|
4140
|
+
throw new Error('Node ESM: use output("arraybuffer") and write the file yourself');
|
|
4141
|
+
}
|
|
4142
|
+
const blob = this.output("blob");
|
|
4143
|
+
const url = URL.createObjectURL(blob);
|
|
4144
|
+
const a = document.createElement("a");
|
|
4145
|
+
a.href = url;
|
|
4146
|
+
a.download = name;
|
|
4147
|
+
document.body.appendChild(a);
|
|
4148
|
+
a.click();
|
|
4149
|
+
document.body.removeChild(a);
|
|
4150
|
+
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
4151
|
+
return this;
|
|
4152
|
+
}
|
|
4153
|
+
};
|
|
4154
|
+
|
|
4155
|
+
// src/jsofd.ts
|
|
4156
|
+
var JSOFD_VERSION = "1.0.0";
|
|
4157
|
+
var DEFAULT_LINE_WIDTH_PT = 0.2 * PT_PER_MM;
|
|
4158
|
+
var _ctx2d;
|
|
4159
|
+
var jsOFD = class {
|
|
4160
|
+
constructor(options = {}, unitArg, formatArg) {
|
|
4161
|
+
/** Active measurement unit. */
|
|
4162
|
+
__publicField(this, "unit");
|
|
4163
|
+
/** Unit → point scale factor (jsPDF-compatible `internal.scaleFactor`). */
|
|
4164
|
+
__publicField(this, "scaleFactor");
|
|
4165
|
+
/** Decimal places used when serializing numbers. */
|
|
4166
|
+
__publicField(this, "floatPrecision");
|
|
4167
|
+
/** @internal Default orientation for `addPage()` without arguments. */
|
|
4168
|
+
__publicField(this, "defaultOrientation");
|
|
4169
|
+
/** Pages in the document (internal coordinates are points). */
|
|
4170
|
+
__publicField(this, "pages", []);
|
|
4171
|
+
/** Zero-based index of the current page. */
|
|
4172
|
+
__publicField(this, "page", 0);
|
|
4173
|
+
/** User-registered fonts. */
|
|
4174
|
+
__publicField(this, "customFonts", {});
|
|
4175
|
+
/** Image resources, keyed by alias, plus insertion order. */
|
|
4176
|
+
__publicField(this, "images", {});
|
|
4177
|
+
__publicField(this, "imageList", []);
|
|
4178
|
+
/** Embedded file attachments. */
|
|
4179
|
+
__publicField(this, "attachments", []);
|
|
4180
|
+
/** Bookmark tree root. */
|
|
4181
|
+
__publicField(this, "outlineRoot", { children: [] });
|
|
4182
|
+
/** Document metadata (see `setProperties`). */
|
|
4183
|
+
__publicField(this, "properties", {
|
|
4184
|
+
title: "",
|
|
4185
|
+
subject: "",
|
|
4186
|
+
author: "",
|
|
4187
|
+
keywords: "",
|
|
4188
|
+
creator: "jsOFD " + JSOFD_VERSION,
|
|
4189
|
+
creatorVersion: JSOFD_VERSION
|
|
4190
|
+
});
|
|
4191
|
+
/** Random document identifier. */
|
|
4192
|
+
__publicField(this, "docID", randomHex32());
|
|
4193
|
+
__publicField(this, "creationDate", /* @__PURE__ */ new Date());
|
|
4194
|
+
__publicField(this, "modDate", null);
|
|
4195
|
+
__publicField(this, "displayMode", null);
|
|
4196
|
+
__publicField(this, "viewerPrefs", {});
|
|
4197
|
+
/** Placeholder (e.g. `{total}`) replaced at output time, see `putTotalPages`. */
|
|
4198
|
+
__publicField(this, "totalPagesPattern", null);
|
|
4199
|
+
// ---- Graphics state ----------------------------------------------------
|
|
4200
|
+
__publicField(this, "activeFontKey", "helvetica");
|
|
4201
|
+
__publicField(this, "activeFontStyle", "normal");
|
|
4202
|
+
/** Font size in points. Like jsPDF, the size is always in pt regardless of unit. */
|
|
4203
|
+
__publicField(this, "fontSize", 16);
|
|
4204
|
+
__publicField(this, "textColor", [0, 0, 0]);
|
|
4205
|
+
__publicField(this, "drawColor", [0, 0, 0]);
|
|
4206
|
+
__publicField(this, "fillColor", [0, 0, 0]);
|
|
4207
|
+
__publicField(this, "lineWidth", DEFAULT_LINE_WIDTH_PT);
|
|
4208
|
+
__publicField(this, "lineDash", null);
|
|
4209
|
+
__publicField(this, "lineCap", 0);
|
|
4210
|
+
__publicField(this, "lineJoin", 0);
|
|
4211
|
+
/** Miter limit in points; 0 omits the attribute. */
|
|
4212
|
+
__publicField(this, "miterLimit", 0);
|
|
4213
|
+
__publicField(this, "charSpace", 0);
|
|
4214
|
+
// pt
|
|
4215
|
+
__publicField(this, "lineHeightFactor", 1.15);
|
|
4216
|
+
__publicField(this, "opacity", null);
|
|
4217
|
+
__publicField(this, "r2l", false);
|
|
4218
|
+
/** @internal Paper format used by `addPage()` without arguments. */
|
|
4219
|
+
__publicField(this, "_lastFormat", "a4");
|
|
4220
|
+
/** jsPDF-style introspection surface. */
|
|
4221
|
+
__publicField(this, "internal");
|
|
4222
|
+
/** Bookmark API: `doc.outline.add(parent, title, options)`. */
|
|
4223
|
+
__publicField(this, "outline");
|
|
4224
|
+
/** Lazily created canvas-2D adapter, see `createContext2d`. */
|
|
4225
|
+
__privateAdd(this, _ctx2d, null);
|
|
4226
|
+
if (typeof options === "string") {
|
|
4227
|
+
options = {
|
|
4228
|
+
orientation: options,
|
|
4229
|
+
unit: unitArg,
|
|
4230
|
+
format: formatArg
|
|
4231
|
+
};
|
|
4232
|
+
}
|
|
4233
|
+
const unit = String(options.unit || "mm").toLowerCase();
|
|
4234
|
+
if (!isUnit(unit)) {
|
|
4235
|
+
throw new Error(`Invalid unit: ${unit}`);
|
|
4236
|
+
}
|
|
4237
|
+
this.unit = unit;
|
|
4238
|
+
this.scaleFactor = UNIT_FACTORS[unit];
|
|
4239
|
+
const ori = String(options.orientation || "p").toLowerCase();
|
|
4240
|
+
this.defaultOrientation = ori === "landscape" || ori === "l" ? "l" : "p";
|
|
4241
|
+
this.floatPrecision = options.floatPrecision || 4;
|
|
4242
|
+
const firstSize = this._resolveFormat(options.format || "a4", this.defaultOrientation);
|
|
4243
|
+
this._addPageWithSize(firstSize[0], firstSize[1]);
|
|
4244
|
+
const pages = () => this.pages;
|
|
4245
|
+
this.internal = {
|
|
4246
|
+
version: JSOFD_VERSION,
|
|
4247
|
+
scaleFactor: this.scaleFactor,
|
|
4248
|
+
pageSize: {
|
|
4249
|
+
getWidth: () => this.pages[this.page].width / this.scaleFactor,
|
|
4250
|
+
getHeight: () => this.pages[this.page].height / this.scaleFactor
|
|
4251
|
+
},
|
|
4252
|
+
get numberOfPages() {
|
|
4253
|
+
return pages().length;
|
|
4254
|
+
},
|
|
4255
|
+
get pages() {
|
|
4256
|
+
return pages();
|
|
4257
|
+
},
|
|
4258
|
+
getFileId: () => this.docID
|
|
4259
|
+
};
|
|
4260
|
+
this.outline = {
|
|
4261
|
+
add: (parent, title, options2) => {
|
|
4262
|
+
const node = {
|
|
4263
|
+
title: String(title),
|
|
4264
|
+
parent: parent || null,
|
|
4265
|
+
children: [],
|
|
4266
|
+
options: options2 || {}
|
|
4267
|
+
};
|
|
4268
|
+
(parent || this.outlineRoot).children.push(node);
|
|
4269
|
+
return node;
|
|
4270
|
+
}
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
/**
|
|
4274
|
+
* Canvas-style drawing context (subset of `CanvasRenderingContext2D`).
|
|
4275
|
+
* Draw calls land on the current page as vector objects.
|
|
4276
|
+
*/
|
|
4277
|
+
get context2d() {
|
|
4278
|
+
if (!__privateGet(this, _ctx2d)) __privateSet(this, _ctx2d, createContext2d(this));
|
|
4279
|
+
return __privateGet(this, _ctx2d);
|
|
4280
|
+
}
|
|
4281
|
+
/* ---------------- Page management ---------------- */
|
|
4282
|
+
/** @internal Resolve a named or `[w, h]` format to a pt size pair. */
|
|
4283
|
+
_resolveFormat(format, orientation) {
|
|
4284
|
+
let size;
|
|
4285
|
+
if (typeof format === "string") {
|
|
4286
|
+
const f = PAGE_FORMATS_MM[format.toLowerCase()];
|
|
4287
|
+
if (!f) throw new Error("Unknown paper format: " + format);
|
|
4288
|
+
size = [f[0] * PT_PER_MM, f[1] * PT_PER_MM];
|
|
4289
|
+
} else if (Array.isArray(format) && format.length >= 2) {
|
|
4290
|
+
size = [Number(format[0]) * this.scaleFactor, Number(format[1]) * this.scaleFactor];
|
|
4291
|
+
} else {
|
|
4292
|
+
throw new Error("Invalid format: " + String(format));
|
|
4293
|
+
}
|
|
4294
|
+
if (orientation === "l") {
|
|
4295
|
+
return [Math.max(size[0], size[1]), Math.min(size[0], size[1])];
|
|
4296
|
+
}
|
|
4297
|
+
return [Math.min(size[0], size[1]), Math.max(size[0], size[1])];
|
|
4298
|
+
}
|
|
4299
|
+
/** @internal Convert a coordinate in the active unit to points. */
|
|
4300
|
+
_u(v) {
|
|
4301
|
+
return v * this.scaleFactor;
|
|
4302
|
+
}
|
|
4303
|
+
/** @internal Append a page sized in points and make it current. */
|
|
4304
|
+
_addPageWithSize(wPt, hPt) {
|
|
4305
|
+
this.pages.push({ width: wPt, height: hPt, objects: [] });
|
|
4306
|
+
this.page = this.pages.length - 1;
|
|
4307
|
+
return this;
|
|
4308
|
+
}
|
|
4309
|
+
/** Append a page, optionally with a different format and orientation. */
|
|
4310
|
+
addPage(format, orientation) {
|
|
4311
|
+
const f = format || this._lastFormat;
|
|
4312
|
+
this._lastFormat = f;
|
|
4313
|
+
const ori = orientation ? String(orientation).toLowerCase() : this.defaultOrientation;
|
|
4314
|
+
const size = this._resolveFormat(f, ori === "landscape" || ori === "l" ? "l" : "p");
|
|
4315
|
+
return this._addPageWithSize(size[0], size[1]);
|
|
4316
|
+
}
|
|
4317
|
+
/** Switch to a one-based page number. */
|
|
4318
|
+
setPage(n) {
|
|
4319
|
+
if (n < 1 || n > this.pages.length) {
|
|
4320
|
+
throw new Error(`setPage: page number out of range 1-${this.pages.length}`);
|
|
4321
|
+
}
|
|
4322
|
+
this.page = n - 1;
|
|
4323
|
+
return this;
|
|
4324
|
+
}
|
|
4325
|
+
/** Insert an empty page before the given one-based position. */
|
|
4326
|
+
insertPage(beforePage) {
|
|
4327
|
+
const cur = this.pages[this.page];
|
|
4328
|
+
const page = { width: cur.width, height: cur.height, objects: [] };
|
|
4329
|
+
const at = Math.max(1, Math.min(beforePage || 1, this.pages.length + 1));
|
|
4330
|
+
this.pages.splice(at - 1, 0, page);
|
|
4331
|
+
this.page = at - 1;
|
|
4332
|
+
return this;
|
|
4333
|
+
}
|
|
4334
|
+
/** Move a page (default: the current one) to before another position. */
|
|
4335
|
+
movePage(targetPage, beforePage) {
|
|
4336
|
+
const from = targetPage != null ? targetPage : this.page + 1;
|
|
4337
|
+
if (from < 1 || from > this.pages.length) return this;
|
|
4338
|
+
const pg = this.pages.splice(from - 1, 1)[0];
|
|
4339
|
+
const at = Math.max(1, Math.min(beforePage || 1, this.pages.length + 1));
|
|
4340
|
+
this.pages.splice(at - 1, 0, pg);
|
|
4341
|
+
this.page = at - 1;
|
|
4342
|
+
return this;
|
|
4343
|
+
}
|
|
4344
|
+
/** Delete a page (the last page is replaced by an empty one). */
|
|
4345
|
+
deletePage(target) {
|
|
4346
|
+
const at = typeof target === "number" ? target - 1 : this.page;
|
|
4347
|
+
if (at < 0 || at >= this.pages.length) return this;
|
|
4348
|
+
if (this.pages.length > 1) {
|
|
4349
|
+
this.pages.splice(at, 1);
|
|
4350
|
+
this.page = Math.min(this.page, this.pages.length - 1);
|
|
4351
|
+
} else {
|
|
4352
|
+
this.pages[0].objects = [];
|
|
4353
|
+
this.page = 0;
|
|
4354
|
+
}
|
|
4355
|
+
return this;
|
|
4356
|
+
}
|
|
4357
|
+
/** Number of pages in the document. */
|
|
4358
|
+
getNumberOfPages() {
|
|
4359
|
+
return this.pages.length;
|
|
4360
|
+
}
|
|
4361
|
+
/** Width of the current page in the active unit. */
|
|
4362
|
+
getPageWidth() {
|
|
4363
|
+
return this.pages[this.page].width / this.scaleFactor;
|
|
4364
|
+
}
|
|
4365
|
+
/** Height of the current page in the active unit. */
|
|
4366
|
+
getPageHeight() {
|
|
4367
|
+
return this.pages[this.page].height / this.scaleFactor;
|
|
4368
|
+
}
|
|
4369
|
+
};
|
|
4370
|
+
_ctx2d = new WeakMap();
|
|
4371
|
+
Object.assign(
|
|
4372
|
+
jsOFD.prototype,
|
|
4373
|
+
StateApi,
|
|
4374
|
+
TextApi,
|
|
4375
|
+
ShapeApi,
|
|
4376
|
+
ImageApi,
|
|
4377
|
+
AnnotationApi,
|
|
4378
|
+
AnnotationVisualApi,
|
|
4379
|
+
FlowApi,
|
|
4380
|
+
TableApi,
|
|
4381
|
+
HtmlApi,
|
|
4382
|
+
SvgApi,
|
|
4383
|
+
ViewApi
|
|
4384
|
+
);
|
|
4385
|
+
|
|
4386
|
+
// src/index.ts
|
|
4387
|
+
var version = JSOFD_VERSION;
|
|
4388
|
+
var src_default = jsOFD;
|
|
4389
|
+
return __toCommonJS(src_exports);
|
|
4390
|
+
})();
|