@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.
Files changed (139) hide show
  1. package/README.md +171 -0
  2. package/dist/api/args.d.ts +10 -0
  3. package/dist/api/args.js +13 -0
  4. package/dist/api/color.d.ts +24 -0
  5. package/dist/api/color.js +215 -0
  6. package/dist/api/content.d.ts +36 -0
  7. package/dist/api/content.js +50 -0
  8. package/dist/api/descriptor.d.ts +25 -0
  9. package/dist/api/descriptor.js +71 -0
  10. package/dist/api/index.d.ts +8 -0
  11. package/dist/api/index.js +27 -0
  12. package/dist/api/insets.d.ts +16 -0
  13. package/dist/api/insets.js +21 -0
  14. package/dist/api/layout.d.ts +72 -0
  15. package/dist/api/layout.js +99 -0
  16. package/dist/api/structure.d.ts +80 -0
  17. package/dist/api/structure.js +125 -0
  18. package/dist/api/table.d.ts +37 -0
  19. package/dist/api/table.js +87 -0
  20. package/dist/api/text.d.ts +28 -0
  21. package/dist/api/text.js +61 -0
  22. package/dist/assets/Courier-Bold.afm +342 -0
  23. package/dist/assets/Courier-BoldOblique.afm +342 -0
  24. package/dist/assets/Courier-Oblique.afm +342 -0
  25. package/dist/assets/Courier.afm +342 -0
  26. package/dist/assets/Helvetica-Bold.afm +2827 -0
  27. package/dist/assets/Helvetica-BoldOblique.afm +2827 -0
  28. package/dist/assets/Helvetica-Oblique.afm +3051 -0
  29. package/dist/assets/Helvetica.afm +3051 -0
  30. package/dist/assets/Symbol.afm +213 -0
  31. package/dist/assets/Times-Bold.afm +2588 -0
  32. package/dist/assets/Times-BoldItalic.afm +2384 -0
  33. package/dist/assets/Times-Italic.afm +2667 -0
  34. package/dist/assets/Times-Roman.afm +2419 -0
  35. package/dist/assets/ZapfDingbats.afm +225 -0
  36. package/dist/assets/agl.txt +695 -0
  37. package/dist/common/color.d.ts +37 -0
  38. package/dist/common/color.js +62 -0
  39. package/dist/constants/page-sizes.d.ts +44 -0
  40. package/dist/constants/page-sizes.js +92 -0
  41. package/dist/constants/pdf-parts.d.ts +5 -0
  42. package/dist/constants/pdf-parts.js +8 -0
  43. package/dist/elements/container-element.d.ts +35 -0
  44. package/dist/elements/container-element.js +91 -0
  45. package/dist/elements/image-element.d.ts +56 -0
  46. package/dist/elements/image-element.js +151 -0
  47. package/dist/elements/index.d.ts +10 -0
  48. package/dist/elements/index.js +28 -0
  49. package/dist/elements/layout/deferred-element.d.ts +19 -0
  50. package/dist/elements/layout/deferred-element.js +33 -0
  51. package/dist/elements/layout/expanded-element.d.ts +30 -0
  52. package/dist/elements/layout/expanded-element.js +68 -0
  53. package/dist/elements/layout/padding-element.d.ts +29 -0
  54. package/dist/elements/layout/padding-element.js +76 -0
  55. package/dist/elements/layout/repeating-header-element.d.ts +29 -0
  56. package/dist/elements/layout/repeating-header-element.js +60 -0
  57. package/dist/elements/layout/sized-container-element.d.ts +14 -0
  58. package/dist/elements/layout/sized-container-element.js +37 -0
  59. package/dist/elements/line-element.d.ts +31 -0
  60. package/dist/elements/line-element.js +44 -0
  61. package/dist/elements/page-element.d.ts +58 -0
  62. package/dist/elements/page-element.js +90 -0
  63. package/dist/elements/pdf-document-element.d.ts +13 -0
  64. package/dist/elements/pdf-document-element.js +22 -0
  65. package/dist/elements/pdf-element.d.ts +67 -0
  66. package/dist/elements/pdf-element.js +55 -0
  67. package/dist/elements/rectangle-element.d.ts +55 -0
  68. package/dist/elements/rectangle-element.js +120 -0
  69. package/dist/elements/row-element.d.ts +42 -0
  70. package/dist/elements/row-element.js +54 -0
  71. package/dist/elements/text-element.d.ts +59 -0
  72. package/dist/elements/text-element.js +137 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.js +21 -0
  75. package/dist/ir/display-list.d.ts +74 -0
  76. package/dist/ir/display-list.js +2 -0
  77. package/dist/layout/box-constraints.d.ts +56 -0
  78. package/dist/layout/box-constraints.js +73 -0
  79. package/dist/layout/fragmentation.d.ts +42 -0
  80. package/dist/layout/fragmentation.js +61 -0
  81. package/dist/renderer/container-renderer.d.ts +6 -0
  82. package/dist/renderer/container-renderer.js +30 -0
  83. package/dist/renderer/deferred-renderer.d.ts +6 -0
  84. package/dist/renderer/deferred-renderer.js +25 -0
  85. package/dist/renderer/expanded-renderer.d.ts +6 -0
  86. package/dist/renderer/expanded-renderer.js +23 -0
  87. package/dist/renderer/image-renderer.d.ts +6 -0
  88. package/dist/renderer/image-renderer.js +85 -0
  89. package/dist/renderer/index.d.ts +10 -0
  90. package/dist/renderer/index.js +26 -0
  91. package/dist/renderer/line-renderer.d.ts +6 -0
  92. package/dist/renderer/line-renderer.js +30 -0
  93. package/dist/renderer/padding-renderer.d.ts +6 -0
  94. package/dist/renderer/padding-renderer.js +23 -0
  95. package/dist/renderer/page-renderer.d.ts +5 -0
  96. package/dist/renderer/page-renderer.js +81 -0
  97. package/dist/renderer/pdf-backend.d.ts +45 -0
  98. package/dist/renderer/pdf-backend.js +184 -0
  99. package/dist/renderer/pdf-config.d.ts +8 -0
  100. package/dist/renderer/pdf-config.js +17 -0
  101. package/dist/renderer/pdf-document-class.d.ts +42 -0
  102. package/dist/renderer/pdf-document-class.js +118 -0
  103. package/dist/renderer/pdf-document-renderer.d.ts +20 -0
  104. package/dist/renderer/pdf-document-renderer.js +104 -0
  105. package/dist/renderer/pdf-renderer.d.ts +5 -0
  106. package/dist/renderer/pdf-renderer.js +92 -0
  107. package/dist/renderer/rectangle-renderer.d.ts +8 -0
  108. package/dist/renderer/rectangle-renderer.js +61 -0
  109. package/dist/renderer/repeating-header-renderer.d.ts +6 -0
  110. package/dist/renderer/repeating-header-renderer.js +28 -0
  111. package/dist/renderer/row-renderer.d.ts +6 -0
  112. package/dist/renderer/row-renderer.js +30 -0
  113. package/dist/renderer/text-renderer.d.ts +9 -0
  114. package/dist/renderer/text-renderer.js +125 -0
  115. package/dist/text/line-breaker.d.ts +40 -0
  116. package/dist/text/line-breaker.js +106 -0
  117. package/dist/utils/afm-parser.d.ts +12 -0
  118. package/dist/utils/afm-parser.js +91 -0
  119. package/dist/utils/flex-layout.d.ts +53 -0
  120. package/dist/utils/flex-layout.js +119 -0
  121. package/dist/utils/font-metrics.d.ts +12 -0
  122. package/dist/utils/font-metrics.js +2 -0
  123. package/dist/utils/font-path.d.ts +1 -0
  124. package/dist/utils/font-path.js +19 -0
  125. package/dist/utils/image-helper.d.ts +43 -0
  126. package/dist/utils/image-helper.js +206 -0
  127. package/dist/utils/pdf-object-manager.d.ts +97 -0
  128. package/dist/utils/pdf-object-manager.js +645 -0
  129. package/dist/utils/renderer-registry.d.ts +6 -0
  130. package/dist/utils/renderer-registry.js +19 -0
  131. package/dist/utils/ttf-parser.d.ts +29 -0
  132. package/dist/utils/ttf-parser.js +191 -0
  133. package/dist/utils/ttf-subsetter.d.ts +1 -0
  134. package/dist/utils/ttf-subsetter.js +161 -0
  135. package/dist/utils/utf8-to-windows1252-encoder.d.ts +2 -0
  136. package/dist/utils/utf8-to-windows1252-encoder.js +55 -0
  137. package/dist/validators/element-validator.d.ts +8 -0
  138. package/dist/validators/element-validator.js +61 -0
  139. package/package.json +50 -0
@@ -0,0 +1,29 @@
1
+ export declare class TTFParser {
2
+ private data;
3
+ unitsPerEm: number;
4
+ ascent: number;
5
+ descent: number;
6
+ bbox: [number, number, number, number];
7
+ private numGlyphs;
8
+ private numHMetrics;
9
+ private advanceWidths;
10
+ private cmap;
11
+ private cmapGroups;
12
+ private tables;
13
+ constructor(data: Buffer);
14
+ getData(): Buffer;
15
+ glyphCount(): number;
16
+ glyphWidths(): number[];
17
+ reverseCmap(): Map<number, number>;
18
+ private toGlyphSpace;
19
+ getGlyphIndex(codePoint: number): number;
20
+ getAdvanceWidth(glyphIndex: number): number;
21
+ getCharWidth(char: string, fontSize: number): number;
22
+ getStringWidth(text: string, fontSize: number): number;
23
+ private table;
24
+ private readTableDirectory;
25
+ private readHmtx;
26
+ private readCmap;
27
+ private readCmapFormat4;
28
+ private readCmapFormat12;
29
+ }
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ // Parses the metric tables of a TrueType (.ttf) font - the binary counterpart to AFMParser.
3
+ // Slice 1: glyph advance widths (hmtx) + the Unicode→glyph map (cmap), enough to compute
4
+ // text width the same way AFMParser does for the standard-14. Embedding/subsetting come later.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TTFParser = void 0;
7
+ class TTFParser {
8
+ constructor(data) {
9
+ this.data = data;
10
+ this.unitsPerEm = 1000;
11
+ // FontDescriptor metrics, in PDF glyph space (1000 units / em).
12
+ this.ascent = 0;
13
+ this.descent = 0;
14
+ this.bbox = [0, 0, 0, 0];
15
+ this.numGlyphs = 0;
16
+ this.numHMetrics = 0;
17
+ this.advanceWidths = []; // per glyph id, in font units
18
+ this.cmap = new Map(); // codepoint → glyph id (format 4)
19
+ this.cmapGroups = []; // format 12, searched on lookup
20
+ this.tables = {};
21
+ this.readTableDirectory();
22
+ const head = this.table("head").offset;
23
+ this.unitsPerEm = this.data.readUInt16BE(head + 18);
24
+ this.numGlyphs = this.data.readUInt16BE(this.table("maxp").offset + 4);
25
+ this.numHMetrics = this.data.readUInt16BE(this.table("hhea").offset + 34);
26
+ this.bbox = [
27
+ this.toGlyphSpace(this.data.readInt16BE(head + 36)), // xMin
28
+ this.toGlyphSpace(this.data.readInt16BE(head + 38)), // yMin
29
+ this.toGlyphSpace(this.data.readInt16BE(head + 40)), // xMax
30
+ this.toGlyphSpace(this.data.readInt16BE(head + 42)), // yMax
31
+ ];
32
+ const hhea = this.table("hhea").offset;
33
+ this.ascent = this.toGlyphSpace(this.data.readInt16BE(hhea + 4));
34
+ this.descent = this.toGlyphSpace(this.data.readInt16BE(hhea + 6));
35
+ this.readHmtx();
36
+ this.readCmap();
37
+ }
38
+ // The raw font bytes, for the /FontFile2 stream.
39
+ getData() {
40
+ return this.data;
41
+ }
42
+ glyphCount() {
43
+ return this.numGlyphs;
44
+ }
45
+ // Advance width of every glyph in PDF glyph space (the /W array of the CIDFont).
46
+ glyphWidths() {
47
+ const out = [];
48
+ for (let g = 0; g < this.numGlyphs; g++) {
49
+ out.push(this.toGlyphSpace(this.getAdvanceWidth(g)));
50
+ }
51
+ return out;
52
+ }
53
+ // glyph id → Unicode codepoint, for the /ToUnicode CMap (BMP / format-4 coverage).
54
+ reverseCmap() {
55
+ const out = new Map();
56
+ this.cmap.forEach((gid, code) => {
57
+ if (!out.has(gid))
58
+ out.set(gid, code);
59
+ });
60
+ return out;
61
+ }
62
+ toGlyphSpace(v) {
63
+ return Math.round((v * 1000) / this.unitsPerEm);
64
+ }
65
+ // Codepoint → glyph id (0 = .notdef when the font has no glyph for it).
66
+ getGlyphIndex(codePoint) {
67
+ const g = this.cmap.get(codePoint);
68
+ if (g !== undefined)
69
+ return g;
70
+ for (const r of this.cmapGroups) {
71
+ if (codePoint >= r.start && codePoint <= r.end) {
72
+ return r.startGlyph + (codePoint - r.start);
73
+ }
74
+ }
75
+ return 0;
76
+ }
77
+ // Advance width of a glyph in font units. Glyphs past numHMetrics reuse the last advance.
78
+ getAdvanceWidth(glyphIndex) {
79
+ var _a;
80
+ const i = Math.min(glyphIndex, this.numHMetrics - 1);
81
+ return (_a = this.advanceWidths[i]) !== null && _a !== void 0 ? _a : 0;
82
+ }
83
+ // Char width at fontSize, scaled from font units (em = unitsPerEm) to points.
84
+ getCharWidth(char, fontSize) {
85
+ const units = this.getAdvanceWidth(this.getGlyphIndex(char.codePointAt(0)));
86
+ return (units / this.unitsPerEm) * fontSize;
87
+ }
88
+ // String width at fontSize.
89
+ getStringWidth(text, fontSize) {
90
+ let width = 0;
91
+ for (const ch of text)
92
+ width += this.getCharWidth(ch, fontSize);
93
+ return width;
94
+ }
95
+ table(tag) {
96
+ const t = this.tables[tag];
97
+ if (!t)
98
+ throw new Error(`TTF: missing required table "${tag}"`);
99
+ return t;
100
+ }
101
+ readTableDirectory() {
102
+ const numTables = this.data.readUInt16BE(4);
103
+ let p = 12; // after the offset table (sfntVersion + numTables + 3 fields)
104
+ for (let i = 0; i < numTables; i++) {
105
+ const tag = this.data.toString("latin1", p, p + 4);
106
+ this.tables[tag] = {
107
+ offset: this.data.readUInt32BE(p + 8),
108
+ length: this.data.readUInt32BE(p + 12),
109
+ };
110
+ p += 16;
111
+ }
112
+ }
113
+ // hmtx is numHMetrics {advanceWidth, lsb} pairs, then lsb-only entries that reuse the last advance.
114
+ readHmtx() {
115
+ const o = this.table("hmtx").offset;
116
+ let last = 0;
117
+ for (let i = 0; i < this.numGlyphs; i++) {
118
+ if (i < this.numHMetrics)
119
+ last = this.data.readUInt16BE(o + i * 4);
120
+ this.advanceWidths.push(last);
121
+ }
122
+ }
123
+ readCmap() {
124
+ const base = this.table("cmap").offset;
125
+ const numTables = this.data.readUInt16BE(base + 2);
126
+ // Prefer the Windows Unicode BMP subtable (3,1); accept full-Unicode (3,10) or any (0,x).
127
+ let subtable = -1;
128
+ let p = base + 4;
129
+ for (let i = 0; i < numTables; i++) {
130
+ const platform = this.data.readUInt16BE(p);
131
+ const encoding = this.data.readUInt16BE(p + 2);
132
+ const offset = this.data.readUInt32BE(p + 4);
133
+ const unicode = (platform === 3 && (encoding === 1 || encoding === 10)) || platform === 0;
134
+ if (unicode) {
135
+ subtable = base + offset;
136
+ if (platform === 3 && encoding === 1)
137
+ break; // best fit, stop looking
138
+ }
139
+ p += 8;
140
+ }
141
+ if (subtable < 0)
142
+ return;
143
+ const format = this.data.readUInt16BE(subtable);
144
+ if (format === 4)
145
+ this.readCmapFormat4(subtable);
146
+ else if (format === 12)
147
+ this.readCmapFormat12(subtable);
148
+ }
149
+ // Format 4: segment mapping. Four parallel arrays keyed by segment; the glyph comes either
150
+ // from idDelta or, when idRangeOffset != 0, from the glyphIdArray it points into.
151
+ readCmapFormat4(o) {
152
+ const segCountX2 = this.data.readUInt16BE(o + 6);
153
+ const endCodes = o + 14;
154
+ const startCodes = endCodes + segCountX2 + 2; // +2 reservedPad
155
+ const idDeltas = startCodes + segCountX2;
156
+ const idRangeOffsets = idDeltas + segCountX2;
157
+ for (let i = 0; i < segCountX2 / 2; i++) {
158
+ const end = this.data.readUInt16BE(endCodes + i * 2);
159
+ const start = this.data.readUInt16BE(startCodes + i * 2);
160
+ const delta = this.data.readUInt16BE(idDeltas + i * 2);
161
+ const rangeOffset = this.data.readUInt16BE(idRangeOffsets + i * 2);
162
+ for (let c = start; c <= end && c !== 0xffff; c++) {
163
+ let glyph;
164
+ if (rangeOffset === 0) {
165
+ glyph = (c + delta) & 0xffff;
166
+ }
167
+ else {
168
+ glyph = this.data.readUInt16BE(idRangeOffsets + i * 2 + rangeOffset + (c - start) * 2);
169
+ if (glyph !== 0)
170
+ glyph = (glyph + delta) & 0xffff;
171
+ }
172
+ if (glyph !== 0)
173
+ this.cmap.set(c, glyph);
174
+ }
175
+ }
176
+ }
177
+ // Format 12: sequential groups for full Unicode (incl. astral). Kept un-expanded.
178
+ readCmapFormat12(o) {
179
+ const nGroups = this.data.readUInt32BE(o + 12);
180
+ let p = o + 16;
181
+ for (let i = 0; i < nGroups; i++) {
182
+ this.cmapGroups.push({
183
+ start: this.data.readUInt32BE(p),
184
+ end: this.data.readUInt32BE(p + 4),
185
+ startGlyph: this.data.readUInt32BE(p + 8),
186
+ });
187
+ p += 12;
188
+ }
189
+ }
190
+ }
191
+ exports.TTFParser = TTFParser;
@@ -0,0 +1 @@
1
+ export declare function subsetTTF(data: Buffer, used: Set<number>): Buffer;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ // Builds a subset TrueType font that KEEPS the original glyph ids: the PDF content stream emits
3
+ // original gids and /W + CIDToGIDMap=Identity stay valid, so only the font program shrinks. We drop
4
+ // the `glyf` outlines of unused glyphs (the bulk of the file) and rebuild `loca`; every other table
5
+ // is copied verbatim. The used-glyph set is closed over composite-glyph components first, so a glyph
6
+ // built from others (e.g. "ä") keeps its parts. Fonts without `glyf`/`loca` (CFF/OTF) pass through.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.subsetTTF = subsetTTF;
9
+ const u32 = (n) => n >>> 0;
10
+ /** TrueType table checksum: sum of big-endian uint32 over the 4-byte-padded table data. */
11
+ function checksum(buf) {
12
+ let sum = 0;
13
+ for (let i = 0; i < buf.length; i += 4)
14
+ sum = u32(sum + buf.readUInt32BE(i));
15
+ return sum;
16
+ }
17
+ /** Right-pad to a 4-byte boundary (table data is aligned in the sfnt). */
18
+ function pad4(buf) {
19
+ const rem = buf.length % 4;
20
+ return rem === 0 ? buf : Buffer.concat([buf, Buffer.alloc(4 - rem)]);
21
+ }
22
+ function subsetTTF(data, used) {
23
+ const numTables = data.readUInt16BE(4);
24
+ const dir = new Map();
25
+ let p = 12;
26
+ for (let i = 0; i < numTables; i++) {
27
+ dir.set(data.toString("latin1", p, p + 4), {
28
+ offset: data.readUInt32BE(p + 8),
29
+ length: data.readUInt32BE(p + 12),
30
+ });
31
+ p += 16;
32
+ }
33
+ // No glyf/loca → not a TrueType-outline font we can subset this way; hand it back unchanged.
34
+ if (!dir.has("glyf") || !dir.has("loca") || !dir.has("head") || !dir.has("maxp"))
35
+ return data;
36
+ const headOff = dir.get("head").offset;
37
+ const longLoca = data.readInt16BE(headOff + 50) === 1;
38
+ const numGlyphs = data.readUInt16BE(dir.get("maxp").offset + 4);
39
+ const locaOff = dir.get("loca").offset;
40
+ const glyfOff = dir.get("glyf").offset;
41
+ // Original glyph offsets (numGlyphs + 1 entries; short loca stores half-offsets).
42
+ const loca = [];
43
+ for (let g = 0; g <= numGlyphs; g++) {
44
+ loca.push(longLoca ? data.readUInt32BE(locaOff + g * 4) : data.readUInt16BE(locaOff + g * 2) * 2);
45
+ }
46
+ const glyphBytes = (g) => data.subarray(glyfOff + loca[g], glyfOff + loca[g + 1]);
47
+ // Close the used set over composite components (and always keep .notdef = gid 0).
48
+ const keep = new Set([0]);
49
+ const stack = [...used, 0];
50
+ while (stack.length) {
51
+ const g = stack.pop();
52
+ if (g < 0 || g >= numGlyphs || keep.has(g))
53
+ continue;
54
+ keep.add(g);
55
+ const gd = glyphBytes(g);
56
+ if (gd.length >= 10 && gd.readInt16BE(0) < 0) {
57
+ // Composite glyph: walk the component records to collect the referenced gids.
58
+ let q = 10; // numberOfContours(2) + xMin/yMin/xMax/yMax(8)
59
+ for (;;) {
60
+ const flags = gd.readUInt16BE(q);
61
+ stack.push(gd.readUInt16BE(q + 2));
62
+ q += 4 + (flags & 0x0001 ? 4 : 2); // ARG_1_AND_2_ARE_WORDS
63
+ if (flags & 0x0008)
64
+ q += 2; // WE_HAVE_A_SCALE
65
+ else if (flags & 0x0040)
66
+ q += 4; // WE_HAVE_AN_X_AND_Y_SCALE
67
+ else if (flags & 0x0080)
68
+ q += 8; // WE_HAVE_A_TWO_BY_TWO
69
+ if (!(flags & 0x0020))
70
+ break; // MORE_COMPONENTS
71
+ }
72
+ }
73
+ }
74
+ // Rebuild glyf (kept glyphs only, original numbering) + a long-format loca.
75
+ const parts = [];
76
+ const newLoca = Buffer.alloc((numGlyphs + 1) * 4);
77
+ let off = 0;
78
+ for (let g = 0; g < numGlyphs; g++) {
79
+ newLoca.writeUInt32BE(off, g * 4);
80
+ if (keep.has(g)) {
81
+ let gd = Buffer.from(glyphBytes(g));
82
+ if (gd.length % 2 === 1)
83
+ gd = Buffer.concat([gd, Buffer.alloc(1)]); // keep offsets even
84
+ parts.push(gd);
85
+ off += gd.length;
86
+ }
87
+ }
88
+ newLoca.writeUInt32BE(off, numGlyphs * 4);
89
+ const newGlyf = Buffer.concat(parts);
90
+ // head copy with indexToLocFormat = 1 (long); checkSumAdjustment zeroed, fixed up at the end.
91
+ const newHead = Buffer.from(data.subarray(headOff, headOff + dir.get("head").length));
92
+ newHead.writeInt16BE(1, 50);
93
+ newHead.writeUInt32BE(0, 8);
94
+ // Tables a PDF-embedded CIDFontType2 doesn't need - layout (GSUB/GPOS…), hinting hints and the
95
+ // signature are dead weight; dropping them is most of the size win. `post` is reduced to format 3
96
+ // (header only, no glyph names). cmap/name/OS-2 are kept for a still-valid standalone font.
97
+ const DROP = new Set([
98
+ "GSUB",
99
+ "GPOS",
100
+ "GDEF",
101
+ "BASE",
102
+ "JSTF",
103
+ "kern",
104
+ "DSIG",
105
+ "hdmx",
106
+ "LTSH",
107
+ "VDMX",
108
+ "PCLT",
109
+ "gasp",
110
+ ]);
111
+ const tags = [...dir.keys()].filter((t) => !DROP.has(t)).sort();
112
+ const tables = tags.map((tag) => {
113
+ let body;
114
+ if (tag === "glyf")
115
+ body = newGlyf;
116
+ else if (tag === "loca")
117
+ body = newLoca;
118
+ else if (tag === "head")
119
+ body = newHead;
120
+ else if (tag === "post") {
121
+ // Keep the 32-byte header, force version 3.0 (drops the per-glyph name list).
122
+ body = Buffer.from(data.subarray(dir.get("post").offset, dir.get("post").offset + 32));
123
+ body.writeUInt32BE(0x00030000, 0);
124
+ }
125
+ else {
126
+ const d = dir.get(tag);
127
+ body = data.subarray(d.offset, d.offset + d.length);
128
+ }
129
+ return { tag, body, length: body.length };
130
+ });
131
+ // sfnt: offset table + table directory + 4-byte-aligned table data.
132
+ const n = tables.length;
133
+ const maxPow2 = 1 << Math.floor(Math.log2(n));
134
+ const out = [];
135
+ const offsetTable = Buffer.alloc(12);
136
+ offsetTable.writeUInt32BE(data.readUInt32BE(0), 0); // sfntVersion
137
+ offsetTable.writeUInt16BE(n, 4);
138
+ offsetTable.writeUInt16BE(maxPow2 * 16, 6); // searchRange
139
+ offsetTable.writeUInt16BE(Math.floor(Math.log2(n)), 8); // entrySelector
140
+ offsetTable.writeUInt16BE(n * 16 - maxPow2 * 16, 10); // rangeShift
141
+ out.push(offsetTable);
142
+ const dirBuf = Buffer.alloc(n * 16);
143
+ let tableOffset = 12 + n * 16;
144
+ const dataBufs = [];
145
+ tables.forEach((t, i) => {
146
+ const padded = pad4(t.body);
147
+ dirBuf.write(t.tag, i * 16, 4, "latin1");
148
+ dirBuf.writeUInt32BE(checksum(padded), i * 16 + 4);
149
+ dirBuf.writeUInt32BE(tableOffset, i * 16 + 8);
150
+ dirBuf.writeUInt32BE(t.length, i * 16 + 12); // real (unpadded) length
151
+ tableOffset += padded.length;
152
+ dataBufs.push(padded);
153
+ });
154
+ out.push(dirBuf, ...dataBufs);
155
+ const font = Buffer.concat(out);
156
+ // head.checkSumAdjustment = 0xB1B0AFBA - checksum(whole font). Locate head in the assembled font.
157
+ const headIdx = tables.findIndex((t) => t.tag === "head");
158
+ const headPos = dirBuf.readUInt32BE(headIdx * 16 + 8);
159
+ font.writeUInt32BE(u32(0xb1b0afba - checksum(font)), headPos + 8);
160
+ return font;
161
+ }
@@ -0,0 +1,2 @@
1
+ /** Encodes a JavaScript string to a Windows-1252 byte buffer (the PDF text encoding). */
2
+ export declare function getArrayBuffer(data: string): ArrayBuffer;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getArrayBuffer = getArrayBuffer;
4
+ // Windows-1252 equals Latin-1 (codepoint == byte) EXCEPT in 0x80-0x9F, where it carries
5
+ // printable punctuation - the Euro sign, smart quotes, dashes, ellipsis, ... - instead of
6
+ // C1 controls. Those glyphs sit at Unicode codepoints far outside 0x00-0xFF, so a naive
7
+ // low-byte cast turns "€" (U+20AC) into 0xAC ("¬"). This table maps each back to its
8
+ // Windows-1252 byte. (The font metrics already resolve these via the Adobe Glyph List, so
9
+ // only the emitted byte was wrong.)
10
+ const CP1252_FROM_UNICODE = {
11
+ 0x20ac: 0x80, // € Euro
12
+ 0x201a: 0x82, // ‚ single low-9 quote
13
+ 0x0192: 0x83, // ƒ florin
14
+ 0x201e: 0x84, // „ double low-9 quote
15
+ 0x2026: 0x85, // … ellipsis
16
+ 0x2020: 0x86, // † dagger
17
+ 0x2021: 0x87, // ‡ double dagger
18
+ 0x02c6: 0x88, // ˆ circumflex
19
+ 0x2030: 0x89, // ‰ per mille
20
+ 0x0160: 0x8a, // Š S caron
21
+ 0x2039: 0x8b, // ‹ single left angle quote
22
+ 0x0152: 0x8c, // Œ OE ligature
23
+ 0x017d: 0x8e, // Ž Z caron
24
+ 0x2018: 0x91, // ' left single quote
25
+ 0x2019: 0x92, // ' right single quote
26
+ 0x201c: 0x93, // " left double quote
27
+ 0x201d: 0x94, // " right double quote
28
+ 0x2022: 0x95, // • bullet
29
+ 0x2013: 0x96, // – en dash
30
+ 0x2014: 0x97, // — em dash
31
+ 0x02dc: 0x98, // ˜ small tilde
32
+ 0x2122: 0x99, // ™ trademark
33
+ 0x0161: 0x9a, // š s caron
34
+ 0x203a: 0x9b, // › single right angle quote
35
+ 0x0153: 0x9c, // œ oe ligature
36
+ 0x017e: 0x9e, // ž z caron
37
+ 0x0178: 0x9f, // Ÿ Y diaeresis
38
+ };
39
+ /** Encodes a JavaScript string to a Windows-1252 byte buffer (the PDF text encoding). */
40
+ function getArrayBuffer(data) {
41
+ const u8 = new Uint8Array(data.length);
42
+ for (let i = 0; i < data.length; i++) {
43
+ const code = data.charCodeAt(i);
44
+ if (code <= 0xff) {
45
+ u8[i] = code; // Latin-1 range: codepoint is the Windows-1252 byte
46
+ }
47
+ else if (CP1252_FROM_UNICODE[code] !== undefined) {
48
+ u8[i] = CP1252_FROM_UNICODE[code];
49
+ }
50
+ else {
51
+ u8[i] = 0x3f; // "?" - not representable in Windows-1252
52
+ }
53
+ }
54
+ return u8.buffer;
55
+ }
@@ -0,0 +1,8 @@
1
+ import { PDFDocumentElement } from "../elements/pdf-document-element";
2
+ import { PDFElement, FlexiblePDFElement, SizedPDFElement } from "../elements/pdf-element";
3
+ export declare class Validator {
4
+ static validateDocument(document: PDFDocumentElement): void;
5
+ static validateElement(element: PDFElement): void;
6
+ static validateSizedElement(element: SizedPDFElement): void;
7
+ static validateFlexElement(element: FlexiblePDFElement): void;
8
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Validator = void 0;
4
+ const pdf_document_element_1 = require("../elements/pdf-document-element");
5
+ const pdf_element_1 = require("../elements/pdf-element");
6
+ class Validator {
7
+ static validateDocument(document) {
8
+ // More validation will be added later...
9
+ document.getProps().children.forEach((page) => {
10
+ page.getProps().children.forEach((element) => {
11
+ if (element instanceof pdf_document_element_1.PDFDocumentElement) {
12
+ throw new Error("PDFDocument cannot be nested inside another element.");
13
+ }
14
+ });
15
+ });
16
+ }
17
+ static validateElement(element) {
18
+ // Structural validation
19
+ if (element instanceof pdf_document_element_1.PDFDocumentElement) {
20
+ throw new Error("PDFDocument cannot be nested inside another element.");
21
+ }
22
+ // Layout validation: geometry comes from the typed getSize(), not the props bag.
23
+ if (element instanceof pdf_element_1.SizedPDFElement) {
24
+ const { x, y, width, height } = element.getSize();
25
+ if (x < 0 || y < 0) {
26
+ throw new Error(`Element ${element.constructor.name} has invalid coordinates (x: ${x}, y: ${y})`);
27
+ }
28
+ // 0 is legitimate (a hairline divider, an empty spacer); only a NEGATIVE size is invalid.
29
+ if ((width !== undefined && width < 0) || (height !== undefined && height < 0)) {
30
+ throw new Error(`Element ${element.constructor.name} has invalid size (width: ${width}, height: ${height})`);
31
+ }
32
+ }
33
+ // Logical validation: Flexible and fixed elements
34
+ if (element instanceof pdf_element_1.FlexiblePDFElement) {
35
+ this.validateFlexElement(element);
36
+ }
37
+ }
38
+ static validateSizedElement(element) {
39
+ const { x, y, width, height } = element.getSize();
40
+ if (x < 0 || y < 0) {
41
+ throw new Error(`Element ${element.constructor.name} has invalid coordinates (x: ${x}, y: ${y})`);
42
+ }
43
+ // A size must be set, but 0 is legitimate (a hairline divider); only NEGATIVE is invalid.
44
+ if (width === undefined || height === undefined || width < 0 || height < 0) {
45
+ throw new Error(`Element ${element.constructor.name} has invalid size (width: ${width}, height: ${height})`);
46
+ }
47
+ }
48
+ static validateFlexElement(element) {
49
+ // Ensure flexible elements have valid flex values
50
+ if (element.getFlex() <= 0) {
51
+ throw new Error(`Flexible element ${element.constructor.name} has invalid flex value`);
52
+ }
53
+ // Ensure a flexible element does not contain another flexible element
54
+ if ((0, pdf_element_1.hasChildProp)(element)) {
55
+ if (element.child instanceof pdf_element_1.FlexiblePDFElement) {
56
+ throw new Error(`Flexible element ${element.constructor.name} cannot hold another flexible element`);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ exports.Validator = Validator;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@jasy/pdf",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Declarative, component-based PDF generation in pure TypeScript - Flutter-style components, real AFM font metrics, a hand-rolled PDF writer. No headless browser, no Java.",
5
+ "keywords": [
6
+ "declarative",
7
+ "factur-x",
8
+ "invoice",
9
+ "layout",
10
+ "pagination",
11
+ "pdf",
12
+ "pdf-a",
13
+ "pdf-generation",
14
+ "typescript",
15
+ "zugferd"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Florian Heuberger",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "dependencies": {
25
+ "jimp": "^1.6.0"
26
+ },
27
+ "devDependencies": {
28
+ "@jimp/types": "^1.6.0",
29
+ "@types/node": "^25.9.3",
30
+ "@vitest/coverage-v8": "^2.1.1",
31
+ "ncp": "^2.0.0",
32
+ "oxfmt": "^0.55.0",
33
+ "oxlint": "^1.70.0",
34
+ "typescript": "^5.5.4",
35
+ "vitest": "^2.1.1"
36
+ },
37
+ "scripts": {
38
+ "copy-assets": "ncp ./src/lib/assets ./dist/assets",
39
+ "cli": "pnpm --filter @jasy/cli dev",
40
+ "cli:watch": "pnpm --filter @jasy/cli watch",
41
+ "play": "pnpm --filter @jasy/playground play",
42
+ "test": "vitest",
43
+ "test:coverage": "vitest --coverage",
44
+ "build": "tsc && pnpm run copy-assets",
45
+ "lint": "oxlint",
46
+ "lint:fix": "oxlint --fix",
47
+ "fmt": "oxfmt",
48
+ "fmt:check": "oxfmt --check"
49
+ }
50
+ }