cvdl-ts 1.0.11 → 1.0.13

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/Layout.js CHANGED
@@ -23,495 +23,407 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Elem = exports.ColorMap = exports.Row = exports.Stack = exports.SectionLayout = void 0;
27
- const Margin = __importStar(require("./Margin"));
28
- const Alignment = __importStar(require("./Alignment"));
26
+ exports.ColorMap = exports.computeTextboxPositions = exports.computeBoxes = exports.breakLines = exports.fillFonts = exports.normalize = exports.scaleWidth = exports.boundWidth = exports.instantiate = exports.isInstantiated = exports.totalElementsWidth = exports.withWidth = exports.withAlignment = exports.withMargin = exports.fonts = exports.isRef = exports.isFill = exports.isContainer = exports.toJson = exports.tag_ = exports.type_ = exports.fromJson = exports.empty = exports.default_ = void 0;
29
27
  const Width = __importStar(require("./Width"));
30
- const Font_1 = require("./Font");
31
- const Resume_1 = require("./Resume");
28
+ const Font = __importStar(require("./Font"));
32
29
  const Box_1 = require("./Box");
33
- const AnyLayout_1 = require("./AnyLayout");
34
30
  const Point_1 = require("./Point");
35
- class SectionLayout {
36
- constructor(inner) {
37
- this.bounding_box = null;
38
- this.inner = inner;
31
+ const Stack = __importStar(require("./Stack"));
32
+ const Row = __importStar(require("./Row"));
33
+ const Elem = __importStar(require("./Elem"));
34
+ function default_(tag) {
35
+ switch (tag) {
36
+ case "Stack": return Stack.default_();
37
+ case "FlexRow": return Row.default_();
38
+ case "FrozenRow": return Row.withFrozen(Row.default_(), true);
39
+ case "Text": return Elem.default_();
40
+ case "Ref": return Elem.asRef(Elem.default_());
39
41
  }
40
- copy() {
41
- return new SectionLayout(this.inner.copy());
42
- }
43
- static constrMap(tag) {
44
- switch (tag) {
45
- case "Stack": return Stack.default_();
46
- case "FlexRow": return Row.default_();
47
- case "FrozenRow": {
48
- const row = Row.default_();
49
- row.is_frozen = true;
50
- return row;
51
- }
52
- case "Text": return Elem.default_();
53
- case "Ref": {
54
- const elem = Elem.default_();
55
- elem.is_ref = true;
56
- return elem;
57
- }
58
- }
59
- }
60
- static empty() {
61
- return new SectionLayout(Stack.default_());
62
- }
63
- static fromJson(json) {
64
- var _a;
65
- const key = Object.keys(json)[0];
66
- switch (key) {
67
- case 'Stack':
68
- case 'FlexRow':
69
- case 'FrozenRow': {
70
- const container = SectionLayout.constrMap(key);
71
- container.elements = json[key].elements.map((element) => SectionLayout.fromJson(element));
72
- container.margin = json[key].margin;
73
- container.alignment = json[key].alignment;
74
- container.width = Width.fromJson(json[key].width);
75
- return new SectionLayout(container);
76
- }
77
- case 'Ref':
78
- case 'Text': {
79
- const inner = SectionLayout.constrMap(key);
80
- inner.item = json[key].item;
81
- inner.margin = json[key].margin;
82
- inner.alignment = json[key].alignment;
83
- inner.width = Width.fromJson(json[key].width);
84
- inner.text_width = Width.fromJson(json[key].text_width);
85
- inner.font = Font_1.Font.fromJson(json[key].font);
86
- inner.url = json[key].url;
87
- inner.background_color = (_a = json[key].background_color) !== null && _a !== void 0 ? _a : "Transparent";
88
- return new SectionLayout(inner);
89
- }
90
- }
91
- throw new Error(`Invalid layout ${key}`);
92
- }
93
- toJson() {
94
- switch (this.type_()) {
95
- case "Stack":
96
- case "Row": {
97
- const container = this.inner;
98
- return {
99
- [this.tag_()]: {
100
- elements: container.elements.map(e => e.toJson()),
101
- margin: container.margin,
102
- alignment: container.alignment,
103
- width: Width.toJson(container.width),
104
- },
105
- };
106
- }
107
- case "Elem": {
108
- const elem = this.inner;
109
- return {
110
- [this.tag_()]: {
111
- item: elem.item,
112
- margin: elem.margin,
113
- alignment: elem.alignment,
114
- width: Width.toJson(elem.width),
115
- text_width: Width.toJson(elem.text_width),
116
- font: elem.font.toJson(),
117
- url: elem.url,
118
- background_color: elem.background_color,
119
- },
120
- };
121
- }
42
+ }
43
+ exports.default_ = default_;
44
+ function empty() {
45
+ return default_("Stack");
46
+ }
47
+ exports.empty = empty;
48
+ function fromJson(json) {
49
+ var _a;
50
+ const key = Object.keys(json)[0];
51
+ switch (key) {
52
+ case 'Stack':
53
+ case 'FlexRow':
54
+ case 'FrozenRow': {
55
+ const container = default_(key);
56
+ container.elements = json[key].elements.map((element) => fromJson(element));
57
+ container.margin = json[key].margin;
58
+ container.alignment = json[key].alignment;
59
+ container.width = Width.fromJson(json[key].width);
60
+ return container;
61
+ }
62
+ case 'Ref':
63
+ case 'Text': {
64
+ const inner = default_(key);
65
+ inner.item = json[key].item;
66
+ inner.margin = json[key].margin;
67
+ inner.alignment = json[key].alignment;
68
+ inner.width = Width.fromJson(json[key].width);
69
+ inner.text_width = Width.fromJson(json[key].text_width);
70
+ inner.font = Font.fromJson(json[key].font);
71
+ inner.url = json[key].url;
72
+ inner.background_color = (_a = json[key].background_color) !== null && _a !== void 0 ? _a : "Transparent";
73
+ return inner;
74
+ }
75
+ }
76
+ throw new Error(`Invalid layout ${key}`);
77
+ }
78
+ exports.fromJson = fromJson;
79
+ function type_(l) {
80
+ return l.tag;
81
+ }
82
+ exports.type_ = type_;
83
+ function tag_(l) {
84
+ switch (l.tag) {
85
+ case "Stack": return "Stack";
86
+ case "Row": {
87
+ const row = l;
88
+ return row.is_frozen ? "FrozenRow" : "FlexRow";
122
89
  }
123
- }
124
- width() {
125
- return this.inner.width;
126
- }
127
- is_container() {
128
- return this.inner.tag === "Stack" || this.inner.tag === "Row";
129
- }
130
- is_fill() {
131
- switch (this.type_()) {
132
- case "Stack":
133
- return this.inner.is_fill && this.inner.elements.map(e => e.is_fill()).reduce((a, b) => a && b, true);
134
- case "Row":
135
- return this.inner.is_fill && this.inner.elements.map(e => e.is_fill()).reduce((a, b) => a && b, true);
136
- case "Elem":
137
- return this.inner.is_fill;
90
+ case "Elem": {
91
+ const elem = l;
92
+ return elem.is_ref ? "Ref" : "Text";
138
93
  }
139
94
  }
140
- is_ref() {
141
- return this.inner.tag === "Elem" && this.inner.is_ref;
142
- }
143
- type_() {
144
- return this.inner.tag;
145
- }
146
- tag_() {
147
- switch (this.type_()) {
148
- case "Stack":
149
- return "Stack";
150
- case "Row":
151
- return this.inner.is_frozen ? "FrozenRow" : "FlexRow";
152
- case "Elem":
153
- return this.is_ref() ? "Ref" : "Text";
95
+ }
96
+ exports.tag_ = tag_;
97
+ function toJson(l) {
98
+ switch (l.tag) {
99
+ case "Stack":
100
+ case "Row": {
101
+ const container = l;
102
+ return {
103
+ [tag_(container)]: {
104
+ elements: container.elements.map(e => toJson(e)),
105
+ margin: container.margin,
106
+ alignment: container.alignment,
107
+ width: Width.toJson(container.width),
108
+ },
109
+ };
110
+ }
111
+ case "Elem": {
112
+ const elem = l;
113
+ return {
114
+ [tag_(elem)]: {
115
+ item: elem.item,
116
+ margin: elem.margin,
117
+ alignment: elem.alignment,
118
+ width: Width.toJson(elem.width),
119
+ text_width: Width.toJson(elem.text_width),
120
+ font: elem.font,
121
+ url: elem.url,
122
+ background_color: elem.background_color,
123
+ },
124
+ };
154
125
  }
155
126
  }
156
- fonts() {
157
- switch (this.type_()) {
158
- case "Stack":
159
- return this.inner.elements.map(e => e.fonts()).reduce((a, b) => a.concat(b), []);
160
- case "Row":
161
- return this.inner.elements.map(e => e.fonts()).reduce((a, b) => a.concat(b), []);
162
- case "Elem":
163
- return [this.inner.font];
164
- }
127
+ }
128
+ exports.toJson = toJson;
129
+ function isContainer(l) {
130
+ return l.tag === "Stack" || l.tag === "Row";
131
+ }
132
+ exports.isContainer = isContainer;
133
+ function isFill(l) {
134
+ switch (l.tag) {
135
+ case "Stack":
136
+ return l.is_fill && l.elements.map(e => isFill(e)).reduce((a, b) => a && b, true);
137
+ case "Row":
138
+ return l.is_fill && l.elements.map(e => isFill(e)).reduce((a, b) => a && b, true);
139
+ case "Elem":
140
+ return l.is_fill;
165
141
  }
166
- with_margin(margin) {
167
- this.inner = this.inner.with_margin(margin);
168
- return this;
142
+ }
143
+ exports.isFill = isFill;
144
+ function isRef(l) {
145
+ return l.tag === "Elem" && l.is_ref;
146
+ }
147
+ exports.isRef = isRef;
148
+ function fonts(l) {
149
+ switch (l.tag) {
150
+ case "Stack":
151
+ return l.elements.map(e => fonts(e)).reduce((a, b) => a.concat(b), []);
152
+ case "Row":
153
+ return l.elements.map(e => fonts(e)).reduce((a, b) => a.concat(b), []);
154
+ case "Elem":
155
+ return [l.font];
169
156
  }
170
- with_alignment(alignment) {
171
- this.inner = this.inner.with_alignment(alignment);
172
- return this;
157
+ }
158
+ exports.fonts = fonts;
159
+ function withMargin(l, margin) {
160
+ switch (l.tag) {
161
+ case "Stack":
162
+ return Stack.withMargin(l, margin);
163
+ case "Row":
164
+ return Row.withMargin(l, margin);
165
+ case "Elem":
166
+ return Elem.withMargin(l, margin);
173
167
  }
174
- with_width(width) {
175
- this.inner = this.inner.with_width(width);
176
- return this;
168
+ }
169
+ exports.withMargin = withMargin;
170
+ function withAlignment(l, alignment) {
171
+ switch (l.tag) {
172
+ case "Stack":
173
+ return Stack.withAlignment(l, alignment);
174
+ case "Row":
175
+ return Row.withAlignment(l, alignment);
176
+ case "Elem":
177
+ return Elem.withAlignment(l, alignment);
177
178
  }
178
- total_elements_width() {
179
- if (this.is_container()) {
180
- return this.inner.elements.map(e => e.total_elements_width()).reduce((a, b) => a + b, 0.0);
181
- }
182
- else {
183
- return Width.get_fixed_unchecked(this.width());
184
- }
179
+ }
180
+ exports.withAlignment = withAlignment;
181
+ function withWidth(l, width) {
182
+ switch (l.tag) {
183
+ case "Stack":
184
+ return Stack.withWidth(l, width);
185
+ case "Row":
186
+ return Row.withWidth(l, width);
187
+ case "Elem":
188
+ return Elem.withWidth(l, width);
185
189
  }
186
- is_instantiated() {
187
- if (this.is_container()) {
188
- return this.inner.elements.map(e => e.is_instantiated()).reduce((a, b) => a && b, true);
189
- }
190
- else {
191
- return !this.inner.is_ref;
192
- }
190
+ }
191
+ exports.withWidth = withWidth;
192
+ function totalElementsWidth(l) {
193
+ switch (l.tag) {
194
+ case "Stack":
195
+ return l.elements.map(e => totalElementsWidth(e)).reduce((a, b) => a + b, 0.0);
196
+ case "Row":
197
+ return l.elements.map(e => totalElementsWidth(e)).reduce((a, b) => a + b, 0.0);
198
+ case "Elem":
199
+ return Width.get_fixed_unchecked(l.width);
193
200
  }
194
- instantiate(section) {
195
- if (this.is_container()) {
196
- return new SectionLayout(this.inner.instantiate(section));
197
- }
198
- else if (this.inner.is_ref) {
199
- return SectionLayout.instantiate_ref_element(this.inner, section);
200
- }
201
- else {
202
- return this;
203
- }
201
+ }
202
+ exports.totalElementsWidth = totalElementsWidth;
203
+ function isInstantiated(l) {
204
+ if (isContainer(l)) {
205
+ return l.elements.map(e => isInstantiated(e)).reduce((a, b) => a && b, true);
204
206
  }
205
- static instantiate_ref_element(element, section) {
206
- const text = section.get(element.item);
207
- if (text === undefined) {
208
- return new SectionLayout(new Stack([], Margin.default_(), Alignment.default_(), Width.default_()));
209
- }
210
- else {
211
- element.item = Resume_1.ItemContent.toString(text);
212
- element.is_ref = false;
213
- if (text.tag === "Url") {
214
- return new SectionLayout(element.with_url(text.value.url).with_item(text.value.text));
215
- }
216
- else {
217
- return new SectionLayout(element);
218
- }
219
- }
207
+ else {
208
+ return !isRef(l);
220
209
  }
221
- bound_width(width) {
222
- const bound = this.inner.width.tag === "Absolute" ? Math.min(this.inner.width.value, width)
223
- : this.inner.width.tag === "Fill" ? width
224
- : null;
225
- if (bound === null) {
226
- throw new Error("Cannot bound width of non-unitized widths!");
227
- }
228
- if (this.inner.tag === "Elem" && this.inner.is_ref) {
229
- throw new Error("Cannot propagate widths of uninstantiated layout");
230
- }
231
- this.inner = this.inner.bound_width(bound - this.inner.margin.left - this.inner.margin.right);
232
- return this;
210
+ }
211
+ exports.isInstantiated = isInstantiated;
212
+ function instantiate(l, section, fields) {
213
+ switch (l.tag) {
214
+ case "Stack":
215
+ return Stack.withElements(l, l.elements.map(e => instantiate(e, section, fields)));
216
+ case "Row":
217
+ return Row.withElements(l, l.elements.map(e => instantiate(e, section, fields)));
218
+ case "Elem":
219
+ return Elem.instantiate(l, section, fields);
233
220
  }
234
- scale_width(document_width) {
235
- if (this.is_ref()) {
236
- throw new Error("Cannot scale width of uninstantiated layout");
237
- }
238
- return new SectionLayout(this.inner.scale_width(document_width));
221
+ }
222
+ exports.instantiate = instantiate;
223
+ function boundWidth(l, width) {
224
+ const bound = l.width.tag === "Absolute" ? Math.min(l.width.value, width)
225
+ : l.width.tag === "Fill" ? width
226
+ : null;
227
+ if (bound === null) {
228
+ throw new Error("Cannot bound width of non-unitized widths!");
229
+ }
230
+ if (l.tag === "Elem" && l.is_ref) {
231
+ throw new Error("Cannot propagate widths of uninstantiated layout");
232
+ }
233
+ switch (l.tag) {
234
+ case "Stack":
235
+ return Stack.boundWidth(l, bound - l.margin.left - l.margin.right);
236
+ case "Row":
237
+ return Row.boundWidth(l, bound - l.margin.left - l.margin.right);
238
+ case "Elem":
239
+ return Elem.boundWidth(l, bound - l.margin.left - l.margin.right);
239
240
  }
240
- normalize(width, font_dict) {
241
- console.debug(`Normalizing document, checking if document is instantiated...`);
242
- if (!this.is_instantiated()) {
243
- throw Error("Cannot normalize uninstantiated layout");
244
- }
245
- console.debug("Document is instantiated. Scaling widths...");
246
- const scaled_layout = this.scale_width(width);
247
- console.debug("Widths are scaled. Bounding widths...");
248
- const bounded_layout = scaled_layout.bound_width(width);
249
- console.debug("Widths are bounded. Filling fonts...");
250
- const font_filled_layout = bounded_layout.fill_fonts(font_dict);
251
- console.debug("Fonts filled. Breaking lines...");
252
- const broken_layout = font_filled_layout.break_lines(font_dict);
253
- console.debug("Lines broken.");
254
- return broken_layout;
241
+ }
242
+ exports.boundWidth = boundWidth;
243
+ function scaleWidth(l, document_width) {
244
+ if (isRef(l)) {
245
+ throw new Error("Cannot scale width of uninstantiated layout");
246
+ }
247
+ switch (l.tag) {
248
+ case "Stack":
249
+ return Stack.scaleWidth(l, document_width);
250
+ case "Row":
251
+ return Row.scaleWidth(l, document_width);
252
+ case "Elem":
253
+ return Elem.scaleWidth(l, document_width);
255
254
  }
256
- fill_fonts(font_dict) {
257
- if (this.is_ref()) {
258
- throw new Error("Cannot fill fonts of uninstantiated layout");
259
- }
260
- switch (this.type_()) {
261
- case "Stack":
262
- case "Row":
263
- this.inner.elements = this.inner.elements.map(e => e.fill_fonts(font_dict));
264
- if (this.is_fill()) {
265
- const total_width = this.total_elements_width();
266
- if (total_width <= Width.get_fixed_unchecked(this.width())) {
267
- // throw `Cannot fill fonts of row with width ${JSON.stringify(this.width())} and total width ${total_width}`
268
- this.inner = this.inner.with_width(Width.absolute(total_width));
269
- }
255
+ }
256
+ exports.scaleWidth = scaleWidth;
257
+ function normalize(l, width, font_dict) {
258
+ console.debug(`Normalizing document, checking if document is instantiated...`);
259
+ if (!isInstantiated(l)) {
260
+ throw Error(`Cannot normalize uninstantiated layout ${JSON.stringify(l)}`);
261
+ }
262
+ console.debug("Document is instantiated. Scaling widths...");
263
+ const scaled_layout = scaleWidth(l, width);
264
+ console.debug("Widths are scaled. Bounding widths...");
265
+ const bounded_layout = boundWidth(scaled_layout, width);
266
+ console.debug("Widths are bounded. Filling fonts...");
267
+ const font_filled_layout = fillFonts(bounded_layout, font_dict);
268
+ console.debug("Fonts filled. Breaking lines...");
269
+ // const broken_layout = breakLines(font_filled_layout, font_dict);
270
+ console.debug("Lines broken.");
271
+ // return broken_layout;
272
+ return font_filled_layout;
273
+ }
274
+ exports.normalize = normalize;
275
+ function fillFonts(l, font_dict) {
276
+ if (isRef(l)) {
277
+ throw new Error("Cannot fill fonts of uninstantiated layout");
278
+ }
279
+ switch (l.tag) {
280
+ case "Stack":
281
+ case "Row": {
282
+ const filledFonts = l.elements.map(e => fillFonts(e, font_dict));
283
+ l = { ...l, elements: filledFonts };
284
+ if (isFill(l)) {
285
+ const total_width = totalElementsWidth(l);
286
+ if (total_width <= Width.get_fixed_unchecked(l.width)) {
287
+ // throw `Cannot fill fonts of row with width ${JSON.stringify(this.width())} and total width ${total_width}`
288
+ return withWidth(l, Width.absolute(total_width));
270
289
  }
271
- break;
272
- case "Elem":
273
- this.inner = this.inner.fill_fonts(font_dict);
274
- break;
290
+ }
291
+ return l;
275
292
  }
276
- return this;
293
+ case "Elem":
294
+ return Elem.fillFonts(l, font_dict);
277
295
  }
278
- break_lines(font_dict) {
279
- switch (this.type_()) {
280
- case "Stack": {
281
- const stack = this.inner;
282
- stack.elements = stack.elements.map(e => e.break_lines(font_dict));
283
- return this;
284
- }
285
- case "Row": {
286
- const row = this.inner;
287
- if (row.is_frozen) {
288
- const total_width = row
289
- .elements
290
- .map(e => Width.get_fixed_unchecked(e.width()))
291
- .reduce((a, b) => a + b, 0.0);
292
- if (total_width > Width.get_fixed_unchecked(this.width())) {
293
- throw `Cannot break lines of frozen row with width ${this.width()} and total width ${total_width}`;
294
- }
295
- else {
296
- row.elements = row.elements.map(e => e.break_lines(font_dict));
297
- }
296
+ }
297
+ exports.fillFonts = fillFonts;
298
+ function breakLines(l, font_dict) {
299
+ switch (l.tag) {
300
+ case "Stack": {
301
+ const stack = l;
302
+ return Stack.withElements(stack, stack.elements.map(e => breakLines(e, font_dict)));
303
+ }
304
+ case "Row": {
305
+ const row = l;
306
+ if (row.is_frozen) {
307
+ const total_width = row
308
+ .elements
309
+ .map(e => Width.get_fixed_unchecked(e.width))
310
+ .reduce((a, b) => a + b, 0.0);
311
+ if (total_width > Width.get_fixed_unchecked(row.width)) {
312
+ throw `Cannot break lines of frozen row with width ${row.width} and total width ${total_width}`;
298
313
  }
299
314
  else {
300
- const lines = row.break_lines(font_dict).map(l => new SectionLayout(l));
301
- return new SectionLayout(new Stack(lines, row.margin, row.alignment, row.width));
315
+ row.elements = row.elements.map(e => breakLines(e, font_dict));
302
316
  }
303
- return this;
304
317
  }
305
- case "Elem": {
306
- if (this.is_ref()) {
307
- throw new Error("Cannot break lines of uninstantiated layout");
308
- }
309
- const elem = this.inner;
310
- const lines = elem.break_lines(font_dict).map(l => new SectionLayout(l));
311
- // Make last line left if it's justified
312
- if (lines[lines.length - 1].inner.alignment === "Justified") {
313
- lines[lines.length - 1] = lines[lines.length - 1].with_alignment("Left");
314
- }
315
- return new SectionLayout(new Stack(lines, elem.margin, elem.alignment, elem.width));
318
+ else {
319
+ const lines = Row.breakLines(row, font_dict);
320
+ return Stack.stack(lines, row.margin, row.alignment, row.width, false);
316
321
  }
322
+ return row;
317
323
  }
318
- }
319
- compute_boxes(font_dict) {
320
- const textbox_positions = [];
321
- const top_left = new Point_1.Point(0.0, 0.0);
322
- const depth = this.compute_textbox_positions(textbox_positions, top_left, font_dict);
323
- console.error("compute_boxes: BBOX: ", this);
324
- const bounding_box = new Box_1.Box(new Point_1.Point(0.0, 0.0), new Point_1.Point(Width.get_fixed_unchecked(this.width()), depth));
325
- return [new AnyLayout_1.ElementBox(bounding_box, textbox_positions), this];
326
- }
327
- compute_textbox_positions(textbox_positions, top_left, font_dict) {
328
- let depth = top_left.y;
329
- switch (this.type_()) {
330
- case "Stack": {
331
- const stack = this.inner;
332
- top_left = top_left.move_y_by(stack.margin.top).move_x_by(stack.margin.left);
333
- const originalTopLeft = top_left;
334
- for (const element of stack.elements) {
335
- depth = element.compute_textbox_positions(textbox_positions, top_left, font_dict);
336
- top_left = top_left.move_y_to(depth);
337
- }
338
- this.bounding_box = new Box_1.Box(originalTopLeft, originalTopLeft.move_y_by(depth).move_x_by(Width.get_fixed_unchecked(stack.width)));
339
- console.error("Stack bounding box", this.bounding_box);
340
- return depth;
324
+ case "Elem": {
325
+ if (isRef(l)) {
326
+ throw new Error("Cannot break lines of uninstantiated layout");
341
327
  }
342
- case "Row": {
343
- const row = this.inner;
344
- top_left = top_left.move_y_by(row.margin.top).move_x_by(row.margin.left);
345
- const originalTopLeft = top_left;
346
- let per_elem_space = 0.0;
347
- switch (row.alignment) {
348
- case "Center":
349
- top_left = top_left.move_x_by((Width.get_fixed_unchecked(row.width) - row.elements_width()) / 2.0);
350
- break;
351
- case "Right":
352
- top_left = top_left.move_x_by(Width.get_fixed_unchecked(row.width) - row.elements_width());
353
- break;
354
- case "Justified":
355
- per_elem_space = (Width.get_fixed_unchecked(row.width) - row.elements_width()) / (row.elements.length - 1);
356
- break;
357
- }
358
- for (const element of row.elements) {
359
- depth = Math.max(depth, element.compute_textbox_positions(textbox_positions, top_left, font_dict));
360
- top_left =
361
- top_left.move_x_by(Width.get_fixed_unchecked(element.width()) + per_elem_space);
362
- }
363
- console.error("Row bounding box", top_left, depth);
364
- this.bounding_box = new Box_1.Box(originalTopLeft, originalTopLeft.move_y_by(depth).move_x_by(Width.get_fixed_unchecked(row.width)));
365
- return depth;
366
- }
367
- case "Elem": {
368
- const elem = this.inner;
369
- if (elem.is_ref) {
370
- throw new Error("Cannot compute textbox positions of uninstantiated layout");
371
- }
372
- if (elem.item === "") {
373
- return top_left.y;
374
- }
375
- const height = elem.font.get_height(font_dict);
376
- const width = Width.get_fixed_unchecked(elem.text_width);
377
- top_left = top_left.move_y_by(elem.margin.top).move_x_by(elem.margin.left);
378
- switch (elem.alignment) {
379
- case "Center":
380
- top_left = top_left.move_x_by((Width.get_fixed_unchecked(elem.width) - width) / 2.0);
381
- break;
382
- case "Right":
383
- top_left = top_left.move_x_by(Width.get_fixed_unchecked(elem.width) - width);
384
- break;
385
- }
386
- const textbox = new Box_1.Box(top_left, top_left.move_x_by(width).move_y_by(height));
387
- textbox_positions.push([textbox, elem]);
388
- this.bounding_box = textbox;
389
- return top_left.y + height;
328
+ const elem = l;
329
+ const lines = Elem.break_lines(elem, font_dict);
330
+ // Make last line left if it's justified
331
+ if (lines[lines.length - 1].alignment === "Justified") {
332
+ lines[lines.length - 1] = withAlignment(lines[lines.length - 1], "Left");
390
333
  }
334
+ return Stack.stack(lines, elem.margin, elem.alignment, elem.width, false);
391
335
  }
392
336
  }
393
337
  }
394
- exports.SectionLayout = SectionLayout;
395
- class Stack {
396
- constructor(elements, margin, alignment, width, is_fill) {
397
- this.tag = "Stack";
398
- this.elements = elements;
399
- this.margin = margin !== null && margin !== void 0 ? margin : Margin.default_();
400
- this.alignment = alignment !== null && alignment !== void 0 ? alignment : Alignment.default_();
401
- this.width = width !== null && width !== void 0 ? width : Width.default_();
402
- this.is_fill = is_fill !== null && is_fill !== void 0 ? is_fill : false;
403
- }
404
- static stack(elements, margin, alignment, width, is_fill) {
405
- return new SectionLayout(new Stack(elements, margin, alignment, width, is_fill));
406
- }
407
- copy() {
408
- return new Stack(this.elements.map((e) => e.copy()), Margin.copy(this.margin), this.alignment, Width.copy(this.width));
409
- }
410
- static default_() {
411
- return new Stack([]);
412
- }
413
- instantiate(section) {
414
- return new Stack(this.elements.map(e => e.instantiate(section)), this.margin, this.alignment, this.width, this.is_fill);
415
- }
416
- with_elements(elements) {
417
- return new Stack(elements, this.margin, this.alignment, this.width, this.is_fill);
418
- }
419
- with_margin(margin) {
420
- return new Stack(this.elements, margin, this.alignment, this.width, this.is_fill);
421
- }
422
- with_alignment(alignment) {
423
- return new Stack(this.elements, this.margin, alignment, this.width, this.is_fill);
424
- }
425
- with_width(width) {
426
- return new Stack(this.elements, this.margin, this.alignment, width, this.is_fill);
427
- }
428
- bound_width(width) {
429
- const bound = this.width.tag === "Absolute" ? Math.min(this.width.value, width)
430
- : this.width.tag === "Fill" ? width
431
- : null;
432
- if (bound === null) {
433
- throw new Error("Cannot bound width of non-unitized widths!");
434
- }
435
- return new Stack(this.elements.map(e => e.bound_width(bound)), this.margin, this.alignment, Width.absolute(bound), Width.is_fill(this.width));
436
- }
437
- scale_width(w) {
438
- return this.with_elements(this.elements.map(e => e.scale_width(w))).with_width(Width.scale(this.width, w));
439
- }
338
+ exports.breakLines = breakLines;
339
+ function computeBoxes(l, font_dict) {
340
+ const top_left = new Point_1.Point(0.0, 0.0);
341
+ return computeTextboxPositions(l, top_left, font_dict).renderedLayout;
440
342
  }
441
- exports.Stack = Stack;
442
- class Row {
443
- constructor(elements, is_frozen, margin, alignment, width, is_fill) {
444
- this.tag = "Row";
445
- this.elements = elements;
446
- this.is_frozen = is_frozen !== null && is_frozen !== void 0 ? is_frozen : false;
447
- this.margin = margin !== null && margin !== void 0 ? margin : Margin.default_();
448
- this.alignment = alignment !== null && alignment !== void 0 ? alignment : Alignment.default_();
449
- this.width = width !== null && width !== void 0 ? width : Width.default_();
450
- this.is_fill = is_fill !== null && is_fill !== void 0 ? is_fill : false;
451
- }
452
- static row(elements, is_frozen, margin, alignment, width, is_fill) {
453
- return new SectionLayout(new Row(elements, is_frozen, margin, alignment, width, is_fill));
454
- }
455
- copy() {
456
- return new Row(this.elements.map((e) => e.copy()), this.is_frozen, Margin.copy(this.margin), this.alignment, Width.copy(this.width), this.is_fill);
457
- }
458
- static default_() {
459
- return new Row([]);
460
- }
461
- instantiate(section) {
462
- return new Row(this.elements.map(e => e.instantiate(section)), this.is_frozen, this.margin, this.alignment, this.width, this.is_fill);
463
- }
464
- with_elements(elements) {
465
- return new Row(elements, this.is_frozen, this.margin, this.alignment, this.width, this.is_fill);
466
- }
467
- with_margin(margin) {
468
- return new Row(this.elements, this.is_frozen, margin, this.alignment, this.width, this.is_fill);
469
- }
470
- with_alignment(alignment) {
471
- return new Row(this.elements, this.is_frozen, this.margin, alignment, this.width, this.is_fill);
472
- }
473
- with_width(width) {
474
- return new Row(this.elements, this.is_frozen, this.margin, this.alignment, width, this.is_fill);
475
- }
476
- elements_width() {
477
- return this.elements.map(e => Width.get_fixed_unchecked(e.width())).reduce((a, b) => a + b, 0.0);
478
- }
479
- bound_width(width) {
480
- const bound = this.width.tag === "Absolute" ? Math.min(this.width.value, width)
481
- : this.width.tag === "Fill" ? width
482
- : null;
483
- if (bound === null) {
484
- throw new Error("Cannot bound width of non-unitized widths!");
485
- }
486
- return new Row(this.elements.map(e => e.bound_width(bound)), this.is_frozen, this.margin, this.alignment, Width.absolute(bound), Width.is_fill(this.width));
487
- }
488
- scale_width(w) {
489
- return this.with_elements(this.elements.map(e => e.scale_width(w))).with_width(Width.scale(this.width, w));
490
- }
491
- break_lines(font_dict) {
492
- const lines = [];
493
- let current_line = [];
494
- let current_width = 0.0;
495
- const elements = this
496
- .elements
497
- .map(e => e.break_lines(font_dict));
498
- for (const element of elements) {
499
- const element_width = Width.get_fixed_unchecked(element.width());
500
- if (current_width + element_width > Width.get_fixed_unchecked(this.width)) {
501
- lines.push(this.with_elements(current_line));
502
- current_line = [];
503
- current_width = 0.0;
343
+ exports.computeBoxes = computeBoxes;
344
+ function computeTextboxPositions(l, top_left, font_dict) {
345
+ let depth = top_left.y;
346
+ switch (l.tag) {
347
+ case "Stack": {
348
+ const stack = l;
349
+ top_left = top_left.move_y_by(stack.margin.top).move_x_by(stack.margin.left);
350
+ const originalTopLeft = top_left;
351
+ const renderedElements = [];
352
+ for (const element of stack.elements) {
353
+ const result = computeTextboxPositions(element, top_left, font_dict);
354
+ depth = result.depth;
355
+ top_left = top_left.move_y_to(depth);
356
+ renderedElements.push(result.renderedLayout);
357
+ }
358
+ return {
359
+ depth, renderedLayout: {
360
+ ...l, bounding_box: new Box_1.Box(originalTopLeft, top_left.move_x_by(Width.get_fixed_unchecked(stack.width))), elements: renderedElements
361
+ }
362
+ };
363
+ }
364
+ case "Row": {
365
+ const row = l;
366
+ top_left = top_left.move_y_by(row.margin.top).move_x_by(row.margin.left);
367
+ const originalTopLeft = top_left;
368
+ let per_elem_space = 0.0;
369
+ switch (row.alignment) {
370
+ case "Center":
371
+ top_left = top_left.move_x_by((Width.get_fixed_unchecked(row.width) - Row.elementsWidth(row)) / 2.0);
372
+ break;
373
+ case "Right":
374
+ top_left = top_left.move_x_by(Width.get_fixed_unchecked(row.width) - Row.elementsWidth(row));
375
+ break;
376
+ case "Justified":
377
+ per_elem_space = (Width.get_fixed_unchecked(row.width) - Row.elementsWidth(row)) / (row.elements.length - 1);
378
+ break;
504
379
  }
505
- current_line.push(element);
506
- current_width += element_width;
380
+ const renderedElements = [];
381
+ for (const element of row.elements) {
382
+ const result = computeTextboxPositions(element, top_left, font_dict);
383
+ depth = Math.max(depth, result.depth);
384
+ top_left =
385
+ top_left.move_x_by(Width.get_fixed_unchecked(element.width) + per_elem_space);
386
+ renderedElements.push(result.renderedLayout);
387
+ }
388
+ return {
389
+ depth, renderedLayout: {
390
+ ...l, bounding_box: new Box_1.Box(originalTopLeft, originalTopLeft.move_y_by(depth).move_x_by(Width.get_fixed_unchecked(row.width))), elements: renderedElements
391
+ }
392
+ };
507
393
  }
508
- if (current_line.length > 0) {
509
- lines.push(this.with_elements(current_line));
394
+ case "Elem": {
395
+ const elem = l;
396
+ if (elem.is_ref) {
397
+ throw new Error("Cannot compute textbox positions of uninstantiated layout");
398
+ }
399
+ const height = Font.get_height(elem.font, font_dict);
400
+ const width = Width.get_fixed_unchecked(elem.text_width);
401
+ top_left = top_left.move_y_by(elem.margin.top).move_x_by(elem.margin.left);
402
+ let line = 1;
403
+ let cursor = top_left.x;
404
+ elem.spans.forEach(span => {
405
+ if (cursor + span.width > Width.get_fixed_unchecked(elem.width) - elem.margin.right) {
406
+ cursor = top_left.x;
407
+ line += 1;
408
+ }
409
+ span.bbox = new Box_1.Box(new Point_1.Point(cursor, (line - 1) * height), new Point_1.Point(cursor + span.width, line * height));
410
+ cursor += span.width;
411
+ console.log(span);
412
+ });
413
+ switch (elem.alignment) {
414
+ case "Center":
415
+ top_left = top_left.move_x_by((Width.get_fixed_unchecked(elem.width) - width) / 2.0);
416
+ break;
417
+ case "Right":
418
+ top_left = top_left.move_x_by(Width.get_fixed_unchecked(elem.width) - width);
419
+ break;
420
+ }
421
+ const textbox = new Box_1.Box(top_left, top_left.move_x_by(width).move_y_by(height));
422
+ return { depth: top_left.y + height, renderedLayout: { ...l, bounding_box: textbox } };
510
423
  }
511
- return lines;
512
424
  }
513
425
  }
514
- exports.Row = Row;
426
+ exports.computeTextboxPositions = computeTextboxPositions;
515
427
  exports.ColorMap = {
516
428
  "Transparent": "transparent",
517
429
  "Light Yellow": "#FFC96F",
@@ -521,126 +433,3 @@ exports.ColorMap = {
521
433
  "Light Blue": "#EEF7FF",
522
434
  "Blue": "#4793AF",
523
435
  };
524
- class Elem {
525
- constructor(item, url, is_ref, is_fill, text_width, font, margin, alignment, width, background_color) {
526
- this.tag = "Elem";
527
- this.item = item;
528
- this.url = url;
529
- this.is_ref = is_ref;
530
- this.is_fill = is_fill;
531
- this.text_width = text_width;
532
- this.font = font;
533
- this.margin = margin;
534
- this.alignment = alignment;
535
- this.width = width;
536
- this.background_color = background_color;
537
- }
538
- static elem(item, url, is_ref, is_fill, text_width, font, margin, alignment, width, background_color) {
539
- return new SectionLayout(new Elem(item, url, is_ref, is_fill, text_width, font, margin, alignment, width, background_color));
540
- }
541
- copy() {
542
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, Width.copy(this.text_width), this.font, Margin.copy(this.margin), this.alignment, Width.copy(this.width), this.background_color);
543
- }
544
- static default_() {
545
- return new Elem("", null, false, false, Width.default_(), Font_1.Font.default_(), Margin.default_(), Alignment.default_(), Width.default_(), "Transparent");
546
- }
547
- with_item(item) {
548
- return new Elem(item, this.url, this.is_ref, this.is_fill, this.text_width, this.font, this.margin, this.alignment, this.width, this.background_color);
549
- }
550
- as_ref() {
551
- return new Elem(this.item, this.url, true, this.is_fill, this.text_width, this.font, this.margin, this.alignment, this.width, this.background_color);
552
- }
553
- with_font(font) {
554
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, this.text_width, font, this.margin, this.alignment, this.width, this.background_color);
555
- }
556
- with_url(url) {
557
- return new Elem(this.item, url, this.is_ref, this.is_fill, this.text_width, this.font, this.margin, this.alignment, this.width, this.background_color);
558
- }
559
- with_margin(margin) {
560
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, this.text_width, this.font, margin, this.alignment, this.width, this.background_color);
561
- }
562
- with_alignment(alignment) {
563
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, this.text_width, this.font, this.margin, alignment, this.width, this.background_color);
564
- }
565
- with_width(width) {
566
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, this.text_width, this.font, this.margin, this.alignment, width, this.background_color);
567
- }
568
- with_text_width(text_width) {
569
- return new Elem(this.item, this.url, this.is_ref, this.is_fill, text_width, this.font, this.margin, this.alignment, this.width, this.background_color);
570
- }
571
- with_is_fill(is_fill) {
572
- return new Elem(this.item, this.url, this.is_ref, is_fill, this.text_width, this.font, this.margin, this.alignment, this.width, this.background_color);
573
- }
574
- scale_width(w) {
575
- return this.with_width(Width.scale(this.width, w));
576
- }
577
- fill_fonts(fonts) {
578
- const text_width_with_font = this.font.get_width(this.item, fonts);
579
- if (this.is_fill) {
580
- return this.with_width(Width.absolute(Math.min(Width.get_fixed_unchecked(this.width), text_width_with_font)))
581
- .with_text_width(Width.absolute(text_width_with_font));
582
- }
583
- else {
584
- return this.with_text_width(Width.absolute(text_width_with_font));
585
- }
586
- }
587
- justified_lines(lines, font_dict) {
588
- const rowLines = [];
589
- for (const line of lines.slice(0, -1)) {
590
- const words = line.item.split(/\s+/);
591
- const row = new Row([], false, line.margin, line.alignment, line.width);
592
- words.forEach(word => {
593
- const word_width = this.font.get_width(word, font_dict);
594
- row.elements.push(new SectionLayout(new Elem(word, null, false, false, Width.absolute(word_width), this.font, Margin.default_(), Alignment.default_(), Width.absolute(word_width), this.background_color)));
595
- });
596
- rowLines.push(row);
597
- }
598
- rowLines.push(new Row([new SectionLayout(lines[lines.length - 1]).with_alignment("Left")], false, lines[0].margin, "Left", lines[0].width));
599
- return rowLines;
600
- }
601
- break_lines(font_dict) {
602
- if (Width.get_fixed_unchecked(this.text_width) <= Width.get_fixed_unchecked(this.width)) {
603
- return [this];
604
- }
605
- const lines = [];
606
- // todo: I'm sure this implementation is pretty buggy. Note to future me, fix
607
- // this.
608
- const words = this.item.split(/\s+/);
609
- const widths = words.map((word) => this.font.get_width(word, font_dict));
610
- const space_width = this.font.get_width(" ", font_dict);
611
- let start = 0;
612
- let width = widths[0];
613
- const max_width = Width.get_fixed_unchecked(this.width);
614
- for (let i = 1; i < words.length; i++) {
615
- const candidate_width = width + space_width + widths[i];
616
- if (candidate_width > max_width) {
617
- const line = words.slice(start, i).join(" ");
618
- const line_width = this.font.get_width(line, font_dict);
619
- lines.push(this.with_item(line)
620
- .with_text_width(Width.absolute(line_width)));
621
- start = i;
622
- width = widths[i];
623
- }
624
- else {
625
- width += space_width + widths[i];
626
- }
627
- }
628
- const line = words.slice(start).join(" ");
629
- const line_width = this.font.get_width(line, font_dict);
630
- lines.push(this.with_item(line)
631
- .with_text_width(Width.absolute(line_width)));
632
- if (this.alignment === "Justified") {
633
- return this.justified_lines(lines, font_dict);
634
- }
635
- return lines;
636
- }
637
- bound_width(width) {
638
- if (!Width.is_fill(this.width)) {
639
- return this.with_width(Width.absolute(Math.min(Width.get_fixed_unchecked(this.width), width))).with_is_fill(false);
640
- }
641
- else {
642
- return this.with_width(Width.absolute(width)).with_is_fill(true);
643
- }
644
- }
645
- }
646
- exports.Elem = Elem;