cvdl-ts 1.0.25 → 1.0.26
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/dist/AnyLayout.d.ts +3 -1
- package/dist/AnyLayout.js +148 -4
- package/dist/Elem.js +1 -1
- package/dist/Layout.d.ts +1 -0
- package/dist/PdfLayout.js +35 -12
- package/package.json +4 -2
package/dist/AnyLayout.d.ts
CHANGED
|
@@ -28,11 +28,13 @@ export type RenderProps = {
|
|
|
28
28
|
bindings: Map<string, unknown>;
|
|
29
29
|
storage: Storage;
|
|
30
30
|
fontDict?: FontDict;
|
|
31
|
+
incremental?: boolean;
|
|
31
32
|
};
|
|
33
|
+
export declare function resetIncrementalCaches(): void;
|
|
32
34
|
export declare class FontDict {
|
|
33
35
|
fonts: Map<string, fontkit.Font>;
|
|
34
36
|
constructor();
|
|
35
37
|
load_fonts(storage: Storage): Promise<this>;
|
|
36
38
|
get_font(name: string): fontkit.Font;
|
|
37
39
|
}
|
|
38
|
-
export declare function render({ resume, layout_schemas, data_schemas, resume_layout, bindings, fontDict, }: RenderProps): Layout.RenderedLayout[];
|
|
40
|
+
export declare function render({ resume, layout_schemas, data_schemas, resume_layout, bindings, fontDict, incremental, }: RenderProps): Layout.RenderedLayout[];
|
package/dist/AnyLayout.js
CHANGED
|
@@ -24,10 +24,50 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.FontDict = void 0;
|
|
27
|
+
exports.resetIncrementalCaches = resetIncrementalCaches;
|
|
27
28
|
exports.render = render;
|
|
28
29
|
const ResumeLayout_1 = require("./ResumeLayout");
|
|
29
30
|
const fontkit = __importStar(require("fontkit"));
|
|
30
31
|
const Layout = __importStar(require("./Layout"));
|
|
32
|
+
const blockCache = new Map();
|
|
33
|
+
let flowPlacementCache = {
|
|
34
|
+
order: [],
|
|
35
|
+
signatures: [],
|
|
36
|
+
offsets: [],
|
|
37
|
+
heights: [],
|
|
38
|
+
};
|
|
39
|
+
const stableBindingsSignature = (bindings) => {
|
|
40
|
+
const sortedEntries = Array.from(bindings.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
41
|
+
return JSON.stringify(sortedEntries);
|
|
42
|
+
};
|
|
43
|
+
const stableSignature = (value) => {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.stringify(value);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const getOrComputeBlock = (key, signature, compute, stats) => {
|
|
52
|
+
const cached = blockCache.get(key);
|
|
53
|
+
if (cached && cached.signature === signature) {
|
|
54
|
+
stats.hits += 1;
|
|
55
|
+
return cached.layout;
|
|
56
|
+
}
|
|
57
|
+
const layout = compute();
|
|
58
|
+
blockCache.set(key, { signature, layout });
|
|
59
|
+
stats.misses += 1;
|
|
60
|
+
return layout;
|
|
61
|
+
};
|
|
62
|
+
function resetIncrementalCaches() {
|
|
63
|
+
blockCache.clear();
|
|
64
|
+
flowPlacementCache = {
|
|
65
|
+
order: [],
|
|
66
|
+
signatures: [],
|
|
67
|
+
offsets: [],
|
|
68
|
+
heights: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
31
71
|
const cartesian = (...a) => a.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat())));
|
|
32
72
|
class FontDict {
|
|
33
73
|
constructor() {
|
|
@@ -56,7 +96,8 @@ class FontDict {
|
|
|
56
96
|
}
|
|
57
97
|
}
|
|
58
98
|
exports.FontDict = FontDict;
|
|
59
|
-
function render({ resume, layout_schemas, data_schemas, resume_layout, bindings, fontDict, }) {
|
|
99
|
+
function render({ resume, layout_schemas, data_schemas, resume_layout, bindings, fontDict, incremental = true, }) {
|
|
100
|
+
var _a;
|
|
60
101
|
// Compute the total usable width by subtracting the margins from the document width
|
|
61
102
|
const width = resume_layout.width -
|
|
62
103
|
(resume_layout.margin.left + resume_layout.margin.right);
|
|
@@ -65,6 +106,14 @@ function render({ resume, layout_schemas, data_schemas, resume_layout, bindings,
|
|
|
65
106
|
? width
|
|
66
107
|
: width - (0, ResumeLayout_1.vertical_margin)(resume_layout.column_type) / 2.0;
|
|
67
108
|
const layouts = [];
|
|
109
|
+
const usedKeys = new Set();
|
|
110
|
+
const stats = { hits: 0, misses: 0 };
|
|
111
|
+
const blockOrder = [];
|
|
112
|
+
const blockSignatures = [];
|
|
113
|
+
const blockHeights = [];
|
|
114
|
+
const bindingsSignature = stableBindingsSignature(bindings);
|
|
115
|
+
const fontSignature = stableSignature(Array.from(fontDict.fonts.keys()).sort());
|
|
116
|
+
let firstDirtyBlock = incremental ? -1 : 0;
|
|
68
117
|
console.info("Rendering sections...");
|
|
69
118
|
for (const section of resume.sections) {
|
|
70
119
|
// Render Section Header
|
|
@@ -85,8 +134,33 @@ function render({ resume, layout_schemas, data_schemas, resume_layout, bindings,
|
|
|
85
134
|
}
|
|
86
135
|
start_time = Date.now();
|
|
87
136
|
// 3. Render the header
|
|
88
|
-
const
|
|
137
|
+
const headerKey = `${section.section_name}::header`;
|
|
138
|
+
const headerSignature = stableSignature({
|
|
139
|
+
column_width,
|
|
140
|
+
fontSignature,
|
|
141
|
+
bindingsSignature,
|
|
142
|
+
layout: layout_schema.header_layout_schema,
|
|
143
|
+
fields: data_schema.header_schema,
|
|
144
|
+
data: section.data,
|
|
145
|
+
});
|
|
146
|
+
usedKeys.add(headerKey);
|
|
147
|
+
const blockIndex = blockOrder.length;
|
|
148
|
+
blockOrder.push(headerKey);
|
|
149
|
+
blockSignatures.push(headerSignature);
|
|
150
|
+
if (incremental &&
|
|
151
|
+
firstDirtyBlock === -1 &&
|
|
152
|
+
(flowPlacementCache.order[blockIndex] !== headerKey ||
|
|
153
|
+
flowPlacementCache.signatures[blockIndex] !== headerSignature)) {
|
|
154
|
+
firstDirtyBlock = blockIndex;
|
|
155
|
+
}
|
|
156
|
+
const layout = incremental
|
|
157
|
+
? getOrComputeBlock(headerKey, headerSignature, () => Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.header_layout_schema, section.data, data_schema.header_schema, bindings), column_width, fontDict), fontDict), stats)
|
|
158
|
+
: (() => {
|
|
159
|
+
stats.misses += 1;
|
|
160
|
+
return Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.header_layout_schema, section.data, data_schema.header_schema, bindings), column_width, fontDict), fontDict);
|
|
161
|
+
})();
|
|
89
162
|
layout.path = { tag: "section", section: section.section_name };
|
|
163
|
+
blockHeights.push(layout.bounding_box.height() + layout.margin.top + layout.margin.bottom);
|
|
90
164
|
console.info("Header is computed");
|
|
91
165
|
layouts.push(layout);
|
|
92
166
|
end_time = Date.now();
|
|
@@ -94,14 +168,84 @@ function render({ resume, layout_schemas, data_schemas, resume_layout, bindings,
|
|
|
94
168
|
start_time = Date.now();
|
|
95
169
|
// Render Section Items
|
|
96
170
|
for (const [index, item] of section.items.entries()) {
|
|
97
|
-
|
|
98
|
-
const
|
|
171
|
+
const itemKey = `${section.section_name}::item::${(_a = item.id) !== null && _a !== void 0 ? _a : index}`;
|
|
172
|
+
const itemSignature = stableSignature({
|
|
173
|
+
column_width,
|
|
174
|
+
fontSignature,
|
|
175
|
+
bindingsSignature,
|
|
176
|
+
layout: layout_schema.item_layout_schema,
|
|
177
|
+
fields: data_schema.item_schema,
|
|
178
|
+
data: item,
|
|
179
|
+
});
|
|
180
|
+
usedKeys.add(itemKey);
|
|
181
|
+
const blockIndex = blockOrder.length;
|
|
182
|
+
blockOrder.push(itemKey);
|
|
183
|
+
blockSignatures.push(itemSignature);
|
|
184
|
+
if (incremental &&
|
|
185
|
+
firstDirtyBlock === -1 &&
|
|
186
|
+
(flowPlacementCache.order[blockIndex] !== itemKey ||
|
|
187
|
+
flowPlacementCache.signatures[blockIndex] !== itemSignature)) {
|
|
188
|
+
firstDirtyBlock = blockIndex;
|
|
189
|
+
}
|
|
190
|
+
const layout = incremental
|
|
191
|
+
? getOrComputeBlock(itemKey, itemSignature, () => Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.item_layout_schema, item, data_schema.item_schema, bindings), column_width, fontDict), fontDict), stats)
|
|
192
|
+
: (() => {
|
|
193
|
+
stats.misses += 1;
|
|
194
|
+
return Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.item_layout_schema, item, data_schema.item_schema, bindings), column_width, fontDict), fontDict);
|
|
195
|
+
})();
|
|
99
196
|
layout.path = { tag: "item", section: section.section_name, item: index };
|
|
197
|
+
blockHeights.push(layout.bounding_box.height() + layout.margin.top + layout.margin.bottom);
|
|
100
198
|
layouts.push(layout);
|
|
101
199
|
}
|
|
102
200
|
end_time = Date.now();
|
|
103
201
|
console.info(`Item rendering time: ${end_time - start_time}ms for section ${section.section_name}`);
|
|
104
202
|
}
|
|
203
|
+
const hasSameBlockShape = flowPlacementCache.order.length === blockOrder.length &&
|
|
204
|
+
firstDirtyBlock === -1;
|
|
205
|
+
if (!incremental || !hasSameBlockShape) {
|
|
206
|
+
if (firstDirtyBlock === -1) {
|
|
207
|
+
firstDirtyBlock = Math.min(blockOrder.length, flowPlacementCache.order.length);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const offsets = new Array(blockOrder.length).fill(0);
|
|
211
|
+
if (incremental && firstDirtyBlock > 0) {
|
|
212
|
+
for (let i = 0; i < firstDirtyBlock; i++) {
|
|
213
|
+
offsets[i] = flowPlacementCache.offsets[i];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!incremental || firstDirtyBlock !== -1) {
|
|
217
|
+
const start = !incremental ? 0 : Math.max(0, firstDirtyBlock);
|
|
218
|
+
let cursor = start === 0 ? 0 : offsets[start - 1] + blockHeights[start - 1];
|
|
219
|
+
for (let i = start; i < blockOrder.length; i++) {
|
|
220
|
+
offsets[i] = cursor;
|
|
221
|
+
cursor += blockHeights[i];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
for (let i = 0; i < blockOrder.length; i++) {
|
|
226
|
+
offsets[i] = flowPlacementCache.offsets[i];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
230
|
+
layouts[i].flow_offset_y = offsets[i];
|
|
231
|
+
}
|
|
232
|
+
if (incremental) {
|
|
233
|
+
for (const key of Array.from(blockCache.keys())) {
|
|
234
|
+
if (!usedKeys.has(key)) {
|
|
235
|
+
blockCache.delete(key);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
flowPlacementCache = {
|
|
239
|
+
order: blockOrder,
|
|
240
|
+
signatures: blockSignatures,
|
|
241
|
+
offsets,
|
|
242
|
+
heights: blockHeights,
|
|
243
|
+
};
|
|
244
|
+
console.info(`Incremental block cache: ${stats.hits} hit(s), ${stats.misses} miss(es), firstDirtyBlock=${firstDirtyBlock}, size=${blockCache.size}`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.info(`Full render mode: ${stats.misses} block(s) recomputed`);
|
|
248
|
+
}
|
|
105
249
|
console.log("Position calculations are completed.");
|
|
106
250
|
return layouts;
|
|
107
251
|
}
|
package/dist/Elem.js
CHANGED
|
@@ -359,7 +359,7 @@ function instantiate(e, section, fields, bindings) {
|
|
|
359
359
|
return e;
|
|
360
360
|
}
|
|
361
361
|
const itemType = fields.find((f) => f.name === e.item);
|
|
362
|
-
if (itemType.type.tag === "MarkdownString") {
|
|
362
|
+
if ((itemType === null || itemType === void 0 ? void 0 : itemType.type.tag) === "MarkdownString") {
|
|
363
363
|
e.is_markdown = true;
|
|
364
364
|
}
|
|
365
365
|
const text = section.fields[e.item];
|
package/dist/Layout.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export type RenderedElem = Elem.t & {
|
|
|
43
43
|
};
|
|
44
44
|
export type RenderedLayout = (RenderedStack | RenderedRow | RenderedElem) & {
|
|
45
45
|
path?: ElementPath;
|
|
46
|
+
flow_offset_y?: number;
|
|
46
47
|
};
|
|
47
48
|
export declare function default_(tag: string): Stack.t | Row.t | Elem.t;
|
|
48
49
|
export declare function empty(): Layout;
|
package/dist/PdfLayout.js
CHANGED
|
@@ -32,7 +32,9 @@ const AnyLayout_1 = require("./AnyLayout");
|
|
|
32
32
|
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
33
33
|
const Resume = __importStar(require("./Resume"));
|
|
34
34
|
const _1 = require(".");
|
|
35
|
+
const Box_1 = require("./Box");
|
|
35
36
|
const render = async ({ resume_name, resume, data_schemas, layout_schemas, resume_layout, bindings, storage, fontDict, }) => {
|
|
37
|
+
var _a;
|
|
36
38
|
let start_time = Date.now();
|
|
37
39
|
if (!resume && !resume_name) {
|
|
38
40
|
throw "Rendering requires either resume_name or resume";
|
|
@@ -95,9 +97,12 @@ const render = async ({ resume_name, resume, data_schemas, layout_schemas, resum
|
|
|
95
97
|
fontDict: fontDict,
|
|
96
98
|
};
|
|
97
99
|
for (const layout of layouts) {
|
|
98
|
-
(
|
|
99
|
-
tracker
|
|
100
|
-
|
|
100
|
+
const flowOffset = (_a = layout.flow_offset_y) !== null && _a !== void 0 ? _a : tracker.height;
|
|
101
|
+
(0, exports.renderSectionLayout)(layout, { ...tracker, height: flowOffset });
|
|
102
|
+
if (layout.flow_offset_y === undefined) {
|
|
103
|
+
tracker.height +=
|
|
104
|
+
layout.bounding_box.height() + layout.margin.top + layout.margin.bottom;
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
console.log("Rendering is completed. Saving the document...");
|
|
103
108
|
console.log("Document is saved to output.pdf");
|
|
@@ -122,20 +127,38 @@ const getPageContainer = (page, tracker) => {
|
|
|
122
127
|
return tracker.pageContainer;
|
|
123
128
|
};
|
|
124
129
|
const mergeSpans = (spans) => {
|
|
130
|
+
if (spans.length === 0) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const firstSpan = spans[0];
|
|
134
|
+
if (!firstSpan) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
125
137
|
const merged_spans = [];
|
|
126
|
-
let currentSpan =
|
|
138
|
+
let currentSpan = firstSpan;
|
|
127
139
|
for (let i = 1; i < spans.length; i++) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
currentSpan.bbox.
|
|
140
|
+
const nextSpan = spans[i];
|
|
141
|
+
if (!nextSpan) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const hasBothBBoxes = Boolean(currentSpan.bbox && nextSpan.bbox);
|
|
145
|
+
if (hasBothBBoxes &&
|
|
146
|
+
currentSpan.bbox.top_left.y === nextSpan.bbox.top_left.y &&
|
|
147
|
+
currentSpan.font === nextSpan.font &&
|
|
148
|
+
currentSpan.is_code === nextSpan.is_code &&
|
|
149
|
+
currentSpan.is_bold === nextSpan.is_bold &&
|
|
150
|
+
currentSpan.is_italic === nextSpan.is_italic) {
|
|
151
|
+
currentSpan = {
|
|
152
|
+
...currentSpan,
|
|
153
|
+
text: currentSpan.text + nextSpan.text,
|
|
154
|
+
bbox: currentSpan.bbox && nextSpan.bbox
|
|
155
|
+
? new Box_1.Box(currentSpan.bbox.top_left, nextSpan.bbox.bottom_right)
|
|
156
|
+
: currentSpan.bbox,
|
|
157
|
+
};
|
|
135
158
|
}
|
|
136
159
|
else {
|
|
137
160
|
merged_spans.push(currentSpan);
|
|
138
|
-
currentSpan =
|
|
161
|
+
currentSpan = nextSpan;
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
164
|
merged_spans.push(currentSpan);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cvdl-ts",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "Typescript Implementation of CVDL Compiler",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"start": "ts-node src/index.ts",
|
|
27
|
-
"cli": "ts-node src/cli.ts"
|
|
27
|
+
"cli": "ts-node src/cli.ts",
|
|
28
|
+
"bench:incremental": "npm run build && node scripts/benchmark_incremental.cjs",
|
|
29
|
+
"bench:incremental:full": "npm run build && BENCH_PROFILE=full node scripts/benchmark_incremental.cjs"
|
|
28
30
|
}
|
|
29
31
|
}
|