cvdl-ts 1.0.24 → 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.
@@ -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,11 +24,51 @@ 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"));
31
- const cartesian = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
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
+ }
71
+ const cartesian = (...a) => a.reduce((a, b) => a.flatMap((d) => b.map((e) => [d, e].flat())));
32
72
  class FontDict {
33
73
  constructor() {
34
74
  this.fonts = new Map();
@@ -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 layout = Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.header_layout_schema, section.data, data_schema.header_schema, bindings), column_width, fontDict), fontDict);
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
- // 3. Render the item
98
- const layout = Layout.computeBoxes(Layout.normalize(Layout.instantiate(layout_schema.item_layout_schema, item, data_schema.item_schema, bindings), column_width, fontDict), fontDict);
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
  }