@weasel-js/pdf 0.0.0-stage → 1.11.1
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 +69 -2
- package/dist/index.d.ts +58 -0
- package/dist/index.js +1307 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,1307 @@
|
|
|
1
|
+
import { glyphOutline, fontEmbedding } from '@weasel-js/font';
|
|
2
|
+
import { mat3, fillsOpenSubpaths, transformPath, pathFromD, resolveCorners, forEachSegment, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, boundsOfPath } from '@weasel-js/geom';
|
|
3
|
+
import { fillHasInk, strokeHasInk, isGradientPaint, isSolidPaint, parseColor, resolveGradientStops, sampleResolvedStops, gradientGeometry } from '@weasel-js/paint';
|
|
4
|
+
import { isOwnComposite, SPRITE_STRIDE, markerDrawCommands, commandsBounds, composeColorMatrix, resolveStrokeLengths, textureRegistry, applyColorMatrix } from '@weasel-js/renderer';
|
|
5
|
+
import { strokeToPath, strokeInsets } from '@weasel-js/stroke';
|
|
6
|
+
import { resolveTextStyle, cachedLayoutRuns, verticalAlignOffset, cachedPaintRuns, syntheticBoldOffset, emboldenGlyphD, SYNTHETIC_ITALIC_RADIANS } from '@weasel-js/text';
|
|
7
|
+
|
|
8
|
+
// src/fonts/fontSource.ts
|
|
9
|
+
var registryFontSource = {
|
|
10
|
+
embedding: fontEmbedding,
|
|
11
|
+
outline: glyphOutline
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/latin1.ts
|
|
15
|
+
function latin1Bytes(s) {
|
|
16
|
+
return Uint8Array.from(s, (c) => c.charCodeAt(0));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/num.ts
|
|
20
|
+
var NonFiniteNumber = class extends Error {
|
|
21
|
+
};
|
|
22
|
+
function num(n) {
|
|
23
|
+
if (!Number.isFinite(n)) throw new NonFiniteNumber(`pdf: cannot write non-finite number ${n}`);
|
|
24
|
+
if (Math.abs(n) >= 1e21) return BigInt(n).toString();
|
|
25
|
+
const s = n.toFixed(4).replace(/\.?0+$/, "");
|
|
26
|
+
return s === "-0" || s === "" ? "0" : s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/content.ts
|
|
30
|
+
var ContentStream = class {
|
|
31
|
+
lines = [];
|
|
32
|
+
/** Numbers are written with `num`; strings go in as given, so a name operand is `'/GS1'`. */
|
|
33
|
+
op(operator, ...operands) {
|
|
34
|
+
const args = operands.map((o) => typeof o === "number" ? num(o) : o);
|
|
35
|
+
this.lines.push(args.length ? `${args.join(" ")} ${operator}` : operator);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
/** Where the stream ends now, for `rollback`. */
|
|
39
|
+
mark() {
|
|
40
|
+
return this.lines.length;
|
|
41
|
+
}
|
|
42
|
+
/** Drops every operator written since `mark` answered `at`. */
|
|
43
|
+
rollback(at) {
|
|
44
|
+
this.lines.length = at;
|
|
45
|
+
}
|
|
46
|
+
bytes() {
|
|
47
|
+
return latin1Bytes(`${this.lines.join("\n")}
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/deflate.ts
|
|
53
|
+
async function deflate(data) {
|
|
54
|
+
const stream = new Blob([data]).stream().pipeThrough(new CompressionStream("deflate"));
|
|
55
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
56
|
+
}
|
|
57
|
+
function foldColor(rgba, m) {
|
|
58
|
+
const [r, g, b, a] = rgba;
|
|
59
|
+
return m ? applyColorMatrix(m, r, g, b, a) : [r, g, b, a];
|
|
60
|
+
}
|
|
61
|
+
function composeMatrix(outer, inner) {
|
|
62
|
+
if (!inner) return outer ? [...outer] : null;
|
|
63
|
+
if (!outer) return [...inner];
|
|
64
|
+
return composeColorMatrix(outer, inner, new Array(20));
|
|
65
|
+
}
|
|
66
|
+
function colorFor(color, state) {
|
|
67
|
+
return foldColor(typeof color === "string" ? parseColor(color) : color, state.colorMatrix);
|
|
68
|
+
}
|
|
69
|
+
function solidColor(paint, state) {
|
|
70
|
+
const [r, g, b, a] = colorFor(paint.color, state);
|
|
71
|
+
return { rgb: [r, g, b], alpha: a * (paint.opacity ?? 1) * state.alpha };
|
|
72
|
+
}
|
|
73
|
+
function foldPixels(data, state) {
|
|
74
|
+
if (!state.colorMatrix) return data;
|
|
75
|
+
const out = new Uint8ClampedArray(data.length);
|
|
76
|
+
for (let i = 0; i + 3 < data.length; i += 4) {
|
|
77
|
+
const [r, g, b, a] = foldColor(
|
|
78
|
+
[data[i] / 255, data[i + 1] / 255, data[i + 2] / 255, data[i + 3] / 255],
|
|
79
|
+
state.colorMatrix
|
|
80
|
+
);
|
|
81
|
+
out[i] = r * 255;
|
|
82
|
+
out[i + 1] = g * 255;
|
|
83
|
+
out[i + 2] = b * 255;
|
|
84
|
+
out[i + 3] = a * 255;
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/objects.ts
|
|
90
|
+
var PdfName = class {
|
|
91
|
+
constructor(value) {
|
|
92
|
+
this.value = value;
|
|
93
|
+
}
|
|
94
|
+
value;
|
|
95
|
+
};
|
|
96
|
+
var PdfRef = class {
|
|
97
|
+
constructor(id) {
|
|
98
|
+
this.id = id;
|
|
99
|
+
}
|
|
100
|
+
id;
|
|
101
|
+
};
|
|
102
|
+
var PdfText = class {
|
|
103
|
+
constructor(value) {
|
|
104
|
+
this.value = value;
|
|
105
|
+
}
|
|
106
|
+
value;
|
|
107
|
+
};
|
|
108
|
+
var PdfBytes = class {
|
|
109
|
+
constructor(value) {
|
|
110
|
+
this.value = value;
|
|
111
|
+
}
|
|
112
|
+
value;
|
|
113
|
+
};
|
|
114
|
+
function name(n) {
|
|
115
|
+
return new PdfName(n);
|
|
116
|
+
}
|
|
117
|
+
function text(s) {
|
|
118
|
+
return new PdfText(s);
|
|
119
|
+
}
|
|
120
|
+
var hex2 = (b) => b.toString(16).toUpperCase().padStart(2, "0");
|
|
121
|
+
function writeName(n) {
|
|
122
|
+
let out = "/";
|
|
123
|
+
for (const b of new TextEncoder().encode(n)) {
|
|
124
|
+
const ch = String.fromCharCode(b);
|
|
125
|
+
out += b < 33 || b > 126 || "#()<>[]{}/%".includes(ch) ? `#${hex2(b)}` : ch;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function writeText(s) {
|
|
130
|
+
if (/^[\x20-\x7e]*$/.test(s)) return `(${s.replace(/[\\()]/g, "\\$&")})`;
|
|
131
|
+
let out = "<FEFF";
|
|
132
|
+
for (let i = 0; i < s.length; i++) out += s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
133
|
+
return `${out}>`;
|
|
134
|
+
}
|
|
135
|
+
function write(v) {
|
|
136
|
+
if (v === null) return "null";
|
|
137
|
+
if (typeof v === "number") return num(v);
|
|
138
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
139
|
+
if (v instanceof PdfName) return writeName(v.value);
|
|
140
|
+
if (v instanceof PdfRef) return `${v.id} 0 R`;
|
|
141
|
+
if (v instanceof PdfText) return writeText(v.value);
|
|
142
|
+
if (v instanceof PdfBytes) return `<${Array.from(v.value, hex2).join("")}>`;
|
|
143
|
+
if (Array.isArray(v)) return `[${v.map(write).join(" ")}]`;
|
|
144
|
+
return writeDict(v);
|
|
145
|
+
}
|
|
146
|
+
function writeDict(d) {
|
|
147
|
+
const parts = [];
|
|
148
|
+
for (const [k, v] of Object.entries(d)) if (v !== void 0) parts.push(`${writeName(k)} ${write(v)}`);
|
|
149
|
+
return `<<${parts.join(" ")}>>`;
|
|
150
|
+
}
|
|
151
|
+
var PdfObjects = class {
|
|
152
|
+
bodies = [];
|
|
153
|
+
pending = [];
|
|
154
|
+
alloc() {
|
|
155
|
+
this.bodies.push(void 0);
|
|
156
|
+
return new PdfRef(this.bodies.length);
|
|
157
|
+
}
|
|
158
|
+
set(ref, value) {
|
|
159
|
+
this.bodies[ref.id - 1] = latin1Bytes(write(value));
|
|
160
|
+
}
|
|
161
|
+
/** Writes `/Length` itself; the caller passes the already-encoded bytes. */
|
|
162
|
+
stream(ref, dict, data) {
|
|
163
|
+
const head = latin1Bytes(`${writeDict({ ...dict, Length: data.length })}
|
|
164
|
+
stream
|
|
165
|
+
`);
|
|
166
|
+
const tail = latin1Bytes("\nendstream");
|
|
167
|
+
const out = new Uint8Array(head.length + data.length + tail.length);
|
|
168
|
+
out.set(head, 0);
|
|
169
|
+
out.set(data, head.length);
|
|
170
|
+
out.set(tail, head.length + data.length);
|
|
171
|
+
this.bodies[ref.id - 1] = out;
|
|
172
|
+
}
|
|
173
|
+
/** Like `stream`, but takes the raw bytes: `settle` compresses them (or not) before `serialize`. */
|
|
174
|
+
streamDeflated(ref, dict, data) {
|
|
175
|
+
writeDict(dict);
|
|
176
|
+
this.pending.push({ ref, dict, data });
|
|
177
|
+
}
|
|
178
|
+
/** How many objects are allocated, for `nullUnset`. */
|
|
179
|
+
mark() {
|
|
180
|
+
return this.bodies.length;
|
|
181
|
+
}
|
|
182
|
+
/** Sets every object allocated since `mark` answered `at` and not yet written to `null`, so a dropped command leaves no hole. */
|
|
183
|
+
nullUnset(at) {
|
|
184
|
+
const waiting = new Set(this.pending.map((p) => p.ref.id));
|
|
185
|
+
for (let id = at + 1; id <= this.bodies.length; id++) {
|
|
186
|
+
if (!this.bodies[id - 1] && !waiting.has(id)) this.set(new PdfRef(id), null);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Writes every `streamDeflated` stream, FlateDecode-compressed when `compress` is set. */
|
|
190
|
+
async settle(compress, deflate2) {
|
|
191
|
+
for (const { ref, dict, data } of this.pending.splice(0)) {
|
|
192
|
+
if (compress) this.stream(ref, { ...dict, Filter: name("FlateDecode") }, await deflate2(data));
|
|
193
|
+
else this.stream(ref, dict, data);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
serialize(root, info) {
|
|
197
|
+
const chunks = [];
|
|
198
|
+
let offset = 0;
|
|
199
|
+
const push = (c) => {
|
|
200
|
+
chunks.push(c);
|
|
201
|
+
offset += c.length;
|
|
202
|
+
};
|
|
203
|
+
push(Uint8Array.of(...latin1Bytes("%PDF-1.7\n%"), 226, 227, 207, 211, 10));
|
|
204
|
+
const offsets = [];
|
|
205
|
+
this.bodies.forEach((body, i) => {
|
|
206
|
+
if (!body) throw new Error(`pdf: object ${i + 1} was allocated but never set`);
|
|
207
|
+
offsets.push(offset);
|
|
208
|
+
push(latin1Bytes(`${i + 1} 0 obj
|
|
209
|
+
`));
|
|
210
|
+
push(body);
|
|
211
|
+
push(latin1Bytes("\nendobj\n"));
|
|
212
|
+
});
|
|
213
|
+
const xref = offset;
|
|
214
|
+
let table = `xref
|
|
215
|
+
0 ${this.bodies.length + 1}
|
|
216
|
+
0000000000 65535 f
|
|
217
|
+
`;
|
|
218
|
+
for (const o of offsets) table += `${String(o).padStart(10, "0")} 00000 n
|
|
219
|
+
`;
|
|
220
|
+
const trailer = writeDict({ Size: this.bodies.length + 1, Root: root, Info: info });
|
|
221
|
+
push(latin1Bytes(`${table}trailer
|
|
222
|
+
${trailer}
|
|
223
|
+
startxref
|
|
224
|
+
${xref}
|
|
225
|
+
%%EOF
|
|
226
|
+
`));
|
|
227
|
+
const out = new Uint8Array(offset);
|
|
228
|
+
let at = 0;
|
|
229
|
+
for (const c of chunks) {
|
|
230
|
+
out.set(c, at);
|
|
231
|
+
at += c.length;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var SAMPLED_STOPS = 64;
|
|
237
|
+
var CONIC_WEDGES = 360;
|
|
238
|
+
function paintShading(ctx, paint, state, path) {
|
|
239
|
+
if (!isGradientPaint(paint)) return null;
|
|
240
|
+
const toFrame = paintToFrame(ctx, paint, state);
|
|
241
|
+
const toPaint = toFrame && mat3.invert(toFrame);
|
|
242
|
+
if (!toFrame || !toPaint) return null;
|
|
243
|
+
const box = paintBox(path, toPaint);
|
|
244
|
+
let segments = rampSegments(stopsOf(paint, state));
|
|
245
|
+
const geometry = geometryOf(paint, box);
|
|
246
|
+
if (geometry.degenerate) segments = [{ lo: 0, hi: 1, c0: segments[0].c0, c1: segments[0].c0 }];
|
|
247
|
+
let alpha = (paint.opacity ?? 1) * state.alpha;
|
|
248
|
+
const a0 = segments[0].c0[3];
|
|
249
|
+
const uniform = segments.every((s) => s.c0[3] === a0 && s.c1[3] === a0);
|
|
250
|
+
let softMask = null;
|
|
251
|
+
if (uniform) alpha *= a0;
|
|
252
|
+
else {
|
|
253
|
+
const mask = writeShading(ctx, geometry, rampFunction(segments, (c) => [c[3]]), "DeviceGray");
|
|
254
|
+
softMask = ctx.resources.softMask(writeMaskGroup(ctx, mask, box));
|
|
255
|
+
}
|
|
256
|
+
const color = writeShading(ctx, geometry, rampFunction(segments, (c) => [c[0], c[1], c[2]]), "DeviceRGB");
|
|
257
|
+
return { shading: ctx.resources.shading(color), softMask, alpha, matrix: isIdentity(toFrame) ? null : toFrame };
|
|
258
|
+
}
|
|
259
|
+
function paintToFrame(ctx, paint, state) {
|
|
260
|
+
if (paint.units === "local") return mat3.identity();
|
|
261
|
+
const fromPage = mat3.invert(state.transform);
|
|
262
|
+
if (!fromPage) return null;
|
|
263
|
+
return paint.units === "world" && ctx.view ? mat3.multiply(fromPage, ctx.view) : fromPage;
|
|
264
|
+
}
|
|
265
|
+
function isIdentity(m) {
|
|
266
|
+
return m[0] === 1 && m[1] === 0 && m[3] === 0 && m[4] === 1 && m[6] === 0 && m[7] === 0;
|
|
267
|
+
}
|
|
268
|
+
function paintBox(path, toPaint) {
|
|
269
|
+
const b = boundsOfPath(path);
|
|
270
|
+
const corners = [
|
|
271
|
+
mat3.apply(toPaint, b.x, b.y),
|
|
272
|
+
mat3.apply(toPaint, b.x + b.width, b.y),
|
|
273
|
+
mat3.apply(toPaint, b.x, b.y + b.height),
|
|
274
|
+
mat3.apply(toPaint, b.x + b.width, b.y + b.height)
|
|
275
|
+
];
|
|
276
|
+
const xs = corners.map((c) => c[0]);
|
|
277
|
+
const ys = corners.map((c) => c[1]);
|
|
278
|
+
return { x0: Math.min(...xs) - 1, y0: Math.min(...ys) - 1, x1: Math.max(...xs) + 1, y1: Math.max(...ys) + 1 };
|
|
279
|
+
}
|
|
280
|
+
function stopsOf(paint, state) {
|
|
281
|
+
const resolved = resolveGradientStops(paint.stops);
|
|
282
|
+
const space = paint.interpolate ?? "rgb";
|
|
283
|
+
if (resolved.length === 0) return [{ offset: 0, rgba: colorFor([0, 0, 0, 0], state) }];
|
|
284
|
+
if (space === "rgb") return resolved.map((s) => ({ offset: s.offset, rgba: colorFor(s.rgba, state) }));
|
|
285
|
+
return Array.from({ length: SAMPLED_STOPS }, (_, i) => {
|
|
286
|
+
const t = i / (SAMPLED_STOPS - 1);
|
|
287
|
+
return { offset: t, rgba: colorFor(sampleResolvedStops(resolved, t, space), state) };
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
var lerp = (a, b, f) => a.map((v, i) => v + (b[i] - v) * f);
|
|
291
|
+
function rampSegments(stops) {
|
|
292
|
+
const first = stops[0];
|
|
293
|
+
const last = stops[stops.length - 1];
|
|
294
|
+
const raw = [];
|
|
295
|
+
if (first.offset > 0) raw.push({ lo: Math.min(0, first.offset), hi: first.offset, c0: first.rgba, c1: first.rgba });
|
|
296
|
+
for (let i = 0; i < stops.length - 1; i++) {
|
|
297
|
+
const a = stops[i];
|
|
298
|
+
const b = stops[i + 1];
|
|
299
|
+
raw.push({ lo: a.offset, hi: b.offset, c0: a.rgba, c1: b.rgba });
|
|
300
|
+
}
|
|
301
|
+
if (last.offset < 1) raw.push({ lo: last.offset, hi: Math.max(1, last.offset), c0: last.rgba, c1: last.rgba });
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const s of raw) {
|
|
304
|
+
const lo = Math.max(s.lo, 0);
|
|
305
|
+
const hi = Math.min(s.hi, 1);
|
|
306
|
+
if (hi <= lo) continue;
|
|
307
|
+
const span = s.hi - s.lo;
|
|
308
|
+
out.push({ lo, hi, c0: lerp(s.c0, s.c1, (lo - s.lo) / span), c1: lerp(s.c0, s.c1, (hi - s.lo) / span) });
|
|
309
|
+
}
|
|
310
|
+
if (out.length === 0) {
|
|
311
|
+
const c = first.offset >= 1 ? first.rgba : last.rgba;
|
|
312
|
+
out.push({ lo: 0, hi: 1, c0: c, c1: c });
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
function rampFunction(segments, channels) {
|
|
317
|
+
const pieces = segments.map(
|
|
318
|
+
(s) => ({ FunctionType: 2, Domain: [0, 1], C0: channels(s.c0), C1: channels(s.c1), N: 1 })
|
|
319
|
+
);
|
|
320
|
+
if (pieces.length === 1) return pieces[0];
|
|
321
|
+
return {
|
|
322
|
+
FunctionType: 3,
|
|
323
|
+
Domain: [0, 1],
|
|
324
|
+
Functions: pieces,
|
|
325
|
+
Bounds: segments.slice(0, -1).map((s) => s.hi),
|
|
326
|
+
Encode: segments.flatMap(() => [0, 1])
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function geometryOf(paint, box) {
|
|
330
|
+
const extend = [true, true];
|
|
331
|
+
if (paint.kind === "linear-gradient") {
|
|
332
|
+
const { from, to } = paint;
|
|
333
|
+
const degenerate = from.x === to.x && from.y === to.y;
|
|
334
|
+
const coords = degenerate ? [from.x, from.y, from.x + 1, from.y] : [from.x, from.y, to.x, to.y];
|
|
335
|
+
return { dict: { ShadingType: 2, Coords: coords, Extend: extend }, degenerate };
|
|
336
|
+
}
|
|
337
|
+
const { center, radius, angle } = gradientGeometry(paint);
|
|
338
|
+
if (paint.kind === "radial-gradient") {
|
|
339
|
+
const r = Math.max(radius, 1e-4);
|
|
340
|
+
return { dict: { ShadingType: 3, Coords: [center.x, center.y, 0, center.x, center.y, r], Extend: extend } };
|
|
341
|
+
}
|
|
342
|
+
const reach = Math.max(
|
|
343
|
+
...[box.x0, box.x1].flatMap((x) => [box.y0, box.y1].map((y) => Math.hypot(x - center.x, y - center.y)))
|
|
344
|
+
);
|
|
345
|
+
return conicMesh(center, angle, reach * 1.01 + 1);
|
|
346
|
+
}
|
|
347
|
+
function conicMesh(center, angle, r) {
|
|
348
|
+
const x0 = center.x - r;
|
|
349
|
+
const y0 = center.y - r;
|
|
350
|
+
const data = new Uint8Array(CONIC_WEDGES * 3 * 11);
|
|
351
|
+
const view = new DataView(data.buffer);
|
|
352
|
+
const coord = (v, lo) => Math.round(Math.min(1, Math.max(0, (v - lo) / (2 * r))) * 4294967295);
|
|
353
|
+
let at = 0;
|
|
354
|
+
const vertex = (x, y, t) => {
|
|
355
|
+
view.setUint8(at, 0);
|
|
356
|
+
view.setUint32(at + 1, coord(x, x0));
|
|
357
|
+
view.setUint32(at + 5, coord(y, y0));
|
|
358
|
+
view.setUint16(at + 9, Math.round(t * 65535));
|
|
359
|
+
at += 11;
|
|
360
|
+
};
|
|
361
|
+
const rim = (i) => {
|
|
362
|
+
const a = angle + 2 * Math.PI * i / CONIC_WEDGES;
|
|
363
|
+
vertex(center.x + r * Math.cos(a), center.y + r * Math.sin(a), i / CONIC_WEDGES);
|
|
364
|
+
};
|
|
365
|
+
for (let i = 0; i < CONIC_WEDGES; i++) {
|
|
366
|
+
vertex(center.x, center.y, (i + 0.5) / CONIC_WEDGES);
|
|
367
|
+
rim(i);
|
|
368
|
+
rim(i + 1);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
dict: {
|
|
372
|
+
ShadingType: 4,
|
|
373
|
+
BitsPerCoordinate: 32,
|
|
374
|
+
BitsPerComponent: 16,
|
|
375
|
+
BitsPerFlag: 8,
|
|
376
|
+
Decode: [x0, x0 + 2 * r, y0, y0 + 2 * r, 0, 1]
|
|
377
|
+
},
|
|
378
|
+
data
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function writeShading(ctx, g, fn, colorSpace) {
|
|
382
|
+
const ref = ctx.objects.alloc();
|
|
383
|
+
const dict = { ...g.dict, ColorSpace: name(colorSpace), Function: fn };
|
|
384
|
+
if (g.data) ctx.objects.streamDeflated(ref, dict, g.data);
|
|
385
|
+
else ctx.objects.set(ref, dict);
|
|
386
|
+
return ref;
|
|
387
|
+
}
|
|
388
|
+
function writeMaskGroup(ctx, mask, box) {
|
|
389
|
+
const ref = ctx.objects.alloc();
|
|
390
|
+
const body = latin1Bytes("/Sh1 sh\n");
|
|
391
|
+
ctx.objects.streamDeflated(
|
|
392
|
+
ref,
|
|
393
|
+
{
|
|
394
|
+
Type: name("XObject"),
|
|
395
|
+
Subtype: name("Form"),
|
|
396
|
+
BBox: [box.x0, box.y0, box.x1, box.y1],
|
|
397
|
+
Group: { Type: name("Group"), S: name("Transparency"), CS: name("DeviceGray"), I: true },
|
|
398
|
+
Resources: { Shading: { Sh1: mask } }
|
|
399
|
+
},
|
|
400
|
+
body
|
|
401
|
+
);
|
|
402
|
+
return ref;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/images.ts
|
|
406
|
+
var ids = /* @__PURE__ */ new WeakMap();
|
|
407
|
+
var embedded = /* @__PURE__ */ new WeakMap();
|
|
408
|
+
var nextId = 0;
|
|
409
|
+
function sizeOf(image) {
|
|
410
|
+
const w = "naturalWidth" in image ? image.naturalWidth : image.width;
|
|
411
|
+
const h = "naturalHeight" in image ? image.naturalHeight : image.height;
|
|
412
|
+
return { w: w || 1, h: h || 1 };
|
|
413
|
+
}
|
|
414
|
+
function defaultReadPixels(image) {
|
|
415
|
+
const { w, h } = sizeOf(image);
|
|
416
|
+
const ctx = new OffscreenCanvas(w, h).getContext("2d");
|
|
417
|
+
if (!ctx) throw new Error("pdf: no 2D context to read image pixels with");
|
|
418
|
+
ctx.drawImage(image, 0, 0);
|
|
419
|
+
return { width: w, height: h, data: ctx.getImageData(0, 0, w, h).data };
|
|
420
|
+
}
|
|
421
|
+
function memo(ctx) {
|
|
422
|
+
let m = embedded.get(ctx.objects);
|
|
423
|
+
if (!m) embedded.set(ctx.objects, m = /* @__PURE__ */ new Map());
|
|
424
|
+
return m;
|
|
425
|
+
}
|
|
426
|
+
function idOf(image) {
|
|
427
|
+
let id = ids.get(image);
|
|
428
|
+
if (id === void 0) ids.set(image, id = ++nextId);
|
|
429
|
+
return id;
|
|
430
|
+
}
|
|
431
|
+
function imageObject(ctx, dict, data) {
|
|
432
|
+
const ref = ctx.objects.alloc();
|
|
433
|
+
ctx.objects.streamDeflated(ref, { Type: name("XObject"), Subtype: name("Image"), ...dict }, data);
|
|
434
|
+
return ref;
|
|
435
|
+
}
|
|
436
|
+
function imageXObject(ctx, image, state = { transform: mat3.identity(), alpha: 1, colorMatrix: null }, sampling = "linear") {
|
|
437
|
+
const interpolate = sampling !== "nearest";
|
|
438
|
+
const key = `${idOf(image)} ${interpolate} ${state.colorMatrix?.join(",") ?? ""}`;
|
|
439
|
+
const cache = memo(ctx);
|
|
440
|
+
const hit = cache.get(key);
|
|
441
|
+
if (hit) return hit;
|
|
442
|
+
let raw;
|
|
443
|
+
try {
|
|
444
|
+
raw = (ctx.opts.readPixels ?? defaultReadPixels)(image);
|
|
445
|
+
} catch {
|
|
446
|
+
skip(ctx, "image could not be read");
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const ref = pixelsXObject(ctx, { ...raw, data: foldPixels(raw.data, state) }, interpolate);
|
|
450
|
+
cache.set(key, ref);
|
|
451
|
+
return ref;
|
|
452
|
+
}
|
|
453
|
+
function pixelsXObject(ctx, raw, interpolate) {
|
|
454
|
+
const rgba = raw.data;
|
|
455
|
+
const n = raw.width * raw.height;
|
|
456
|
+
const rgb = new Uint8Array(n * 3);
|
|
457
|
+
const alpha = new Uint8Array(n);
|
|
458
|
+
let translucent = false;
|
|
459
|
+
for (let i = 0; i < n; i++) {
|
|
460
|
+
rgb[i * 3] = rgba[i * 4];
|
|
461
|
+
rgb[i * 3 + 1] = rgba[i * 4 + 1];
|
|
462
|
+
rgb[i * 3 + 2] = rgba[i * 4 + 2];
|
|
463
|
+
alpha[i] = rgba[i * 4 + 3];
|
|
464
|
+
if (alpha[i] < 255) translucent = true;
|
|
465
|
+
}
|
|
466
|
+
const dims = (space) => ({
|
|
467
|
+
Width: raw.width,
|
|
468
|
+
Height: raw.height,
|
|
469
|
+
ColorSpace: name(space),
|
|
470
|
+
BitsPerComponent: 8,
|
|
471
|
+
Interpolate: interpolate
|
|
472
|
+
});
|
|
473
|
+
const mask = translucent ? imageObject(ctx, dims("DeviceGray"), alpha) : void 0;
|
|
474
|
+
return imageObject(ctx, { ...dims("DeviceRGB"), SMask: mask }, rgb);
|
|
475
|
+
}
|
|
476
|
+
function writeRaster(ctx, raw, rect, opacity) {
|
|
477
|
+
place(ctx, pixelsXObject(ctx, raw, true), raw.width, raw.height, {
|
|
478
|
+
x: rect.x,
|
|
479
|
+
y: rect.y,
|
|
480
|
+
w: rect.width,
|
|
481
|
+
h: rect.height,
|
|
482
|
+
opacity
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function place(ctx, ref, iw, ih, p) {
|
|
486
|
+
const src = p.source ?? { x: 0, y: 0, w: iw, h: ih };
|
|
487
|
+
if (!(src.w > 0 && src.h > 0 && p.w !== 0 && p.h !== 0)) return;
|
|
488
|
+
const { out } = ctx;
|
|
489
|
+
const W = iw * p.w / src.w;
|
|
490
|
+
const H = ih * p.h / src.h;
|
|
491
|
+
const left = p.x - src.x * p.w / src.w;
|
|
492
|
+
const top = p.y - src.y * p.h / src.h;
|
|
493
|
+
const right = p.x + p.w + src.x * p.w / src.w;
|
|
494
|
+
const bottom = p.y + p.h + src.y * p.h / src.h;
|
|
495
|
+
const a = p.flipX ? -W : W;
|
|
496
|
+
const e = p.flipX ? right : left;
|
|
497
|
+
const d = p.flipY ? H : -H;
|
|
498
|
+
const f = p.flipY ? bottom - H : top + H;
|
|
499
|
+
out.op("q");
|
|
500
|
+
if (p.opacity < 1) out.op("gs", `/${ctx.resources.extGState(p.opacity, p.opacity)}`);
|
|
501
|
+
if (p.source) out.op("re", p.x, p.y, p.w, p.h).op("W").op("n");
|
|
502
|
+
out.op("cm", a, 0, 0, d, e, f);
|
|
503
|
+
out.op("Do", `/${ctx.resources.xobject(ref)}`);
|
|
504
|
+
out.op("Q");
|
|
505
|
+
}
|
|
506
|
+
function writeImage(ctx, cmd, state) {
|
|
507
|
+
const { w: iw, h: ih } = sizeOf(cmd.image);
|
|
508
|
+
const ref = imageXObject(ctx, cmd.image, state, cmd.sampling);
|
|
509
|
+
if (!ref) return;
|
|
510
|
+
if (cmd.kind === "image") {
|
|
511
|
+
place(ctx, ref, iw, ih, { ...cmd, opacity: (cmd.opacity ?? 1) * state.alpha });
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const s = cmd.sprites;
|
|
515
|
+
for (let i = 0; i + SPRITE_STRIDE <= s.length; i += SPRITE_STRIDE) {
|
|
516
|
+
const [dx, dy, dw, dh, sx, sy, sw, sh, opacity] = s.subarray(i, i + SPRITE_STRIDE);
|
|
517
|
+
place(ctx, ref, iw, ih, {
|
|
518
|
+
x: dx,
|
|
519
|
+
y: dy,
|
|
520
|
+
w: dw,
|
|
521
|
+
h: dh,
|
|
522
|
+
source: { x: sw < 0 ? sx + sw : sx, y: sh < 0 ? sy + sh : sy, w: Math.abs(sw), h: Math.abs(sh) },
|
|
523
|
+
flipX: sw < 0,
|
|
524
|
+
flipY: sh < 0,
|
|
525
|
+
opacity: opacity * state.alpha
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function texturePattern(ctx, paint, state) {
|
|
530
|
+
const entry = textureRegistry.get(paint.texture.id);
|
|
531
|
+
if (!entry) return null;
|
|
532
|
+
const toFrame = paintToFrame(ctx, paint, state);
|
|
533
|
+
if (!toFrame) return null;
|
|
534
|
+
const [a, b, c, d] = paint.transform ?? [1, 0, 0, 1];
|
|
535
|
+
const o = paint.origin ?? { x: 0, y: 0 };
|
|
536
|
+
const placement = mat3.fromAffine([a, b, c, d, o.x, o.y]);
|
|
537
|
+
if (!mat3.invert(placement)) return null;
|
|
538
|
+
const m = mat3.multiply(mat3.multiply(pageFlip(ctx), mat3.multiply(Float64Array.from(state.transform), toFrame)), placement);
|
|
539
|
+
const matrix = mat3.toAffine(m);
|
|
540
|
+
const key = `pattern ${paint.texture.id} ${matrix.join(",")} ${state.colorMatrix?.join(",") ?? ""}`;
|
|
541
|
+
const cache = memo(ctx);
|
|
542
|
+
let ref = cache.get(key);
|
|
543
|
+
if (!ref) {
|
|
544
|
+
const { w, h } = sizeOf(entry.source);
|
|
545
|
+
const image = imageXObject(ctx, entry.source, state, "linear");
|
|
546
|
+
if (!image) return null;
|
|
547
|
+
ref = ctx.objects.alloc();
|
|
548
|
+
ctx.objects.streamDeflated(
|
|
549
|
+
ref,
|
|
550
|
+
{
|
|
551
|
+
Type: name("Pattern"),
|
|
552
|
+
PatternType: 1,
|
|
553
|
+
PaintType: 1,
|
|
554
|
+
TilingType: 1,
|
|
555
|
+
BBox: [0, 0, w, h],
|
|
556
|
+
XStep: w,
|
|
557
|
+
YStep: h,
|
|
558
|
+
Matrix: matrix,
|
|
559
|
+
Resources: { XObject: { Im1: image } }
|
|
560
|
+
},
|
|
561
|
+
latin1Bytes(`${w} 0 0 -${h} 0 ${h} cm
|
|
562
|
+
/Im1 Do
|
|
563
|
+
`)
|
|
564
|
+
);
|
|
565
|
+
cache.set(key, ref);
|
|
566
|
+
}
|
|
567
|
+
return ctx.resources.pattern(ref);
|
|
568
|
+
}
|
|
569
|
+
function pathOps(out, path, which) {
|
|
570
|
+
if (path.kind === "rect") {
|
|
571
|
+
out.op("re", path.x, path.y, path.width, path.height);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const { commands, coords } = resolveCorners(path);
|
|
575
|
+
const contours = [];
|
|
576
|
+
let cur;
|
|
577
|
+
forEachSegment(commands, coords, (cmd, ci, px, py, _i, opens) => {
|
|
578
|
+
if (cmd === PATH_M || opens || !cur) {
|
|
579
|
+
cur = { ops: [], closed: false };
|
|
580
|
+
contours.push(cur);
|
|
581
|
+
if (cmd === PATH_M) {
|
|
582
|
+
cur.ops.push(["m", [coords[ci], coords[ci + 1]]]);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
cur.ops.push(["m", [px, py]]);
|
|
586
|
+
}
|
|
587
|
+
if (cmd === PATH_L) cur.ops.push(["l", [coords[ci], coords[ci + 1]]]);
|
|
588
|
+
else if (cmd === PATH_C) cur.ops.push(["c", Array.from({ length: 6 }, (_, k) => coords[ci + k])]);
|
|
589
|
+
else if (cmd === PATH_Q) {
|
|
590
|
+
const [qx, qy, x, y] = [coords[ci], coords[ci + 1], coords[ci + 2], coords[ci + 3]];
|
|
591
|
+
cur.ops.push(["c", [px + (qx - px) * 2 / 3, py + (qy - py) * 2 / 3, x + (qx - x) * 2 / 3, y + (qy - y) * 2 / 3, x, y]]);
|
|
592
|
+
} else if (cmd === PATH_Z) {
|
|
593
|
+
cur.ops.push(["h", []]);
|
|
594
|
+
cur.closed = true;
|
|
595
|
+
cur = void 0;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
for (const c of contours) {
|
|
599
|
+
if (which === "closed" && !c.closed) continue;
|
|
600
|
+
for (const [op, args] of c.ops) out.op(op, ...args);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function fillPathOps(out, path) {
|
|
604
|
+
const closeOpen = path.kind === "rect" || fillsOpenSubpaths(path);
|
|
605
|
+
pathOps(out, path, closeOpen ? "all" : "closed");
|
|
606
|
+
return path.kind === "polygon" && path.fillRule === "evenodd";
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/resources.ts
|
|
610
|
+
var PageResources = class {
|
|
611
|
+
gstates = /* @__PURE__ */ new Map();
|
|
612
|
+
masks = /* @__PURE__ */ new Map();
|
|
613
|
+
refs = {
|
|
614
|
+
Shading: /* @__PURE__ */ new Map(),
|
|
615
|
+
XObject: /* @__PURE__ */ new Map(),
|
|
616
|
+
Font: /* @__PURE__ */ new Map(),
|
|
617
|
+
Pattern: /* @__PURE__ */ new Map()
|
|
618
|
+
};
|
|
619
|
+
extGState(fillAlpha, strokeAlpha) {
|
|
620
|
+
num(fillAlpha);
|
|
621
|
+
num(strokeAlpha);
|
|
622
|
+
const key = `${fillAlpha} ${strokeAlpha}`;
|
|
623
|
+
let n = this.gstates.get(key);
|
|
624
|
+
if (n === void 0) {
|
|
625
|
+
n = this.nextGState();
|
|
626
|
+
this.gstates.set(key, n);
|
|
627
|
+
}
|
|
628
|
+
return n;
|
|
629
|
+
}
|
|
630
|
+
/** An ExtGState whose `/SMask` is the luminosity of the transparency group `group`. */
|
|
631
|
+
softMask(group) {
|
|
632
|
+
let n = this.masks.get(group.id);
|
|
633
|
+
if (n === void 0) {
|
|
634
|
+
n = this.nextGState();
|
|
635
|
+
this.masks.set(group.id, n);
|
|
636
|
+
}
|
|
637
|
+
return n;
|
|
638
|
+
}
|
|
639
|
+
nextGState() {
|
|
640
|
+
return `GS${this.gstates.size + this.masks.size + 1}`;
|
|
641
|
+
}
|
|
642
|
+
shading(ref) {
|
|
643
|
+
return this.named("Shading", "Sh", ref);
|
|
644
|
+
}
|
|
645
|
+
xobject(ref) {
|
|
646
|
+
return this.named("XObject", "Im", ref);
|
|
647
|
+
}
|
|
648
|
+
font(ref) {
|
|
649
|
+
return this.named("Font", "F", ref);
|
|
650
|
+
}
|
|
651
|
+
pattern(ref) {
|
|
652
|
+
return this.named("Pattern", "P", ref);
|
|
653
|
+
}
|
|
654
|
+
named(kind, prefix, ref) {
|
|
655
|
+
const m = this.refs[kind];
|
|
656
|
+
let n = m.get(ref.id);
|
|
657
|
+
if (n === void 0) {
|
|
658
|
+
n = `${prefix}${m.size + 1}`;
|
|
659
|
+
m.set(ref.id, n);
|
|
660
|
+
}
|
|
661
|
+
return n;
|
|
662
|
+
}
|
|
663
|
+
dict() {
|
|
664
|
+
const out = {};
|
|
665
|
+
if (this.gstates.size || this.masks.size) {
|
|
666
|
+
const g = {};
|
|
667
|
+
for (const [key, n] of this.gstates) {
|
|
668
|
+
const [ca, CA] = key.split(" ").map(Number);
|
|
669
|
+
g[n] = { Type: name("ExtGState"), ca, CA };
|
|
670
|
+
}
|
|
671
|
+
for (const [id, n] of this.masks) {
|
|
672
|
+
g[n] = { Type: name("ExtGState"), SMask: { Type: name("Mask"), S: name("Luminosity"), G: new PdfRef(id) } };
|
|
673
|
+
}
|
|
674
|
+
out.ExtGState = g;
|
|
675
|
+
}
|
|
676
|
+
for (const [kind, m] of Object.entries(this.refs)) {
|
|
677
|
+
if (!m.size) continue;
|
|
678
|
+
const d = {};
|
|
679
|
+
for (const [id, n] of m) d[n] = new PdfRef(id);
|
|
680
|
+
out[kind] = d;
|
|
681
|
+
}
|
|
682
|
+
return out;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// src/groupOps.ts
|
|
687
|
+
var RASTER_SCALE = 2;
|
|
688
|
+
function setClip(ctx, clip) {
|
|
689
|
+
ctx.out.op(fillPathOps(ctx.out, clip) ? "W*" : "W");
|
|
690
|
+
ctx.out.op("n");
|
|
691
|
+
}
|
|
692
|
+
function compositeName(op) {
|
|
693
|
+
return typeof op === "string" ? op : `${op.source}/${op.destination}`;
|
|
694
|
+
}
|
|
695
|
+
async function drawRaster(ctx, commands, box, state, reason, unhooked = reason) {
|
|
696
|
+
const hook = ctx.opts.rasterize;
|
|
697
|
+
if (!hook) return skip(ctx, unhooked);
|
|
698
|
+
if (box.width <= 0 || box.height <= 0) return;
|
|
699
|
+
let px;
|
|
700
|
+
try {
|
|
701
|
+
px = await hook(commands, box, RASTER_SCALE, ctx.view);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
return skip(ctx, `${reason} raster failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
704
|
+
}
|
|
705
|
+
if (px.data.length !== px.width * px.height * 4) return skip(ctx, `${reason} raster has the wrong number of pixels`);
|
|
706
|
+
const inverse = mat3.invert(state.transform);
|
|
707
|
+
if (!inverse) return;
|
|
708
|
+
const { out } = ctx;
|
|
709
|
+
out.op("q");
|
|
710
|
+
out.op("cm", ...mat3.toAffine(inverse));
|
|
711
|
+
writeRaster(ctx, { ...px, data: foldPixels(px.data, state) }, box, state.alpha);
|
|
712
|
+
out.op("Q");
|
|
713
|
+
}
|
|
714
|
+
function writeShader(ctx, cmd, state) {
|
|
715
|
+
const { x, y, w, h } = cmd.bounds;
|
|
716
|
+
const x0 = Math.max(0, Math.floor(x));
|
|
717
|
+
const y0 = Math.max(0, Math.floor(y));
|
|
718
|
+
const x1 = Math.min(ctx.width, Math.ceil(x + w));
|
|
719
|
+
const y1 = Math.min(ctx.height, Math.ceil(y + h));
|
|
720
|
+
return drawRaster(ctx, [cmd], { x: x0, y: y0, width: x1 - x0, height: y1 - y0 }, state, "shader");
|
|
721
|
+
}
|
|
722
|
+
function rasterFill(ctx, cmd, state, kind) {
|
|
723
|
+
const wrapper = {
|
|
724
|
+
kind: "group",
|
|
725
|
+
transform: state.transform,
|
|
726
|
+
children: [{ kind: "path", path: cmd.path, fill: cmd.fill }]
|
|
727
|
+
};
|
|
728
|
+
const box = commandsBounds([wrapper], mat3.identity(), ctx);
|
|
729
|
+
return drawRaster(ctx, [wrapper], box, state, `paint kind ${kind}`, `paint kind ${kind} needs a raster`);
|
|
730
|
+
}
|
|
731
|
+
async function rasterGroup(ctx, cmd, state, reason) {
|
|
732
|
+
const full = cmd.transform ? mat3.multiply(Float64Array.from(state.transform), cmd.transform) : state.transform;
|
|
733
|
+
const wrapper = {
|
|
734
|
+
kind: "group",
|
|
735
|
+
transform: full,
|
|
736
|
+
clip: cmd.clip,
|
|
737
|
+
effects: cmd.effects,
|
|
738
|
+
children: cmd.children
|
|
739
|
+
};
|
|
740
|
+
const box = commandsBounds([wrapper], mat3.identity(), ctx);
|
|
741
|
+
const grouped = {
|
|
742
|
+
transform: state.transform,
|
|
743
|
+
alpha: state.alpha * (cmd.alpha ?? 1),
|
|
744
|
+
colorMatrix: composeMatrix(state.colorMatrix, cmd.colorMatrix)
|
|
745
|
+
};
|
|
746
|
+
await drawRaster(ctx, [wrapper], box, grouped, reason);
|
|
747
|
+
}
|
|
748
|
+
async function isolatedGroup(ctx, cmd, state, inner) {
|
|
749
|
+
if (cmd.colorMatrix?.[19]) skip(ctx, "group color matrix alpha offset is not drawn");
|
|
750
|
+
const flip = pageFlip(ctx);
|
|
751
|
+
const toPage = mat3.multiply(flip, Float64Array.from(state.transform));
|
|
752
|
+
const fromPage = mat3.invert(toPage);
|
|
753
|
+
if (!fromPage) return;
|
|
754
|
+
const sub = { ...ctx, out: new ContentStream(), resources: new PageResources() };
|
|
755
|
+
sub.out.op("cm", ...mat3.toAffine(mat3.multiply(flip, Float64Array.from(inner.transform))));
|
|
756
|
+
if (cmd.clip) setClip(sub, cmd.clip);
|
|
757
|
+
await writeCommands(sub, cmd.children, { ...inner, alpha: 1 });
|
|
758
|
+
const form = ctx.objects.alloc();
|
|
759
|
+
ctx.objects.streamDeflated(
|
|
760
|
+
form,
|
|
761
|
+
{
|
|
762
|
+
Type: name("XObject"),
|
|
763
|
+
Subtype: name("Form"),
|
|
764
|
+
BBox: [0, 0, ctx.width, ctx.height],
|
|
765
|
+
Group: { Type: name("Group"), S: name("Transparency"), I: true },
|
|
766
|
+
Resources: sub.resources.dict()
|
|
767
|
+
},
|
|
768
|
+
sub.out.bytes()
|
|
769
|
+
);
|
|
770
|
+
const { out } = ctx;
|
|
771
|
+
out.op("q");
|
|
772
|
+
out.op("cm", ...mat3.toAffine(fromPage));
|
|
773
|
+
if (inner.alpha < 1) out.op("gs", `/${ctx.resources.extGState(inner.alpha, inner.alpha)}`);
|
|
774
|
+
out.op("Do", `/${ctx.resources.xobject(form)}`);
|
|
775
|
+
out.op("Q");
|
|
776
|
+
}
|
|
777
|
+
async function writeGroup(ctx, cmd, state) {
|
|
778
|
+
if (cmd.composite && isOwnComposite(cmd.composite)) {
|
|
779
|
+
skip(ctx, `composite ${compositeName(cmd.composite)} drawn as source-over`);
|
|
780
|
+
}
|
|
781
|
+
if (cmd.effects?.length) return rasterGroup(ctx, cmd, state, "effects");
|
|
782
|
+
const m = cmd.transform;
|
|
783
|
+
const inner = {
|
|
784
|
+
transform: m ? mat3.multiply(Float64Array.from(state.transform), m) : state.transform,
|
|
785
|
+
alpha: state.alpha * (cmd.alpha ?? 1),
|
|
786
|
+
colorMatrix: composeMatrix(state.colorMatrix, cmd.colorMatrix)
|
|
787
|
+
};
|
|
788
|
+
if (cmd.isolate) return isolatedGroup(ctx, cmd, state, inner);
|
|
789
|
+
const { out } = ctx;
|
|
790
|
+
out.op("q");
|
|
791
|
+
if (m) out.op("cm", ...mat3.toAffine(m));
|
|
792
|
+
if (cmd.clip) setClip(ctx, cmd.clip);
|
|
793
|
+
await writeCommands(ctx, cmd.children, inner);
|
|
794
|
+
out.op("Q");
|
|
795
|
+
}
|
|
796
|
+
var CAPS = { butt: 0, round: 1, square: 2 };
|
|
797
|
+
var JOINS = { miter: 0, round: 1, bevel: 2 };
|
|
798
|
+
function isNative(stroke, width) {
|
|
799
|
+
if (stroke.align && stroke.align !== "center") return false;
|
|
800
|
+
if (stroke.vertexWidths || stroke.nib || stroke.vertexColors) return false;
|
|
801
|
+
const { start, end } = strokeInsets(stroke, width);
|
|
802
|
+
return start === 0 && end === 0;
|
|
803
|
+
}
|
|
804
|
+
function strokeStyleOps(ctx, stroke, width) {
|
|
805
|
+
const { out } = ctx;
|
|
806
|
+
out.op("w", width);
|
|
807
|
+
out.op("J", CAPS[stroke.cap ?? "butt"]);
|
|
808
|
+
out.op("j", JOINS[stroke.join ?? "miter"]);
|
|
809
|
+
out.op("M", stroke.miterLimit ?? 4);
|
|
810
|
+
const dash = stroke.dash ?? [];
|
|
811
|
+
if (dash.reduce((a, b) => a + b, 0) > 0) out.op("d", `[${dash.map(num).join(" ")}]`, 0);
|
|
812
|
+
}
|
|
813
|
+
function resolveStroke(path, raw, state) {
|
|
814
|
+
const { cmd } = resolveStrokeLengths(state.transform, { kind: "path", path, stroke: raw }, { quantize: false });
|
|
815
|
+
return { stroke: cmd.stroke, width: typeof cmd.stroke.width === "number" ? cmd.stroke.width : 1 };
|
|
816
|
+
}
|
|
817
|
+
function writeNative(ctx, path, stroke, state, width) {
|
|
818
|
+
const paint = stroke.paint;
|
|
819
|
+
if (!paint || !isSolidPaint(paint)) return;
|
|
820
|
+
const { rgb, alpha } = solidColor(paint, state);
|
|
821
|
+
const { out } = ctx;
|
|
822
|
+
out.op("q");
|
|
823
|
+
out.op("gs", `/${ctx.resources.extGState(alpha, alpha)}`);
|
|
824
|
+
out.op("RG", ...rgb);
|
|
825
|
+
strokeStyleOps(ctx, stroke, width);
|
|
826
|
+
pathOps(out, path, "all");
|
|
827
|
+
out.op("S");
|
|
828
|
+
out.op("Q");
|
|
829
|
+
}
|
|
830
|
+
async function writeStroke(ctx, path, rawStroke, state) {
|
|
831
|
+
if (!rawStroke.paint) return;
|
|
832
|
+
const { stroke, width } = resolveStroke(path, { ...rawStroke, paint: rawStroke.paint }, state);
|
|
833
|
+
if (!(width > 0)) return;
|
|
834
|
+
let heads = stroke.markerStart || stroke.markerMid || stroke.markerEnd ? markerDrawCommands(path, stroke, width, void 0) : [];
|
|
835
|
+
if (isNative(stroke, width) && isSolidPaint(stroke.paint)) writeNative(ctx, path, stroke, state, width);
|
|
836
|
+
else {
|
|
837
|
+
if (stroke.vertexColors) skip(ctx, "stroke vertexColors approximated by a single paint");
|
|
838
|
+
heads = heads.filter((h) => h.stroke || h.fill?.paint !== stroke.paint);
|
|
839
|
+
const outline = strokeToPath(path, stroke);
|
|
840
|
+
await writeCommands(ctx, [{ kind: "path", path: outline, fill: { paint: stroke.paint } }], state);
|
|
841
|
+
}
|
|
842
|
+
if (heads.length) await writeCommands(ctx, heads, state);
|
|
843
|
+
}
|
|
844
|
+
function isSpace(cp) {
|
|
845
|
+
return cp === 32 || cp === 160;
|
|
846
|
+
}
|
|
847
|
+
function codepointLabel(cp) {
|
|
848
|
+
return `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
849
|
+
}
|
|
850
|
+
function skipGlyph(ctx, g, why) {
|
|
851
|
+
const { family, weight, style } = g.resolved.resolved;
|
|
852
|
+
ctx.skipped.push({ kind: "glyph", detail: `${codepointLabel(g.cp)} in ${family} ${weight} ${style} ${why}` });
|
|
853
|
+
}
|
|
854
|
+
function placedOutline(ctx, g, dx, dy) {
|
|
855
|
+
const { family, weight, style } = g.resolved.resolved;
|
|
856
|
+
let d;
|
|
857
|
+
try {
|
|
858
|
+
d = ctx.opts.fonts.outline(family, weight, style, g.cp);
|
|
859
|
+
} catch {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
if (d === null) return null;
|
|
863
|
+
const grown = emboldenGlyphD(d, g.resolved)?.d ?? d;
|
|
864
|
+
const s = g.fontSize;
|
|
865
|
+
const shear = g.resolved.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;
|
|
866
|
+
return transformPath(pathFromD(grown), [s, 0, -shear * s, s, g.x + dx, g.baselineY + dy]);
|
|
867
|
+
}
|
|
868
|
+
function outlineCommand(ctx, g, cmd, dx, dy, paint = "all") {
|
|
869
|
+
const path = placedOutline(ctx, g, dx, dy);
|
|
870
|
+
if (!path) return null;
|
|
871
|
+
const run = cmd.runs[g.run];
|
|
872
|
+
const out = { kind: "path", path };
|
|
873
|
+
if (paint !== "stroke" && fillHasInk(run.fill)) out.fill = run.fill;
|
|
874
|
+
if (strokeHasInk(run.stroke)) out.stroke = run.stroke;
|
|
875
|
+
return out;
|
|
876
|
+
}
|
|
877
|
+
var FILL = 0;
|
|
878
|
+
var STROKE = 1;
|
|
879
|
+
var FILL_STROKE = 2;
|
|
880
|
+
var INVISIBLE = 3;
|
|
881
|
+
var CLIP = 7;
|
|
882
|
+
var JOINS2 = { miter: 0, round: 1 };
|
|
883
|
+
var BOLD_MITER_LIMIT = 4;
|
|
884
|
+
function inkBox(seg, dx, dy) {
|
|
885
|
+
const { fontSize: s, baselineY } = seg.glyphs[0].g;
|
|
886
|
+
let x0 = Infinity;
|
|
887
|
+
let x1 = -Infinity;
|
|
888
|
+
for (const { g } of seg.glyphs) {
|
|
889
|
+
x0 = Math.min(x0, g.x);
|
|
890
|
+
x1 = Math.max(x1, g.x + g.advance);
|
|
891
|
+
}
|
|
892
|
+
return { kind: "rect", x: x0 + dx - s, y: baselineY + dy - 1.5 * s, width: x1 - x0 + 2 * s, height: 2.5 * s };
|
|
893
|
+
}
|
|
894
|
+
function textObject(ctx, seg, mode, dx, dy) {
|
|
895
|
+
const { out } = ctx;
|
|
896
|
+
const first = seg.glyphs[0].g;
|
|
897
|
+
const s = first.fontSize;
|
|
898
|
+
const shear = first.resolved.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;
|
|
899
|
+
out.op("BT");
|
|
900
|
+
out.op("Tf", `/${ctx.resources.font(seg.font.ref)}`, 1);
|
|
901
|
+
if (mode !== FILL) out.op("Tr", mode);
|
|
902
|
+
out.op("Tm", s, 0, s * shear, -s, first.x + dx, first.baselineY + dy);
|
|
903
|
+
const parts = [];
|
|
904
|
+
let hex = "";
|
|
905
|
+
let pen = 0;
|
|
906
|
+
for (const { g, gid } of seg.glyphs) {
|
|
907
|
+
const adjust = num((pen - (g.x - first.x) / s) * 1e3);
|
|
908
|
+
if (adjust !== "0") {
|
|
909
|
+
if (hex) parts.push(`<${hex}>`);
|
|
910
|
+
hex = "";
|
|
911
|
+
parts.push(adjust);
|
|
912
|
+
pen -= Number(adjust) / 1e3;
|
|
913
|
+
}
|
|
914
|
+
hex += gid.toString(16).toUpperCase().padStart(4, "0");
|
|
915
|
+
pen += seg.font.widthOf(gid) / 1e3;
|
|
916
|
+
}
|
|
917
|
+
parts.push(`<${hex}>`);
|
|
918
|
+
out.op("TJ", `[${parts.join(" ")}]`);
|
|
919
|
+
out.op("ET");
|
|
920
|
+
}
|
|
921
|
+
async function paintOutlines(ctx, seg, cmd, dx, dy, state, paint) {
|
|
922
|
+
for (const { g } of seg.glyphs) {
|
|
923
|
+
if (isSpace(g.cp)) continue;
|
|
924
|
+
const outline = outlineCommand(ctx, g, cmd, dx, dy, paint);
|
|
925
|
+
if (outline) await writeCommands(ctx, [outline], state);
|
|
926
|
+
else skipGlyph(ctx, g, "has no outline to paint its ink with");
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async function writeOutlinedInk(ctx, seg, cmd, dx, dy, state) {
|
|
930
|
+
ctx.out.op("q");
|
|
931
|
+
textObject(ctx, seg, INVISIBLE, dx, dy);
|
|
932
|
+
ctx.out.op("Q");
|
|
933
|
+
await paintOutlines(ctx, seg, cmd, dx, dy, state, "all");
|
|
934
|
+
}
|
|
935
|
+
async function writeSegment(ctx, seg, cmd, dx, dy, state) {
|
|
936
|
+
const first = seg.glyphs[0].g;
|
|
937
|
+
const run = cmd.runs[first.run];
|
|
938
|
+
const fill = fillHasInk(run.fill) ? run.fill.paint : void 0;
|
|
939
|
+
const box = inkBox(seg, dx, dy);
|
|
940
|
+
const rawStroke = strokeHasInk(run.stroke) ? run.stroke : void 0;
|
|
941
|
+
const stroke = rawStroke && resolveStroke(box, rawStroke, state);
|
|
942
|
+
const nativeStroke = stroke && isNative(stroke.stroke, stroke.width) && isSolidPaint(stroke.stroke.paint);
|
|
943
|
+
const solidFill = !fill || isSolidPaint(fill);
|
|
944
|
+
const bold = syntheticBoldOffset(first.resolved);
|
|
945
|
+
const fillColor = fill && isSolidPaint(fill) ? solidColor(fill, state) : void 0;
|
|
946
|
+
const boldInText = bold && !stroke && solidFill && fillColor.alpha >= 1;
|
|
947
|
+
if (stroke && !nativeStroke || bold && !boldInText) return writeOutlinedInk(ctx, seg, cmd, dx, dy, state);
|
|
948
|
+
const { out } = ctx;
|
|
949
|
+
if (!solidFill) {
|
|
950
|
+
out.op("q");
|
|
951
|
+
textObject(ctx, seg, CLIP, dx, dy);
|
|
952
|
+
await writeCommands(ctx, [{ kind: "path", path: box, fill: { paint: fill } }], state);
|
|
953
|
+
out.op("Q");
|
|
954
|
+
if (stroke) await paintOutlines(ctx, seg, cmd, dx, dy, state, "stroke");
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const strokeColor = nativeStroke ? solidColor(stroke.stroke.paint, state) : void 0;
|
|
958
|
+
const paintsFill = fillColor;
|
|
959
|
+
if (!paintsFill && !strokeColor) return;
|
|
960
|
+
out.op("q");
|
|
961
|
+
out.op("gs", `/${ctx.resources.extGState(paintsFill ? fillColor.alpha : 1, strokeColor?.alpha ?? fillColor?.alpha ?? 1)}`);
|
|
962
|
+
if (paintsFill) out.op("rg", ...fillColor.rgb);
|
|
963
|
+
if (stroke && strokeColor) {
|
|
964
|
+
out.op("RG", ...strokeColor.rgb);
|
|
965
|
+
strokeStyleOps(ctx, stroke.stroke, stroke.width);
|
|
966
|
+
} else if (bold && fillColor) {
|
|
967
|
+
out.op("RG", ...fillColor.rgb);
|
|
968
|
+
out.op("w", 2 * bold.growth * first.fontSize);
|
|
969
|
+
out.op("j", JOINS2[bold.join]);
|
|
970
|
+
out.op("M", BOLD_MITER_LIMIT);
|
|
971
|
+
}
|
|
972
|
+
textObject(ctx, seg, paintsFill ? strokeColor || bold ? FILL_STROKE : FILL : STROKE, dx, dy);
|
|
973
|
+
out.op("Q");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/textOps.ts
|
|
977
|
+
var BLANK = /^[\s\p{Default_Ignorable_Code_Point}]$/u;
|
|
978
|
+
function joins(seg, g, line) {
|
|
979
|
+
if (!seg || seg.line !== line) return false;
|
|
980
|
+
const a = seg.glyphs[0].g;
|
|
981
|
+
const [ra, rg] = [a.resolved, g.resolved];
|
|
982
|
+
return a.run === g.run && a.fontSize === g.fontSize && a.baselineY === g.baselineY && ra.source === rg.source && ra.resolved.family === rg.resolved.family && ra.resolved.weight === rg.resolved.weight && ra.resolved.style === rg.resolved.style && ra.synthetic.bold === rg.synthetic.bold && ra.synthetic.italic === rg.synthetic.italic;
|
|
983
|
+
}
|
|
984
|
+
async function writeText2(ctx, cmd, state) {
|
|
985
|
+
const style = resolveTextStyle(cmd.style);
|
|
986
|
+
const laid = cachedLayoutRuns(cmd.runs, {
|
|
987
|
+
maxWidth: cmd.maxWidth ?? Infinity,
|
|
988
|
+
alignWidth: cmd.width,
|
|
989
|
+
lineHeight: style.lineHeight,
|
|
990
|
+
align: cmd.align ?? style.align,
|
|
991
|
+
direction: cmd.direction ?? style.direction,
|
|
992
|
+
tabSize: style.tabSize,
|
|
993
|
+
justify: cmd.justify
|
|
994
|
+
});
|
|
995
|
+
const dx = cmd.x;
|
|
996
|
+
const dy = cmd.y + verticalAlignOffset(cmd.verticalAlign, cmd.height, laid.bounds.height);
|
|
997
|
+
const items = [];
|
|
998
|
+
let seg;
|
|
999
|
+
for (const [line, box] of laid.lines.entries()) {
|
|
1000
|
+
for (const g of box.glyphs) {
|
|
1001
|
+
const run = cmd.runs[g.run];
|
|
1002
|
+
if (!fillHasInk(run.fill) && !strokeHasInk(run.stroke)) continue;
|
|
1003
|
+
const space = isSpace(g.cp);
|
|
1004
|
+
if (!space && BLANK.test(String.fromCodePoint(g.cp))) continue;
|
|
1005
|
+
if (!space && (g.glyph?.color || g.resolved.source !== "outline" && g.glyph === null)) {
|
|
1006
|
+
skipGlyph(ctx, g, "has no glyph to draw");
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
const { family, weight, style: fontStyle } = g.resolved.resolved;
|
|
1010
|
+
const font = await ctx.fonts.face(family, weight, fontStyle);
|
|
1011
|
+
const gid = font?.use(g.cp) ?? null;
|
|
1012
|
+
if (font && gid !== null) {
|
|
1013
|
+
if (!joins(seg, g, line)) items.push(seg = { font, line, glyphs: [] });
|
|
1014
|
+
seg.glyphs.push({ g, gid });
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (space) continue;
|
|
1018
|
+
const outline = outlineCommand(ctx, g, cmd, dx, dy);
|
|
1019
|
+
if (outline) items.push(outline);
|
|
1020
|
+
else skipGlyph(ctx, g, "has no glyph to draw");
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
for (const item of items) {
|
|
1024
|
+
if ("kind" in item) await writeCommands(ctx, [item], state);
|
|
1025
|
+
else await writeSegment(ctx, item, cmd, dx, dy, state);
|
|
1026
|
+
}
|
|
1027
|
+
const { decorations } = cachedPaintRuns(laid, cmd.runs, {});
|
|
1028
|
+
const rules = decorations.map((d) => ({
|
|
1029
|
+
kind: "path",
|
|
1030
|
+
path: { kind: "rect", x: d.x0 + dx, y: d.y0 + dy, width: d.x1 - d.x0, height: d.y1 - d.y0 },
|
|
1031
|
+
fill: d.fill
|
|
1032
|
+
}));
|
|
1033
|
+
await writeCommands(ctx, rules, state);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/drawCommands.ts
|
|
1037
|
+
function skip(ctx, detail) {
|
|
1038
|
+
ctx.skipped.push({ kind: "command", detail });
|
|
1039
|
+
}
|
|
1040
|
+
function pageFlip(ctx) {
|
|
1041
|
+
return mat3.fromAffine([1, 0, 0, -1, 0, ctx.height]);
|
|
1042
|
+
}
|
|
1043
|
+
function setAlpha(ctx, fill, stroke) {
|
|
1044
|
+
ctx.out.op("gs", `/${ctx.resources.extGState(fill, stroke)}`);
|
|
1045
|
+
}
|
|
1046
|
+
function writeShadedFill(ctx, cmd, state, paint) {
|
|
1047
|
+
const sh = paintShading(ctx, paint, state, cmd.path);
|
|
1048
|
+
if (!sh) return;
|
|
1049
|
+
const { out } = ctx;
|
|
1050
|
+
out.op("q");
|
|
1051
|
+
out.op(fillPathOps(out, cmd.path) ? "W*" : "W");
|
|
1052
|
+
out.op("n");
|
|
1053
|
+
if (sh.alpha < 1) setAlpha(ctx, sh.alpha, sh.alpha);
|
|
1054
|
+
if (sh.matrix) out.op("cm", ...mat3.toAffine(sh.matrix));
|
|
1055
|
+
if (sh.softMask) out.op("gs", `/${sh.softMask}`);
|
|
1056
|
+
out.op("sh", `/${sh.shading}`);
|
|
1057
|
+
out.op("Q");
|
|
1058
|
+
}
|
|
1059
|
+
function writeTextureFill(ctx, cmd, state, paint) {
|
|
1060
|
+
const pattern = texturePattern(ctx, paint, state);
|
|
1061
|
+
if (!pattern) return skip(ctx, `texture ${paint.texture.id} cannot be drawn`);
|
|
1062
|
+
const alpha = (paint.opacity ?? 1) * state.alpha;
|
|
1063
|
+
const { out } = ctx;
|
|
1064
|
+
out.op("q");
|
|
1065
|
+
setAlpha(ctx, alpha, alpha);
|
|
1066
|
+
out.op("cs", "/Pattern");
|
|
1067
|
+
out.op("scn", `/${pattern}`);
|
|
1068
|
+
out.op(fillPathOps(out, cmd.path) ? "f*" : "f");
|
|
1069
|
+
out.op("Q");
|
|
1070
|
+
}
|
|
1071
|
+
async function writeFill(ctx, cmd, state) {
|
|
1072
|
+
const paint = cmd.fill?.paint;
|
|
1073
|
+
if (!paint) return;
|
|
1074
|
+
if (isGradientPaint(paint)) return writeShadedFill(ctx, cmd, state, paint);
|
|
1075
|
+
if (paint.kind === "texture") return writeTextureFill(ctx, cmd, state, paint);
|
|
1076
|
+
if (!isSolidPaint(paint)) return rasterFill(ctx, cmd, state, paint.kind);
|
|
1077
|
+
if (cmd.vertexColors?.length) skip(ctx, "fill vertexColors approximated by the solid fill");
|
|
1078
|
+
const { rgb, alpha } = solidColor(paint, state);
|
|
1079
|
+
const { out } = ctx;
|
|
1080
|
+
out.op("q");
|
|
1081
|
+
setAlpha(ctx, alpha, alpha);
|
|
1082
|
+
out.op("rg", ...rgb);
|
|
1083
|
+
out.op(fillPathOps(out, cmd.path) ? "f*" : "f");
|
|
1084
|
+
out.op("Q");
|
|
1085
|
+
}
|
|
1086
|
+
async function writePath(ctx, cmd, state) {
|
|
1087
|
+
await writeFill(ctx, cmd, state);
|
|
1088
|
+
if (cmd.stroke) await writeStroke(ctx, cmd.path, cmd.stroke, state);
|
|
1089
|
+
}
|
|
1090
|
+
async function writeCommand(ctx, cmd, state) {
|
|
1091
|
+
if (cmd.kind === "path") await writePath(ctx, cmd, state);
|
|
1092
|
+
else if (cmd.kind === "group") await writeGroup(ctx, cmd, state);
|
|
1093
|
+
else if (cmd.kind === "image" || cmd.kind === "sprites") writeImage(ctx, cmd, state);
|
|
1094
|
+
else if (cmd.kind === "text") await writeText2(ctx, cmd, state);
|
|
1095
|
+
else await writeShader(ctx, cmd, state);
|
|
1096
|
+
}
|
|
1097
|
+
async function writeCommands(ctx, cmds, state) {
|
|
1098
|
+
for (const cmd of cmds) {
|
|
1099
|
+
const line = ctx.out.mark();
|
|
1100
|
+
const object = ctx.objects.mark();
|
|
1101
|
+
const skips = ctx.skipped.length;
|
|
1102
|
+
try {
|
|
1103
|
+
await writeCommand(ctx, cmd, state);
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
if (!(err instanceof NonFiniteNumber)) throw err;
|
|
1106
|
+
ctx.out.rollback(line);
|
|
1107
|
+
ctx.objects.nullUnset(object);
|
|
1108
|
+
ctx.skipped.length = skips;
|
|
1109
|
+
skip(ctx, `${cmd.kind} has a non-finite coordinate`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/fonts/toUnicode.ts
|
|
1115
|
+
var MAX_PER_BLOCK = 100;
|
|
1116
|
+
var hex4 = (n) => n.toString(16).toUpperCase().padStart(4, "0");
|
|
1117
|
+
function utf16Hex(cp) {
|
|
1118
|
+
if (cp <= 65535) return hex4(cp);
|
|
1119
|
+
const v = cp - 65536;
|
|
1120
|
+
return hex4(55296 + (v >> 10)) + hex4(56320 + (v & 1023));
|
|
1121
|
+
}
|
|
1122
|
+
function toUnicodeCMap(glyphs) {
|
|
1123
|
+
const entries = [...glyphs].sort((a, b) => a[0] - b[0]);
|
|
1124
|
+
const lines = [
|
|
1125
|
+
"/CIDInit /ProcSet findresource begin",
|
|
1126
|
+
"12 dict begin",
|
|
1127
|
+
"begincmap",
|
|
1128
|
+
"/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def",
|
|
1129
|
+
"/CMapName /Adobe-Identity-UCS def",
|
|
1130
|
+
"/CMapType 2 def",
|
|
1131
|
+
"1 begincodespacerange",
|
|
1132
|
+
"<0000> <FFFF>",
|
|
1133
|
+
"endcodespacerange"
|
|
1134
|
+
];
|
|
1135
|
+
for (let i = 0; i < entries.length; i += MAX_PER_BLOCK) {
|
|
1136
|
+
const block = entries.slice(i, i + MAX_PER_BLOCK);
|
|
1137
|
+
lines.push(`${block.length} beginbfchar`);
|
|
1138
|
+
for (const [gid, cp] of block) lines.push(`<${hex4(gid)}> <${utf16Hex(cp)}>`);
|
|
1139
|
+
lines.push("endbfchar");
|
|
1140
|
+
}
|
|
1141
|
+
lines.push("endcmap", "CMapName currentdict /CMap defineresource pop", "end", "end");
|
|
1142
|
+
return lines.join("\n");
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/fonts/embedFont.ts
|
|
1146
|
+
var encoder = new TextEncoder();
|
|
1147
|
+
function baseFontName(e, family, weight, style) {
|
|
1148
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "");
|
|
1149
|
+
return safe(e.postScriptName) || safe(`${family}-${weight}-${style}`);
|
|
1150
|
+
}
|
|
1151
|
+
var Face = class {
|
|
1152
|
+
constructor(objects, emb, baseFont) {
|
|
1153
|
+
this.objects = objects;
|
|
1154
|
+
this.emb = emb;
|
|
1155
|
+
this.baseFont = baseFont;
|
|
1156
|
+
}
|
|
1157
|
+
objects;
|
|
1158
|
+
emb;
|
|
1159
|
+
baseFont;
|
|
1160
|
+
/** Glyph id to the first codepoint that asked for it. */
|
|
1161
|
+
used = /* @__PURE__ */ new Map();
|
|
1162
|
+
fontRef;
|
|
1163
|
+
written = false;
|
|
1164
|
+
get ref() {
|
|
1165
|
+
if (!this.fontRef) throw new Error("pdf: font ref read before any glyph was used");
|
|
1166
|
+
return this.fontRef;
|
|
1167
|
+
}
|
|
1168
|
+
use(cp) {
|
|
1169
|
+
const gid = this.emb.glyphIndexOf(cp);
|
|
1170
|
+
if (gid === null || gid === 0) return null;
|
|
1171
|
+
if (!this.used.has(gid)) this.used.set(gid, cp);
|
|
1172
|
+
this.fontRef ??= this.objects.alloc();
|
|
1173
|
+
return gid;
|
|
1174
|
+
}
|
|
1175
|
+
widthOf(gid) {
|
|
1176
|
+
return Math.round(this.emb.advanceOfGlyph(gid) * 1e3 / this.emb.unitsPerEm);
|
|
1177
|
+
}
|
|
1178
|
+
write() {
|
|
1179
|
+
if (this.written || !this.fontRef) return;
|
|
1180
|
+
this.written = true;
|
|
1181
|
+
const { objects, emb } = this;
|
|
1182
|
+
const k = 1e3 / emb.unitsPerEm;
|
|
1183
|
+
const file = objects.alloc();
|
|
1184
|
+
const descriptor = objects.alloc();
|
|
1185
|
+
const cid = objects.alloc();
|
|
1186
|
+
const toUnicode = objects.alloc();
|
|
1187
|
+
objects.streamDeflated(file, { Length1: emb.sfnt.byteLength }, new Uint8Array(emb.sfnt));
|
|
1188
|
+
objects.streamDeflated(toUnicode, {}, encoder.encode(toUnicodeCMap(this.used)));
|
|
1189
|
+
objects.set(descriptor, {
|
|
1190
|
+
Type: name("FontDescriptor"),
|
|
1191
|
+
FontName: name(this.baseFont),
|
|
1192
|
+
Flags: 4,
|
|
1193
|
+
FontBBox: emb.bbox.map((v, i) => i < 2 ? Math.floor(v * k) : Math.ceil(v * k)),
|
|
1194
|
+
ItalicAngle: emb.italicAngle,
|
|
1195
|
+
Ascent: Math.round(emb.ascent * k),
|
|
1196
|
+
Descent: Math.round(emb.descent * k),
|
|
1197
|
+
CapHeight: Math.round(emb.capHeight * k),
|
|
1198
|
+
StemV: 80,
|
|
1199
|
+
// required by the spec, advisory for TrueType
|
|
1200
|
+
FontFile2: file
|
|
1201
|
+
});
|
|
1202
|
+
const widths = [];
|
|
1203
|
+
for (const gid of [...this.used.keys()].sort((a, b) => a - b)) widths.push(gid, [this.widthOf(gid)]);
|
|
1204
|
+
objects.set(cid, {
|
|
1205
|
+
Type: name("Font"),
|
|
1206
|
+
Subtype: name("CIDFontType2"),
|
|
1207
|
+
BaseFont: name(this.baseFont),
|
|
1208
|
+
CIDSystemInfo: { Registry: text("Adobe"), Ordering: text("Identity"), Supplement: 0 },
|
|
1209
|
+
FontDescriptor: descriptor,
|
|
1210
|
+
CIDToGIDMap: name("Identity"),
|
|
1211
|
+
W: widths
|
|
1212
|
+
});
|
|
1213
|
+
objects.set(this.fontRef, {
|
|
1214
|
+
Type: name("Font"),
|
|
1215
|
+
Subtype: name("Type0"),
|
|
1216
|
+
BaseFont: name(this.baseFont),
|
|
1217
|
+
Encoding: name("Identity-H"),
|
|
1218
|
+
DescendantFonts: [cid],
|
|
1219
|
+
ToUnicode: toUnicode
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
var DocumentFonts = class {
|
|
1224
|
+
constructor(objects, source) {
|
|
1225
|
+
this.objects = objects;
|
|
1226
|
+
this.source = source;
|
|
1227
|
+
}
|
|
1228
|
+
objects;
|
|
1229
|
+
source;
|
|
1230
|
+
faces = /* @__PURE__ */ new Map();
|
|
1231
|
+
/** The embedded font for a face, or null to draw its glyphs as outlines. */
|
|
1232
|
+
face(family, weight, style) {
|
|
1233
|
+
const key = JSON.stringify([family, weight, style]);
|
|
1234
|
+
let p = this.faces.get(key);
|
|
1235
|
+
if (!p) {
|
|
1236
|
+
p = this.load(family, weight, style);
|
|
1237
|
+
this.faces.set(key, p);
|
|
1238
|
+
}
|
|
1239
|
+
return p;
|
|
1240
|
+
}
|
|
1241
|
+
async load(family, weight, style) {
|
|
1242
|
+
let emb;
|
|
1243
|
+
try {
|
|
1244
|
+
emb = await this.source.embedding(family, weight, style);
|
|
1245
|
+
} catch {
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
if (!emb || emb.outlines !== "truetype" || !emb.embeddable) return null;
|
|
1249
|
+
return new Face(this.objects, emb, baseFontName(emb, family, weight, style));
|
|
1250
|
+
}
|
|
1251
|
+
/** Writes every face's objects; call before `PdfObjects.settle`. */
|
|
1252
|
+
async finish() {
|
|
1253
|
+
for (const p of this.faces.values()) (await p)?.write();
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// src/writePdf.ts
|
|
1258
|
+
function writePdf(pages, opts) {
|
|
1259
|
+
return writePdfWith(pages, opts, deflate);
|
|
1260
|
+
}
|
|
1261
|
+
async function writePdfWith(pages, opts, deflate2) {
|
|
1262
|
+
if (pages.length === 0) throw new Error("pdf: a document needs at least one page");
|
|
1263
|
+
const objects = new PdfObjects();
|
|
1264
|
+
const catalog = objects.alloc();
|
|
1265
|
+
const tree = objects.alloc();
|
|
1266
|
+
const info = objects.alloc();
|
|
1267
|
+
const skipped = [];
|
|
1268
|
+
const kids = [];
|
|
1269
|
+
const fonts = new DocumentFonts(objects, opts.fonts);
|
|
1270
|
+
for (const page of pages) {
|
|
1271
|
+
const pageRef = objects.alloc();
|
|
1272
|
+
const contentRef = objects.alloc();
|
|
1273
|
+
const ctx = {
|
|
1274
|
+
objects,
|
|
1275
|
+
resources: new PageResources(),
|
|
1276
|
+
out: new ContentStream(),
|
|
1277
|
+
opts,
|
|
1278
|
+
fonts,
|
|
1279
|
+
skipped,
|
|
1280
|
+
view: page.view,
|
|
1281
|
+
width: page.width,
|
|
1282
|
+
height: page.height
|
|
1283
|
+
};
|
|
1284
|
+
ctx.out.op("cm", ...mat3.toAffine(pageFlip(page)));
|
|
1285
|
+
await writeCommands(ctx, page.commands, { transform: mat3.identity(), alpha: 1, colorMatrix: null });
|
|
1286
|
+
const raw = ctx.out.bytes();
|
|
1287
|
+
objects.streamDeflated(contentRef, {}, raw);
|
|
1288
|
+
objects.set(pageRef, {
|
|
1289
|
+
Type: name("Page"),
|
|
1290
|
+
Parent: tree,
|
|
1291
|
+
MediaBox: [0, 0, page.width, page.height],
|
|
1292
|
+
Contents: contentRef,
|
|
1293
|
+
Resources: ctx.resources.dict()
|
|
1294
|
+
});
|
|
1295
|
+
kids.push(pageRef);
|
|
1296
|
+
}
|
|
1297
|
+
objects.set(catalog, { Type: name("Catalog"), Pages: tree });
|
|
1298
|
+
objects.set(tree, { Type: name("Pages"), Kids: kids, Count: kids.length });
|
|
1299
|
+
objects.set(info, { Producer: text("weasel"), Title: opts.title === void 0 ? void 0 : text(opts.title) });
|
|
1300
|
+
await fonts.finish();
|
|
1301
|
+
await objects.settle(opts.compress !== false, deflate2);
|
|
1302
|
+
return { bytes: objects.serialize(catalog, info), skipped };
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export { registryFontSource, writePdf };
|
|
1306
|
+
//# sourceMappingURL=index.js.map
|
|
1307
|
+
//# sourceMappingURL=index.js.map
|