@jasy/pdf 1.0.0-alpha.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/README.md +171 -0
- package/dist/api/args.d.ts +10 -0
- package/dist/api/args.js +13 -0
- package/dist/api/color.d.ts +24 -0
- package/dist/api/color.js +215 -0
- package/dist/api/content.d.ts +36 -0
- package/dist/api/content.js +50 -0
- package/dist/api/descriptor.d.ts +25 -0
- package/dist/api/descriptor.js +71 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +27 -0
- package/dist/api/insets.d.ts +16 -0
- package/dist/api/insets.js +21 -0
- package/dist/api/layout.d.ts +72 -0
- package/dist/api/layout.js +99 -0
- package/dist/api/structure.d.ts +80 -0
- package/dist/api/structure.js +125 -0
- package/dist/api/table.d.ts +37 -0
- package/dist/api/table.js +87 -0
- package/dist/api/text.d.ts +28 -0
- package/dist/api/text.js +61 -0
- package/dist/assets/Courier-Bold.afm +342 -0
- package/dist/assets/Courier-BoldOblique.afm +342 -0
- package/dist/assets/Courier-Oblique.afm +342 -0
- package/dist/assets/Courier.afm +342 -0
- package/dist/assets/Helvetica-Bold.afm +2827 -0
- package/dist/assets/Helvetica-BoldOblique.afm +2827 -0
- package/dist/assets/Helvetica-Oblique.afm +3051 -0
- package/dist/assets/Helvetica.afm +3051 -0
- package/dist/assets/Symbol.afm +213 -0
- package/dist/assets/Times-Bold.afm +2588 -0
- package/dist/assets/Times-BoldItalic.afm +2384 -0
- package/dist/assets/Times-Italic.afm +2667 -0
- package/dist/assets/Times-Roman.afm +2419 -0
- package/dist/assets/ZapfDingbats.afm +225 -0
- package/dist/assets/agl.txt +695 -0
- package/dist/common/color.d.ts +37 -0
- package/dist/common/color.js +62 -0
- package/dist/constants/page-sizes.d.ts +44 -0
- package/dist/constants/page-sizes.js +92 -0
- package/dist/constants/pdf-parts.d.ts +5 -0
- package/dist/constants/pdf-parts.js +8 -0
- package/dist/elements/container-element.d.ts +35 -0
- package/dist/elements/container-element.js +91 -0
- package/dist/elements/image-element.d.ts +56 -0
- package/dist/elements/image-element.js +151 -0
- package/dist/elements/index.d.ts +10 -0
- package/dist/elements/index.js +28 -0
- package/dist/elements/layout/deferred-element.d.ts +19 -0
- package/dist/elements/layout/deferred-element.js +33 -0
- package/dist/elements/layout/expanded-element.d.ts +30 -0
- package/dist/elements/layout/expanded-element.js +68 -0
- package/dist/elements/layout/padding-element.d.ts +29 -0
- package/dist/elements/layout/padding-element.js +76 -0
- package/dist/elements/layout/repeating-header-element.d.ts +29 -0
- package/dist/elements/layout/repeating-header-element.js +60 -0
- package/dist/elements/layout/sized-container-element.d.ts +14 -0
- package/dist/elements/layout/sized-container-element.js +37 -0
- package/dist/elements/line-element.d.ts +31 -0
- package/dist/elements/line-element.js +44 -0
- package/dist/elements/page-element.d.ts +58 -0
- package/dist/elements/page-element.js +90 -0
- package/dist/elements/pdf-document-element.d.ts +13 -0
- package/dist/elements/pdf-document-element.js +22 -0
- package/dist/elements/pdf-element.d.ts +67 -0
- package/dist/elements/pdf-element.js +55 -0
- package/dist/elements/rectangle-element.d.ts +55 -0
- package/dist/elements/rectangle-element.js +120 -0
- package/dist/elements/row-element.d.ts +42 -0
- package/dist/elements/row-element.js +54 -0
- package/dist/elements/text-element.d.ts +59 -0
- package/dist/elements/text-element.js +137 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +21 -0
- package/dist/ir/display-list.d.ts +74 -0
- package/dist/ir/display-list.js +2 -0
- package/dist/layout/box-constraints.d.ts +56 -0
- package/dist/layout/box-constraints.js +73 -0
- package/dist/layout/fragmentation.d.ts +42 -0
- package/dist/layout/fragmentation.js +61 -0
- package/dist/renderer/container-renderer.d.ts +6 -0
- package/dist/renderer/container-renderer.js +30 -0
- package/dist/renderer/deferred-renderer.d.ts +6 -0
- package/dist/renderer/deferred-renderer.js +25 -0
- package/dist/renderer/expanded-renderer.d.ts +6 -0
- package/dist/renderer/expanded-renderer.js +23 -0
- package/dist/renderer/image-renderer.d.ts +6 -0
- package/dist/renderer/image-renderer.js +85 -0
- package/dist/renderer/index.d.ts +10 -0
- package/dist/renderer/index.js +26 -0
- package/dist/renderer/line-renderer.d.ts +6 -0
- package/dist/renderer/line-renderer.js +30 -0
- package/dist/renderer/padding-renderer.d.ts +6 -0
- package/dist/renderer/padding-renderer.js +23 -0
- package/dist/renderer/page-renderer.d.ts +5 -0
- package/dist/renderer/page-renderer.js +81 -0
- package/dist/renderer/pdf-backend.d.ts +45 -0
- package/dist/renderer/pdf-backend.js +184 -0
- package/dist/renderer/pdf-config.d.ts +8 -0
- package/dist/renderer/pdf-config.js +17 -0
- package/dist/renderer/pdf-document-class.d.ts +42 -0
- package/dist/renderer/pdf-document-class.js +118 -0
- package/dist/renderer/pdf-document-renderer.d.ts +20 -0
- package/dist/renderer/pdf-document-renderer.js +104 -0
- package/dist/renderer/pdf-renderer.d.ts +5 -0
- package/dist/renderer/pdf-renderer.js +92 -0
- package/dist/renderer/rectangle-renderer.d.ts +8 -0
- package/dist/renderer/rectangle-renderer.js +61 -0
- package/dist/renderer/repeating-header-renderer.d.ts +6 -0
- package/dist/renderer/repeating-header-renderer.js +28 -0
- package/dist/renderer/row-renderer.d.ts +6 -0
- package/dist/renderer/row-renderer.js +30 -0
- package/dist/renderer/text-renderer.d.ts +9 -0
- package/dist/renderer/text-renderer.js +125 -0
- package/dist/text/line-breaker.d.ts +40 -0
- package/dist/text/line-breaker.js +106 -0
- package/dist/utils/afm-parser.d.ts +12 -0
- package/dist/utils/afm-parser.js +91 -0
- package/dist/utils/flex-layout.d.ts +53 -0
- package/dist/utils/flex-layout.js +119 -0
- package/dist/utils/font-metrics.d.ts +12 -0
- package/dist/utils/font-metrics.js +2 -0
- package/dist/utils/font-path.d.ts +1 -0
- package/dist/utils/font-path.js +19 -0
- package/dist/utils/image-helper.d.ts +43 -0
- package/dist/utils/image-helper.js +206 -0
- package/dist/utils/pdf-object-manager.d.ts +97 -0
- package/dist/utils/pdf-object-manager.js +645 -0
- package/dist/utils/renderer-registry.d.ts +6 -0
- package/dist/utils/renderer-registry.js +19 -0
- package/dist/utils/ttf-parser.d.ts +29 -0
- package/dist/utils/ttf-parser.js +191 -0
- package/dist/utils/ttf-subsetter.d.ts +1 -0
- package/dist/utils/ttf-subsetter.js +161 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +2 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +55 -0
- package/dist/validators/element-validator.d.ts +8 -0
- package/dist/validators/element-validator.js +61 -0
- package/package.json +50 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PDFObjectManager = exports.FontStyle = void 0;
|
|
37
|
+
const page_sizes_1 = require("../constants/page-sizes");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const crypto_1 = require("crypto");
|
|
41
|
+
const zlib_1 = require("zlib");
|
|
42
|
+
const afm_parser_1 = require("./afm-parser");
|
|
43
|
+
const ttf_parser_1 = require("./ttf-parser");
|
|
44
|
+
const ttf_subsetter_1 = require("./ttf-subsetter");
|
|
45
|
+
// Enums come from the leaf config module (never in a cycle); the config type is
|
|
46
|
+
// erased at runtime so it can come from the cyclic module safely.
|
|
47
|
+
const pdf_config_1 = require("../renderer/pdf-config");
|
|
48
|
+
var FontStyle;
|
|
49
|
+
(function (FontStyle) {
|
|
50
|
+
FontStyle["Normal"] = "normal";
|
|
51
|
+
FontStyle["Bold"] = "bold";
|
|
52
|
+
FontStyle["Italic"] = "italic";
|
|
53
|
+
FontStyle["BoldItalic"] = "boldItalic";
|
|
54
|
+
})(FontStyle || (exports.FontStyle = FontStyle = {}));
|
|
55
|
+
// Suffix appended to an embedded font's PDF /BaseFont so each style variant of a family keeps
|
|
56
|
+
// a distinct name (the variants are otherwise selected by the font resource, not the name).
|
|
57
|
+
const STYLE_SUFFIX = {
|
|
58
|
+
[FontStyle.Normal]: "",
|
|
59
|
+
[FontStyle.Bold]: "-Bold",
|
|
60
|
+
[FontStyle.Italic]: "-Italic",
|
|
61
|
+
[FontStyle.BoldItalic]: "-BoldItalic",
|
|
62
|
+
};
|
|
63
|
+
class ImageManager {
|
|
64
|
+
constructor() {
|
|
65
|
+
this.images = new Map();
|
|
66
|
+
}
|
|
67
|
+
addImage(resourceNumber) {
|
|
68
|
+
this.images.set("IM" + resourceNumber.toString(), resourceNumber);
|
|
69
|
+
}
|
|
70
|
+
getAllImages() {
|
|
71
|
+
return this.images;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Graphics-state objects for transparency, deduped by their (fill, stroke) alpha pair. */
|
|
75
|
+
class ExtGStateManager {
|
|
76
|
+
constructor() {
|
|
77
|
+
// alpha-pair key -> { resource name, indirect-object number }
|
|
78
|
+
this.states = new Map();
|
|
79
|
+
}
|
|
80
|
+
get(key) {
|
|
81
|
+
return this.states.get(key);
|
|
82
|
+
}
|
|
83
|
+
add(key, name, objectNumber) {
|
|
84
|
+
this.states.set(key, { name, objectNumber });
|
|
85
|
+
}
|
|
86
|
+
size() {
|
|
87
|
+
return this.states.size;
|
|
88
|
+
}
|
|
89
|
+
/** name -> object number, for the page `/Resources /ExtGState` dict. */
|
|
90
|
+
getAll() {
|
|
91
|
+
const out = new Map();
|
|
92
|
+
this.states.forEach((value) => out.set(value.name, value.objectNumber));
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
class FontManager {
|
|
97
|
+
constructor() {
|
|
98
|
+
this.fonts = new Map();
|
|
99
|
+
}
|
|
100
|
+
addFont(fontName, fontIndex, resourceIndex, fontStyle = FontStyle.Normal, fullName = fontName, force = false) {
|
|
101
|
+
const fontKey = this._createFontKey(fontName, fontStyle);
|
|
102
|
+
// `force` lets an embedded custom font override a same-named standard-14 entry (e.g. embedding
|
|
103
|
+
// "Helvetica" as Liberation for PDF/A) - otherwise the standard /Type1 resource would win and
|
|
104
|
+
// the CID text would be drawn against the wrong font.
|
|
105
|
+
if (force || !this.fonts.has(fontKey)) {
|
|
106
|
+
this.fonts.set(fontKey, {
|
|
107
|
+
fontIndex,
|
|
108
|
+
resourceIndex,
|
|
109
|
+
fontStyle,
|
|
110
|
+
fullName,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
hasFont(fontName, fontStyle = FontStyle.Normal) {
|
|
115
|
+
const fontKey = this._createFontKey(fontName, fontStyle);
|
|
116
|
+
return this.fonts.has(fontKey);
|
|
117
|
+
}
|
|
118
|
+
getFont(fontName, fontStyle = FontStyle.Normal) {
|
|
119
|
+
const fontKey = this._createFontKey(fontName, fontStyle);
|
|
120
|
+
return this.fonts.get(fontKey);
|
|
121
|
+
}
|
|
122
|
+
addCustomFont(fontName, fontStyle, fullName = fontName) {
|
|
123
|
+
const fontIndex = this.getLastFontIndex() + 1;
|
|
124
|
+
const resourceIndex = this.getLastResourceIndex() + 1;
|
|
125
|
+
this.addFont(fontName, fontIndex, resourceIndex, fontStyle, fullName);
|
|
126
|
+
}
|
|
127
|
+
getAllFonts() {
|
|
128
|
+
return this.fonts;
|
|
129
|
+
}
|
|
130
|
+
size() {
|
|
131
|
+
return this.fonts.size;
|
|
132
|
+
}
|
|
133
|
+
getLastFontIndex() {
|
|
134
|
+
let maxFontIndex = 0;
|
|
135
|
+
this.fonts.forEach((value) => {
|
|
136
|
+
if (value.fontIndex > maxFontIndex) {
|
|
137
|
+
maxFontIndex = value.fontIndex;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return maxFontIndex;
|
|
141
|
+
}
|
|
142
|
+
getLastResourceIndex() {
|
|
143
|
+
let maxResourceIndex = 0;
|
|
144
|
+
this.fonts.forEach((value) => {
|
|
145
|
+
if (value.resourceIndex > maxResourceIndex) {
|
|
146
|
+
maxResourceIndex = value.resourceIndex;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return maxResourceIndex;
|
|
150
|
+
}
|
|
151
|
+
_createFontKey(fontName, fontStyle) {
|
|
152
|
+
return `${fontName}-${fontStyle}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
class PDFObjectManager {
|
|
156
|
+
constructor(pageSize) {
|
|
157
|
+
this.objects = [];
|
|
158
|
+
this.objectPositions = [];
|
|
159
|
+
this.parentObjectNumber = 0;
|
|
160
|
+
this.fonts = new FontManager(); // Stores the fonts
|
|
161
|
+
this.images = new ImageManager(); // Stores the images (object numbers and names)
|
|
162
|
+
this.extGStates = new ExtGStateManager(); // Transparency states
|
|
163
|
+
this.pageFormat = page_sizes_1.pageFormats[page_sizes_1.PageSize.A4];
|
|
164
|
+
this.afmParsers = [];
|
|
165
|
+
// Embedded TrueType fonts, keyed by the name the user registers them under. When a font
|
|
166
|
+
// name is in here the metric/emission paths take the TTF branch instead of the AFM one.
|
|
167
|
+
this.customFonts = new Map();
|
|
168
|
+
// Per-registered face: the reserved font-object numbers + the glyph ids actually used. The font
|
|
169
|
+
// program is subsetted and the objects filled in by finalizeCustomFonts(), after the render pass.
|
|
170
|
+
this.customFontEmit = new Map();
|
|
171
|
+
// Embedded file attachments (their /Filespec object numbers + display names). Referenced from
|
|
172
|
+
// the catalog's /Names/EmbeddedFiles + /AF. Drives ZUGFeRD's embedded factur-x.xml.
|
|
173
|
+
this.attachments = [];
|
|
174
|
+
// PDF header version (PDF/A-3 needs 1.7) and whether to write a trailer /ID (PDF/A needs one).
|
|
175
|
+
// Both default to the pre-PDF/A behaviour, so a normal document stays byte-identical.
|
|
176
|
+
this.pdfVersion = "1.4";
|
|
177
|
+
this.documentId = false;
|
|
178
|
+
this.compress = false;
|
|
179
|
+
if (pageSize)
|
|
180
|
+
this.pageFormat = page_sizes_1.pageFormats[pageSize];
|
|
181
|
+
this.fillConfigWithStandardValues();
|
|
182
|
+
}
|
|
183
|
+
// Adds an object and returns its number
|
|
184
|
+
addObject(content) {
|
|
185
|
+
// The text is encoded in Windows-1252 if necessary
|
|
186
|
+
const objectNumber = this.objects.length + 1;
|
|
187
|
+
const position = this.getCurrentByteLength(); // Calculate the current byte length
|
|
188
|
+
this.objectPositions.push(position);
|
|
189
|
+
this.objects.push(content);
|
|
190
|
+
return objectNumber;
|
|
191
|
+
}
|
|
192
|
+
// Replaces an object at the index `objectNumber`
|
|
193
|
+
replaceObject(objectNumber, content) {
|
|
194
|
+
this.objects[objectNumber - 1] = content;
|
|
195
|
+
}
|
|
196
|
+
// FlateDecode compression for stream payloads (default off; renderPdf turns it on). Off keeps the
|
|
197
|
+
// PDF greppable for debugging and the internal tests; the XMP metadata stream is never routed here.
|
|
198
|
+
setCompress(on) {
|
|
199
|
+
this.compress = on;
|
|
200
|
+
}
|
|
201
|
+
// Builds a stream object body: `<< extraDict /Length n >> stream … endstream`, FlateDecode-compressed
|
|
202
|
+
// when enabled AND it actually helps (tiny streams aren't inflated). Binary bytes ride as a latin1
|
|
203
|
+
// string - the final encoder passes 0x00-0xFF through unchanged.
|
|
204
|
+
stream(extraDict, data) {
|
|
205
|
+
const head = extraDict ? extraDict + " " : "";
|
|
206
|
+
if (this.compress) {
|
|
207
|
+
const z = (0, zlib_1.deflateSync)(data);
|
|
208
|
+
if (z.length < data.length) {
|
|
209
|
+
return `<< ${head}/Filter /FlateDecode /Length ${z.length} >>\nstream\n${z.toString("latin1")}\nendstream`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return `<< ${head}/Length ${data.length} >>\nstream\n${data.toString("latin1")}\nendstream`;
|
|
213
|
+
}
|
|
214
|
+
// Adds a page content stream (compressed when enabled). The caller passes the raw operator string.
|
|
215
|
+
addContentStream(content) {
|
|
216
|
+
return this.addObject(this.stream("", Buffer.from(content, "latin1")));
|
|
217
|
+
}
|
|
218
|
+
changePDFConfig(config) {
|
|
219
|
+
this.pdfConfig = Object.assign(Object.assign({}, this.pdfConfig), config);
|
|
220
|
+
}
|
|
221
|
+
getPDFConfig() {
|
|
222
|
+
return this.pdfConfig;
|
|
223
|
+
}
|
|
224
|
+
fillConfigWithStandardValues() {
|
|
225
|
+
this.pdfConfig = {
|
|
226
|
+
orientation: pdf_config_1.Orientation.portrait,
|
|
227
|
+
defaultFont: {
|
|
228
|
+
fontFamily: "Helvetica",
|
|
229
|
+
fontSize: 12,
|
|
230
|
+
fontStyle: FontStyle.Normal,
|
|
231
|
+
},
|
|
232
|
+
pageSize: page_sizes_1.PageSize.A4,
|
|
233
|
+
margin: { left: 72, top: 72, bottom: 72, right: 72 },
|
|
234
|
+
colorMode: pdf_config_1.ColorMode.color,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Calculates the total length of the document in bytes (for XRef)
|
|
238
|
+
getCurrentByteLength() {
|
|
239
|
+
let length = this.getHeader().length; // Start with the header
|
|
240
|
+
for (let i = 0; i < this.objects.length; i++) {
|
|
241
|
+
const obj = this.objects[i];
|
|
242
|
+
const objectContent = `${i + 1} 0 obj\n${obj}\nendobj\n`;
|
|
243
|
+
// Convert the object content into a buffer with the correct encoding
|
|
244
|
+
const encodedContent = Buffer.from(objectContent, "binary");
|
|
245
|
+
// Add the actual byte length to the total length
|
|
246
|
+
length += encodedContent.length;
|
|
247
|
+
}
|
|
248
|
+
return length;
|
|
249
|
+
}
|
|
250
|
+
// Sets the parent object number
|
|
251
|
+
setParentObjectNumber(number) {
|
|
252
|
+
this.parentObjectNumber = number;
|
|
253
|
+
}
|
|
254
|
+
// Returns the parent object number
|
|
255
|
+
getParentObjectNumber() {
|
|
256
|
+
return this.parentObjectNumber;
|
|
257
|
+
}
|
|
258
|
+
// Registers an image
|
|
259
|
+
registerImage(width, height, imageType, imageData) {
|
|
260
|
+
const imageObject = `<< /Type /XObject
|
|
261
|
+
/Subtype /Image
|
|
262
|
+
/Width ${width}
|
|
263
|
+
/Height ${height}
|
|
264
|
+
/ColorSpace /DeviceRGB
|
|
265
|
+
/BitsPerComponent 8
|
|
266
|
+
/Filter /${imageType}
|
|
267
|
+
/Length ${imageData.length} >>
|
|
268
|
+
stream
|
|
269
|
+
${imageData}
|
|
270
|
+
endstream`;
|
|
271
|
+
// Add the image and its object number to the image manager - return the object number
|
|
272
|
+
const imageObjectNumber = this.addObject(imageObject);
|
|
273
|
+
this.images.addImage(imageObjectNumber);
|
|
274
|
+
return imageObjectNumber;
|
|
275
|
+
}
|
|
276
|
+
// Embeds a file as an associated file (PDF/A-3 / PDF 2.0): an /EmbeddedFile stream + a /Filespec.
|
|
277
|
+
// The catalog wiring (/Names/EmbeddedFiles + /AF) is added by PDFRenderer when attachments exist.
|
|
278
|
+
// `relationship` is the /AFRelationship (e.g. "Data" for the ZUGFeRD factur-x.xml). Binary bytes
|
|
279
|
+
// ride as a latin1 string, like images/fonts (the final encoder passes 0x00-0xFF through).
|
|
280
|
+
attachFile(name, data, opts = {}) {
|
|
281
|
+
var _a, _b;
|
|
282
|
+
const subtype = ((_a = opts.mimeType) !== null && _a !== void 0 ? _a : "application/octet-stream").replace(/\//g, "#2F");
|
|
283
|
+
const embedded = this.addObject(this.stream(`/Type /EmbeddedFile /Subtype /${subtype} /Params << /Size ${data.length} >>`, data));
|
|
284
|
+
const escaped = (s) => s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
285
|
+
const desc = opts.description ? ` /Desc (${escaped(opts.description)})` : "";
|
|
286
|
+
const filespec = this.addObject(`<< /Type /Filespec /F (${escaped(name)}) /UF (${escaped(name)}) ` +
|
|
287
|
+
`/AFRelationship /${(_b = opts.relationship) !== null && _b !== void 0 ? _b : "Unspecified"}${desc} ` +
|
|
288
|
+
`/EF << /F ${embedded} 0 R /UF ${embedded} 0 R >> >>`);
|
|
289
|
+
this.attachments.push({ name, filespec });
|
|
290
|
+
}
|
|
291
|
+
// Filespec object numbers + names, for the catalog's /Names/EmbeddedFiles and /AF.
|
|
292
|
+
getAttachments() {
|
|
293
|
+
return this.attachments;
|
|
294
|
+
}
|
|
295
|
+
// Sets the document XMP packet (catalog /Metadata). ASCII expected (see the field comment).
|
|
296
|
+
setXmpMetadata(xml) {
|
|
297
|
+
this.xmpMetadata = xml;
|
|
298
|
+
}
|
|
299
|
+
getXmpMetadata() {
|
|
300
|
+
return this.xmpMetadata;
|
|
301
|
+
}
|
|
302
|
+
// Embeds an ICC profile and a PDF/A /OutputIntent that points at it (catalog /OutputIntents).
|
|
303
|
+
// `icc` are the raw profile bytes (an RGB profile, /N 3 - e.g. sRGB).
|
|
304
|
+
setOutputIntent(icc, opts = {}) {
|
|
305
|
+
var _a, _b;
|
|
306
|
+
const profile = this.addObject(this.stream("/N 3", icc));
|
|
307
|
+
this.outputIntent = this.addObject(`<< /Type /OutputIntent /S /GTS_PDFA1 ` +
|
|
308
|
+
`/OutputConditionIdentifier (${(_a = opts.identifier) !== null && _a !== void 0 ? _a : "sRGB"}) ` +
|
|
309
|
+
`/Info (${(_b = opts.info) !== null && _b !== void 0 ? _b : "sRGB IEC61966-2.1"}) /DestOutputProfile ${profile} 0 R >>`);
|
|
310
|
+
}
|
|
311
|
+
getOutputIntent() {
|
|
312
|
+
return this.outputIntent;
|
|
313
|
+
}
|
|
314
|
+
// PDF header version, e.g. "1.7" for PDF/A-3. Always "1.X" so the 9-byte header length (and the
|
|
315
|
+
// xref offsets that depend on it) never change.
|
|
316
|
+
setPdfVersion(version) {
|
|
317
|
+
this.pdfVersion = version;
|
|
318
|
+
}
|
|
319
|
+
getPdfVersion() {
|
|
320
|
+
return this.pdfVersion;
|
|
321
|
+
}
|
|
322
|
+
// The full PDF header: the version line + a binary-marker comment whose 4 bytes are all > 127
|
|
323
|
+
// (PDF/A clause 6.1.2). Every char is <= 0xFF, so the string length equals the emitted byte
|
|
324
|
+
// length - which the xref offset calculation relies on.
|
|
325
|
+
getHeader() {
|
|
326
|
+
// The marker is `%` + â ã Ï Ó (Latin-1 0xE2 0xE3 0xCF 0xD3, all > 127); each char is <= 0xFF so
|
|
327
|
+
// the final encoder emits it as that exact byte.
|
|
328
|
+
return `%PDF-${this.pdfVersion}\n%âãÏÓ\n`;
|
|
329
|
+
}
|
|
330
|
+
// Enables a trailer /ID (required by PDF/A). The id is a content hash, so it is deterministic.
|
|
331
|
+
enableDocumentId() {
|
|
332
|
+
this.documentId = true;
|
|
333
|
+
}
|
|
334
|
+
// Registers (or reuses) a transparency graphics state and returns its resource name
|
|
335
|
+
// (e.g. "GS1"). `fillAlpha` -> /ca, `strokeAlpha` -> /CA. Deduped by the alpha pair.
|
|
336
|
+
registerExtGState(fillAlpha, strokeAlpha) {
|
|
337
|
+
const ca = fillAlpha.toFixed(3);
|
|
338
|
+
const CA = strokeAlpha.toFixed(3);
|
|
339
|
+
const key = `${ca}:${CA}`;
|
|
340
|
+
const existing = this.extGStates.get(key);
|
|
341
|
+
if (existing)
|
|
342
|
+
return existing.name;
|
|
343
|
+
const name = `GS${this.extGStates.size() + 1}`;
|
|
344
|
+
const objectNumber = this.addObject(`<< /Type /ExtGState /ca ${ca} /CA ${CA} >>`);
|
|
345
|
+
this.extGStates.add(key, name, objectNumber);
|
|
346
|
+
return name;
|
|
347
|
+
}
|
|
348
|
+
// Returns all registered transparency states (name -> object number) for the page
|
|
349
|
+
getAllExtGStatesRaw() {
|
|
350
|
+
return this.extGStates.getAll();
|
|
351
|
+
}
|
|
352
|
+
// Registers a font
|
|
353
|
+
registerFont(fontName, fontStyle = FontStyle.Normal, fullName = fontName) {
|
|
354
|
+
if (this.fonts.hasFont(fontName, fontStyle)) {
|
|
355
|
+
return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
|
|
356
|
+
}
|
|
357
|
+
const afmFilePath = path.resolve(__dirname, "../", `assets/${fullName}.afm`);
|
|
358
|
+
if (fs.existsSync(afmFilePath)) {
|
|
359
|
+
const data = fs.readFileSync(afmFilePath, "utf-8");
|
|
360
|
+
this.afmParsers.push({
|
|
361
|
+
fontName,
|
|
362
|
+
fontStyle,
|
|
363
|
+
fullFontName: fullName,
|
|
364
|
+
parser: new afm_parser_1.AFMParser(data),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const resourceNumber = this.objects.length + 1; // New resource number
|
|
368
|
+
const fontNumber = this.fonts.getLastFontIndex() + 1; // New font index number
|
|
369
|
+
this.fonts.addFont(fontName, fontNumber, resourceNumber, fontStyle); // Store it
|
|
370
|
+
const fontObject = `<</BaseFont/${fullName}/Type/Font\n/Encoding/WinAnsiEncoding\n/Subtype/Type1>>`;
|
|
371
|
+
this.addObject(fontObject);
|
|
372
|
+
return {
|
|
373
|
+
fontIndex: fontNumber,
|
|
374
|
+
resourceIndex: resourceNumber,
|
|
375
|
+
fontStyle: fontStyle,
|
|
376
|
+
fullName: fullName,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
// Registers one variant (default Normal) of an embedded TrueType family `name`: stores it for
|
|
380
|
+
// metrics and emits its PDF font objects. All variants share the family `name`; bold/italic are
|
|
381
|
+
// separate .ttf files registered under the same name with a different style.
|
|
382
|
+
registerCustomFont(name, data, style = FontStyle.Normal) {
|
|
383
|
+
const key = this.customKey(name, style);
|
|
384
|
+
if (this.customFonts.has(key))
|
|
385
|
+
return;
|
|
386
|
+
this.customFonts.set(key, new ttf_parser_1.TTFParser(data));
|
|
387
|
+
// Emission (the PDF font objects + page resource) is DEFERRED to first use via ensureEmitted(),
|
|
388
|
+
// so a registered-but-never-rendered face - e.g. a bundled family the document doesn't touch -
|
|
389
|
+
// costs nothing in the output. The metrics above are still available for layout immediately.
|
|
390
|
+
}
|
|
391
|
+
// Reserves the font objects and registers the page resource the FIRST time a face is actually
|
|
392
|
+
// used. Idempotent; `style` is the already-resolved variant.
|
|
393
|
+
ensureEmitted(name, style) {
|
|
394
|
+
const key = this.customKey(name, style);
|
|
395
|
+
if (this.customFontEmit.has(key))
|
|
396
|
+
return;
|
|
397
|
+
// Reserve the font objects (dependency order so each reference points at an already-numbered
|
|
398
|
+
// object); their content - a SUBSET of the font - is filled in by finalizeCustomFonts().
|
|
399
|
+
const fontFile = this.addObject("<< >>");
|
|
400
|
+
const descriptor = this.addObject("<< >>");
|
|
401
|
+
const cidFont = this.addObject("<< >>");
|
|
402
|
+
const toUnicode = this.addObject("<< >>");
|
|
403
|
+
const type0 = this.addObject("<< >>");
|
|
404
|
+
// The Type0 dict is the resource the page references (/F{index} -> type0 object). `force` so an
|
|
405
|
+
// embedded font overrides a same-named standard-14 entry instead of being silently dropped.
|
|
406
|
+
this.fonts.addFont(name, this.fonts.getLastFontIndex() + 1, type0, style, name, true);
|
|
407
|
+
this.customFontEmit.set(key, {
|
|
408
|
+
pdfName: name + STYLE_SUFFIX[style],
|
|
409
|
+
fontFile,
|
|
410
|
+
descriptor,
|
|
411
|
+
cidFont,
|
|
412
|
+
toUnicode,
|
|
413
|
+
type0,
|
|
414
|
+
used: new Set([0]), // .notdef always present
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
// Fills the reserved font objects with a SUBSET of each font (only the glyphs the document used),
|
|
418
|
+
// tagged "ABCDEF+" as PDF/A requires for subsets. Call once, after the render pass, before output.
|
|
419
|
+
finalizeCustomFonts() {
|
|
420
|
+
for (const [key, e] of this.customFontEmit) {
|
|
421
|
+
const ttf = this.customFonts.get(key);
|
|
422
|
+
const base = `${this.subsetTag(e.pdfName, e.used)}+${e.pdfName}`;
|
|
423
|
+
this.replaceObject(e.fontFile, this.buildFontFile2(ttf, e.used));
|
|
424
|
+
this.replaceObject(e.descriptor, this.buildFontDescriptor(base, ttf, e.fontFile));
|
|
425
|
+
this.replaceObject(e.cidFont, this.buildCIDFont(base, ttf, e.descriptor, e.used));
|
|
426
|
+
this.replaceObject(e.toUnicode, this.buildToUnicode(ttf, e.used));
|
|
427
|
+
this.replaceObject(e.type0, this.buildType0(base, e.cidFont, e.toUnicode));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// A deterministic 6-uppercase-letter subset tag (PDF/A wants the "TAG+FontName" form).
|
|
431
|
+
subsetTag(pdfName, used) {
|
|
432
|
+
const h = (0, crypto_1.createHash)("md5")
|
|
433
|
+
.update(pdfName + [...used].sort((a, b) => a - b).join(","))
|
|
434
|
+
.digest();
|
|
435
|
+
let tag = "";
|
|
436
|
+
for (let i = 0; i < 6; i++)
|
|
437
|
+
tag += String.fromCharCode(65 + (h[i] % 26));
|
|
438
|
+
return tag;
|
|
439
|
+
}
|
|
440
|
+
customKey(name, style) {
|
|
441
|
+
return `${name}-${style}`;
|
|
442
|
+
}
|
|
443
|
+
// The variant to actually use for (name, style): the requested style if registered, else the
|
|
444
|
+
// family's Normal as a clean fallback (e.g. bold chosen but no bold file), else undefined when
|
|
445
|
+
// `name` is not a custom family at all.
|
|
446
|
+
resolveCustomStyle(name, style = FontStyle.Normal) {
|
|
447
|
+
if (!name)
|
|
448
|
+
return undefined;
|
|
449
|
+
if (this.customFonts.has(this.customKey(name, style)))
|
|
450
|
+
return style;
|
|
451
|
+
if (this.customFonts.has(this.customKey(name, FontStyle.Normal)))
|
|
452
|
+
return FontStyle.Normal;
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
getCustomFont(name, style = FontStyle.Normal) {
|
|
456
|
+
const resolved = this.resolveCustomStyle(name, style);
|
|
457
|
+
return resolved ? this.customFonts.get(this.customKey(name, resolved)) : undefined;
|
|
458
|
+
}
|
|
459
|
+
isCustomFont(name, style = FontStyle.Normal) {
|
|
460
|
+
return !!this.resolveCustomStyle(name, style);
|
|
461
|
+
}
|
|
462
|
+
// The page font resource for the resolved variant - same resolution as getCustomFont, so the
|
|
463
|
+
// selected Type0 object and the emitted glyph ids always come from the SAME font file.
|
|
464
|
+
getCustomFontResource(name, style = FontStyle.Normal) {
|
|
465
|
+
const resolved = this.resolveCustomStyle(name, style);
|
|
466
|
+
if (!resolved)
|
|
467
|
+
return undefined;
|
|
468
|
+
this.ensureEmitted(name, resolved);
|
|
469
|
+
return this.fonts.getFont(name, resolved);
|
|
470
|
+
}
|
|
471
|
+
// Encodes text as a hex Identity-H string for an embedded font's Tj operator: each codepoint
|
|
472
|
+
// becomes its 2-byte glyph id (CID == GID under /CIDToGIDMap /Identity).
|
|
473
|
+
encodeCustomText(name, text, style = FontStyle.Normal) {
|
|
474
|
+
const resolved = this.resolveCustomStyle(name, style);
|
|
475
|
+
if (!resolved)
|
|
476
|
+
return "";
|
|
477
|
+
this.ensureEmitted(name, resolved);
|
|
478
|
+
const key = this.customKey(name, resolved);
|
|
479
|
+
const ttf = this.customFonts.get(key);
|
|
480
|
+
const used = this.customFontEmit.get(key).used;
|
|
481
|
+
let hex = "";
|
|
482
|
+
for (const ch of text) {
|
|
483
|
+
const gid = ttf.getGlyphIndex(ch.codePointAt(0));
|
|
484
|
+
used.add(gid); // record the glyph so the subset keeps it
|
|
485
|
+
hex += gid.toString(16).padStart(4, "0").toUpperCase();
|
|
486
|
+
}
|
|
487
|
+
return hex;
|
|
488
|
+
}
|
|
489
|
+
// The SUBSET font program (only the used glyphs' outlines). Binary bytes survive as a latin1
|
|
490
|
+
// string - the final Windows-1252 encoder passes 0x00-0xFF through unchanged (see getArrayBuffer).
|
|
491
|
+
buildFontFile2(ttf, used) {
|
|
492
|
+
const bytes = (0, ttf_subsetter_1.subsetTTF)(ttf.getData(), used);
|
|
493
|
+
// /Length1 is the UNCOMPRESSED font-program length (required even when FlateDecode'd).
|
|
494
|
+
return this.stream(`/Length1 ${bytes.length}`, bytes);
|
|
495
|
+
}
|
|
496
|
+
buildFontDescriptor(name, ttf, fontFile) {
|
|
497
|
+
const [x0, y0, x1, y1] = ttf.bbox;
|
|
498
|
+
return (`<< /Type /FontDescriptor /FontName /${name} /Flags 4 ` +
|
|
499
|
+
`/FontBBox [${x0} ${y0} ${x1} ${y1}] /ItalicAngle 0 ` +
|
|
500
|
+
`/Ascent ${ttf.ascent} /Descent ${ttf.descent} /CapHeight ${ttf.ascent} /StemV 80 ` +
|
|
501
|
+
`/FontFile2 ${fontFile} 0 R >>`);
|
|
502
|
+
}
|
|
503
|
+
// CIDToGIDMap /Identity means CID == GID. /W lists only the USED glyphs' widths (sparse, one
|
|
504
|
+
// `gid [w]` entry each); everything else falls back to /DW.
|
|
505
|
+
buildCIDFont(name, ttf, descriptor, used) {
|
|
506
|
+
const all = ttf.glyphWidths();
|
|
507
|
+
const w = [...used]
|
|
508
|
+
.sort((a, b) => a - b)
|
|
509
|
+
.map((g) => { var _a; return `${g} [${(_a = all[g]) !== null && _a !== void 0 ? _a : 1000}]`; })
|
|
510
|
+
.join(" ");
|
|
511
|
+
return (`<< /Type /Font /Subtype /CIDFontType2 /BaseFont /${name} ` +
|
|
512
|
+
`/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> ` +
|
|
513
|
+
`/FontDescriptor ${descriptor} 0 R /CIDToGIDMap /Identity ` +
|
|
514
|
+
`/DW 1000 /W [${w}] >>`);
|
|
515
|
+
}
|
|
516
|
+
// Maps glyph id -> Unicode so the text stays copy-/searchable (rendering doesn't need it). Only
|
|
517
|
+
// the used glyphs are listed, to match the subset.
|
|
518
|
+
buildToUnicode(ttf, used) {
|
|
519
|
+
const hex4 = (n) => n.toString(16).padStart(4, "0").toUpperCase();
|
|
520
|
+
const rev = ttf.reverseCmap();
|
|
521
|
+
const entries = [...used]
|
|
522
|
+
.sort((a, b) => a - b)
|
|
523
|
+
.filter((g) => rev.has(g))
|
|
524
|
+
.map((g) => [g, rev.get(g)]);
|
|
525
|
+
const blocks = [];
|
|
526
|
+
for (let i = 0; i < entries.length; i += 100) {
|
|
527
|
+
const block = entries.slice(i, i + 100);
|
|
528
|
+
const lines = block.map(([gid, code]) => `<${hex4(gid)}> <${hex4(code)}>`);
|
|
529
|
+
blocks.push(`${block.length} beginbfchar\n${lines.join("\n")}\nendbfchar`);
|
|
530
|
+
}
|
|
531
|
+
const body = `/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n` +
|
|
532
|
+
`/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n` +
|
|
533
|
+
`/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n` +
|
|
534
|
+
`1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n${blocks.join("\n")}\n` +
|
|
535
|
+
`endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend`;
|
|
536
|
+
return this.stream("", Buffer.from(body, "latin1"));
|
|
537
|
+
}
|
|
538
|
+
buildType0(name, cidFont, toUnicode) {
|
|
539
|
+
return (`<< /Type /Font /Subtype /Type0 /BaseFont /${name} /Encoding /Identity-H ` +
|
|
540
|
+
`/DescendantFonts [${cidFont} 0 R] /ToUnicode ${toUnicode} 0 R >>`);
|
|
541
|
+
}
|
|
542
|
+
// Returns the current width of a text, included kernings
|
|
543
|
+
getStringWidth(text, fontFamily, fontSize, fontStyle) {
|
|
544
|
+
let width = 0;
|
|
545
|
+
// We must calculate each sign
|
|
546
|
+
for (let i = 0; i < text.length; i++) {
|
|
547
|
+
const char = text[i];
|
|
548
|
+
const nextChar = text[i + 1] || null;
|
|
549
|
+
// Get signs width
|
|
550
|
+
const charWidth = this.getCharWidth(char, fontSize, undefined, fontFamily, fontStyle);
|
|
551
|
+
width += charWidth;
|
|
552
|
+
// If a next sign available calculate the kerning
|
|
553
|
+
if (nextChar) {
|
|
554
|
+
const kerning = this.getKerning(char, nextChar, undefined, fontFamily, fontStyle);
|
|
555
|
+
width += kerning * fontSize; // Kerning must be scaled with the font size
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return width;
|
|
559
|
+
}
|
|
560
|
+
getCharCode(char) {
|
|
561
|
+
return char.charCodeAt(0).toString();
|
|
562
|
+
}
|
|
563
|
+
getAVMParserByFont(fullFontName, fontName, fontStyle) {
|
|
564
|
+
if (!fullFontName && (!fontName || !fontStyle)) {
|
|
565
|
+
throw new Error("No font family is given. Please set a full font name or a font with font style");
|
|
566
|
+
}
|
|
567
|
+
let result;
|
|
568
|
+
if (fullFontName) {
|
|
569
|
+
result = this.afmParsers.find((f) => f.fullFontName === fullFontName);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
result = this.afmParsers.find((f) => f.fontName === fontName && f.fontStyle === fontStyle);
|
|
573
|
+
}
|
|
574
|
+
if (!result)
|
|
575
|
+
throw new Error(`Cannot find a parser for the given font family ${fullFontName || fontName || "No given font"}`);
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
// Methode zur Berechnung der Zeichenbreite anhand der Schriftgröße
|
|
579
|
+
getCharWidth(char, fontSize, fullFontName, fontName, fontStyle) {
|
|
580
|
+
const ttf = this.getCustomFont(fullFontName !== null && fullFontName !== void 0 ? fullFontName : fontName, fontStyle);
|
|
581
|
+
if (ttf)
|
|
582
|
+
return ttf.getCharWidth(char, fontSize);
|
|
583
|
+
const currentParser = this.getAVMParserByFont(fullFontName, fontName, fontStyle);
|
|
584
|
+
const advanceWidth = currentParser.parser.getAdvanceWidth(char);
|
|
585
|
+
// Normally we got still zero. TODO: Return a alternative width like the "space"
|
|
586
|
+
if (!advanceWidth) {
|
|
587
|
+
throw new Error(`Kein Metrik-Eintrag für Zeichen: ${char} ${this.getCharCode(char)}`);
|
|
588
|
+
}
|
|
589
|
+
// Width of the character multiplied by the font size (scaled proportionally)
|
|
590
|
+
return (advanceWidth / 1000) * fontSize;
|
|
591
|
+
}
|
|
592
|
+
// Method to get the kerning, if available, between two signs
|
|
593
|
+
getKerning(char, nextChar, fullFontName, fontName, fontStyle) {
|
|
594
|
+
// TrueType kerning (the kern/GPOS tables) is not wired up yet - no kerning for custom fonts.
|
|
595
|
+
if (this.getCustomFont(fullFontName !== null && fullFontName !== void 0 ? fullFontName : fontName, fontStyle))
|
|
596
|
+
return 0;
|
|
597
|
+
const currentParser = this.getAVMParserByFont(fullFontName, fontName, fontStyle);
|
|
598
|
+
return currentParser.parser.getKerning(char, nextChar) / 1000;
|
|
599
|
+
}
|
|
600
|
+
// Returns all fonts
|
|
601
|
+
getAllFontsRaw() {
|
|
602
|
+
return this.fonts.getAllFonts();
|
|
603
|
+
}
|
|
604
|
+
// Returns all images
|
|
605
|
+
getAllImagesRaw() {
|
|
606
|
+
return this.images.getAllImages();
|
|
607
|
+
}
|
|
608
|
+
// Returns all rendered objects as a string
|
|
609
|
+
getRenderedObjects() {
|
|
610
|
+
let result = "";
|
|
611
|
+
this.objectPositions = [];
|
|
612
|
+
this.objects.forEach((content, index) => {
|
|
613
|
+
const position = result.length + this.getHeader().length; // Calculate positions after the header
|
|
614
|
+
this.objectPositions.push(position);
|
|
615
|
+
result += `${index + 1} 0 obj\n${content}\nendobj\n`;
|
|
616
|
+
});
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
// Creates the cross-reference table
|
|
620
|
+
getXRefTable() {
|
|
621
|
+
let xref = "xref\n";
|
|
622
|
+
xref += `0 ${this.objects.length + 1}\n`;
|
|
623
|
+
xref += `0000000000 65535 f \n`; // Free object
|
|
624
|
+
this.objectPositions.forEach((pos) => {
|
|
625
|
+
xref += `${pos.toString().padStart(10, "0")} 00000 n \n`;
|
|
626
|
+
});
|
|
627
|
+
return xref;
|
|
628
|
+
}
|
|
629
|
+
// Calculates the position of the XRef table and returns the trailer
|
|
630
|
+
getTrailerAndXRef(startxref) {
|
|
631
|
+
const objectCount = this.getObjectCount();
|
|
632
|
+
const root = this.objects.findIndex((f) => f.toLowerCase().includes("catalog")) + 1;
|
|
633
|
+
// A fresh document uses the same hash for both /ID strings; they only diverge on an update.
|
|
634
|
+
const id = this.documentId ? ` /ID [${this.contentId()} ${this.contentId()}]` : "";
|
|
635
|
+
return `trailer\n<< /Size ${objectCount + 1} /Root ${root} 0 R${id} >>\nstartxref\n${startxref}\n%%EOF`;
|
|
636
|
+
}
|
|
637
|
+
contentId() {
|
|
638
|
+
return `<${(0, crypto_1.createHash)("md5").update(this.objects.join("")).digest("hex").toUpperCase()}>`;
|
|
639
|
+
}
|
|
640
|
+
// Returns the number of objects
|
|
641
|
+
getObjectCount() {
|
|
642
|
+
return this.objects.length;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
exports.PDFObjectManager = PDFObjectManager;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RendererRegistry = void 0;
|
|
4
|
+
class RendererRegistry {
|
|
5
|
+
static register(elementClass, renderer) {
|
|
6
|
+
if (!RendererRegistry.renderers.has(elementClass)) {
|
|
7
|
+
RendererRegistry.renderers.set(elementClass, renderer);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
// Keyed on the element's constructor, so it only needs an object - not `any`.
|
|
11
|
+
static getRenderer(element) {
|
|
12
|
+
return RendererRegistry.renderers.get(element.constructor);
|
|
13
|
+
}
|
|
14
|
+
static isRendererAsync(renderer) {
|
|
15
|
+
return renderer.constructor.name === "AsyncFunction";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.RendererRegistry = RendererRegistry;
|
|
19
|
+
RendererRegistry.renderers = new Map();
|