postext 0.3.38 → 0.3.39
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/__tests__/exports.test.js +5 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/pipeline/rotatedResource.test.d.ts +2 -0
- package/dist/__tests__/pipeline/rotatedResource.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/rotatedResource.test.js +242 -0
- package/dist/__tests__/pipeline/rotatedResource.test.js.map +1 -0
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +7 -1
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +21 -4
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/canvas-backend/swatch.d.ts +8 -0
- package/dist/canvas-backend/swatch.d.ts.map +1 -0
- package/dist/canvas-backend/swatch.js +20 -0
- package/dist/canvas-backend/swatch.js.map +1 -0
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +38 -10
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/knuthPlass/richAdapter.d.ts +4 -0
- package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/richAdapter.js +2 -1
- package/dist/knuthPlass/richAdapter.js.map +1 -1
- package/dist/measure/rich.d.ts +8 -0
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +24 -2
- package/dist/measure/rich.js.map +1 -1
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +13 -9
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/index.d.ts +2 -0
- package/dist/parse/index.d.ts.map +1 -1
- package/dist/parse/index.js +1 -0
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/inlineFormatting.d.ts +26 -0
- package/dist/parse/inlineFormatting.d.ts.map +1 -1
- package/dist/parse/inlineFormatting.js +45 -0
- package/dist/parse/inlineFormatting.js.map +1 -1
- package/dist/parse/inlineSnippet.d.ts +1 -1
- package/dist/parse/inlineSnippet.d.ts.map +1 -1
- package/dist/parse/inlineSnippet.js +4 -3
- package/dist/parse/inlineSnippet.js.map +1 -1
- package/dist/parse/sourceMapping.d.ts.map +1 -1
- package/dist/parse/sourceMapping.js +19 -1
- package/dist/parse/sourceMapping.js.map +1 -1
- package/dist/parse/types.d.ts +10 -0
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +77 -26
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/floatPlacement.d.ts +10 -3
- package/dist/pipeline/floatPlacement.d.ts.map +1 -1
- package/dist/pipeline/floatPlacement.js +10 -5
- package/dist/pipeline/floatPlacement.js.map +1 -1
- package/dist/pipeline/floatSlots.d.ts +3 -0
- package/dist/pipeline/floatSlots.d.ts.map +1 -1
- package/dist/pipeline/floatSlots.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +6 -2
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts +16 -2
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +51 -4
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/table/model.d.ts +7 -1
- package/dist/table/model.d.ts.map +1 -1
- package/dist/table/model.js +19 -0
- package/dist/table/model.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +50 -1
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js +29 -0
- package/dist/vdt.js.map +1 -1
- package/package.json +1 -1
package/dist/pipeline/build.js
CHANGED
|
@@ -233,6 +233,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
233
233
|
/** Offset a resolved resource block's caption/table geometry from
|
|
234
234
|
* block-relative to absolute page coordinates (mirrors inline placement). */
|
|
235
235
|
const offsetResourceBlockToAbsolute = (rb, ox, oy) => {
|
|
236
|
+
// A rotated block keeps its geometry in the upright frame; only the
|
|
237
|
+
// frame's origin moves on the page.
|
|
238
|
+
if (rb.rotation) {
|
|
239
|
+
rb.rotation.originX += ox;
|
|
240
|
+
rb.rotation.originY += oy;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
236
243
|
for (const ln of rb.captionLines) {
|
|
237
244
|
ln.bbox.x += ox;
|
|
238
245
|
ln.bbox.y += oy;
|
|
@@ -268,7 +275,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
268
275
|
}
|
|
269
276
|
}
|
|
270
277
|
};
|
|
271
|
-
const
|
|
278
|
+
const rotationKey = (rotated) => rotated ? `:${rotated.direction}${rotated.length.toFixed(2)}` : '';
|
|
279
|
+
const layoutFloat = (resourceId, width, slice, rotated) => {
|
|
272
280
|
const resource = resourceById.get(resourceId);
|
|
273
281
|
if (!resource)
|
|
274
282
|
return null;
|
|
@@ -282,8 +290,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
282
290
|
resourceTypes,
|
|
283
291
|
resources,
|
|
284
292
|
...(slice ? { slice } : {}),
|
|
293
|
+
...(rotated ? { rotate: rotated.direction, rotatedLength: rotated.length } : {}),
|
|
285
294
|
});
|
|
286
295
|
};
|
|
296
|
+
/** The rotation of a pending float on a band whose columns keep `avail`
|
|
297
|
+
* px: the upright frame is as long as the grid multiple within that
|
|
298
|
+
* room, less the float gap, so the band it takes is `avail` at most. */
|
|
299
|
+
const rotationFor = (f, avail) => f.rotate
|
|
300
|
+
? { direction: f.rotate, length: Math.max(1, Math.floor((avail + 0.01) / baselineGrid) * baselineGrid - floatGapPx) }
|
|
301
|
+
: undefined;
|
|
287
302
|
/** Row count of a table resource (0 for anything else). */
|
|
288
303
|
const tableRowCount = (resourceId) => resourceById.get(resourceId)?.table?.model.rows.length ?? 0;
|
|
289
304
|
/** The slice a pending float stands for: the whole resource, or — for the
|
|
@@ -295,26 +310,31 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
295
310
|
const sliceKey = (slice) => slice ? `:${slice.startRow}-${slice.endRow}${slice.continues ? '+' : ''}` : '';
|
|
296
311
|
/** Row metrics of a table float laid out in full at a width, memoised. */
|
|
297
312
|
const tableMetricsMemo = new Map();
|
|
298
|
-
const tableMetrics = (resourceId, width) => {
|
|
299
|
-
const key = `${resourceId}:${width.toFixed(2)}`;
|
|
313
|
+
const tableMetrics = (resourceId, width, rotated) => {
|
|
314
|
+
const key = `${resourceId}:${width.toFixed(2)}${rotationKey(rotated)}`;
|
|
300
315
|
const memo = tableMetricsMemo.get(key);
|
|
301
316
|
if (memo !== undefined)
|
|
302
317
|
return memo;
|
|
303
|
-
const m = layoutFloat(resourceId, width)?.tableRows ?? null;
|
|
318
|
+
const m = layoutFloat(resourceId, width, undefined, rotated)?.tableRows ?? null;
|
|
304
319
|
tableMetricsMemo.set(key, m);
|
|
305
320
|
return m;
|
|
306
321
|
};
|
|
307
322
|
/** Height (and caption baseline) of a float at a given width, memoised —
|
|
308
323
|
* fit checks run for every pending float on every loop iteration. */
|
|
309
324
|
const floatMeasureMemo = new Map();
|
|
310
|
-
const measureFloat = (resourceId, width, slice) => {
|
|
311
|
-
const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}`;
|
|
325
|
+
const measureFloat = (resourceId, width, slice, rotated) => {
|
|
326
|
+
const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}${rotationKey(rotated)}`;
|
|
312
327
|
const memo = floatMeasureMemo.get(key);
|
|
313
328
|
if (memo !== undefined)
|
|
314
329
|
return memo;
|
|
315
|
-
const laid = layoutFloat(resourceId, width, slice);
|
|
330
|
+
const laid = layoutFloat(resourceId, width, slice, rotated);
|
|
316
331
|
let m = null;
|
|
317
|
-
if (laid) {
|
|
332
|
+
if (laid && laid.block.rotation) {
|
|
333
|
+
// A rotated block takes its band whole: no caption baseline to align,
|
|
334
|
+
// and its upright height is what must fit the band's width.
|
|
335
|
+
m = { height: laid.totalHeight, rotatedWidth: laid.block.rotation.height };
|
|
336
|
+
}
|
|
337
|
+
else if (laid) {
|
|
318
338
|
// A bottom band aligns the float's LAST text line to the grid: the
|
|
319
339
|
// caption's (or the note's) last line when it sits under the body. A
|
|
320
340
|
// caption set above the body (caption bars) leaves the body's bottom
|
|
@@ -338,8 +358,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
338
358
|
};
|
|
339
359
|
/** Measure + build a float block at horizontal offset `x` (y = 0), or null
|
|
340
360
|
* when the resource id is unknown. Caller offsets it to its final `y`. */
|
|
341
|
-
const buildFloatBlock = (resourceId, x, width, slice
|
|
342
|
-
|
|
361
|
+
const buildFloatBlock = (resourceId, x, width, slice, rotated,
|
|
362
|
+
/** A rotated block sits flush to the band's right edge (the spine of a
|
|
363
|
+
* verso page) instead of its left. */
|
|
364
|
+
flushEnd = false) => {
|
|
365
|
+
const laid = layoutFloat(resourceId, width, slice, rotated);
|
|
343
366
|
if (!laid)
|
|
344
367
|
return null;
|
|
345
368
|
const { block: rb, totalHeight } = laid;
|
|
@@ -350,9 +373,23 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
350
373
|
blk.snappedToGrid = false;
|
|
351
374
|
blk.bbox = createBoundingBox(x, 0, width, totalHeight);
|
|
352
375
|
blk.lines = [];
|
|
353
|
-
|
|
376
|
+
if (rb.rotation) {
|
|
377
|
+
// The upright frame's origin: for a counter-clockwise turn the frame's
|
|
378
|
+
// top-left lands at the bottom-left of the block, for a clockwise one
|
|
379
|
+
// at its top-right (see `resourceBlockToPage`).
|
|
380
|
+
const used = Math.min(rb.rotation.height, width);
|
|
381
|
+
const left = x + (flushEnd ? Math.max(0, width - used) : 0);
|
|
382
|
+
rb.rotation.originX = rb.rotation.direction === 'ccw' ? left : left + used;
|
|
383
|
+
rb.rotation.originY = rb.rotation.direction === 'ccw' ? totalHeight : 0;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
offsetResourceBlockToAbsolute(rb, x, 0);
|
|
387
|
+
}
|
|
354
388
|
return { block: blk, height: totalHeight };
|
|
355
389
|
};
|
|
390
|
+
/** Whether a rotated float on `page` sits flush to the band's right edge:
|
|
391
|
+
* with mirrored margins the spine of a verso (even-numbered) page. */
|
|
392
|
+
const rotatedFlushEnd = (page) => !!resolved.page.margins.mirror && (page.index + pageIndexOffset) % 2 === 1;
|
|
356
393
|
/** Float bands reserved per column in this pass (the fresh-page flush
|
|
357
394
|
* sends single-column floats to the least reserved column). */
|
|
358
395
|
const floatReserved = new Map();
|
|
@@ -386,14 +423,14 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
386
423
|
* (current-page) mode nothing is forced: `'none'` when not even the
|
|
387
424
|
* smallest slice fits the slot.
|
|
388
425
|
*/
|
|
389
|
-
const splitTableFloat = (f, width, position, targetCols, contentArea, avail, mode) => {
|
|
426
|
+
const splitTableFloat = (f, width, position, targetCols, contentArea, avail, mode, rotated) => {
|
|
390
427
|
const rowCount = tableRowCount(f.resourceId);
|
|
391
428
|
if (rowCount === 0)
|
|
392
429
|
return null;
|
|
393
430
|
const overflow = resolved.tableStyle.overflow;
|
|
394
431
|
if (overflow === 'hide')
|
|
395
432
|
return 'skip';
|
|
396
|
-
const metrics = tableMetrics(f.resourceId, width);
|
|
433
|
+
const metrics = tableMetrics(f.resourceId, width, rotated);
|
|
397
434
|
if (!metrics)
|
|
398
435
|
return null;
|
|
399
436
|
const startRow = f.startRow ?? 0;
|
|
@@ -402,14 +439,16 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
402
439
|
return null;
|
|
403
440
|
// Overhead of a continuing slice at this width — caption, gaps, marker —
|
|
404
441
|
// from a one-row probe; the row heights come from the metrics.
|
|
405
|
-
const probe = layoutFloat(f.resourceId, width, { startRow, endRow: firstBody + 1, continues: true });
|
|
442
|
+
const probe = layoutFloat(f.resourceId, width, { startRow, endRow: firstBody + 1, continues: true }, rotated);
|
|
406
443
|
if (!probe)
|
|
407
444
|
return null;
|
|
408
|
-
|
|
445
|
+
// A rotated table's rows run across the page: the room for them is
|
|
446
|
+
// the band's width, less the upright overhead.
|
|
447
|
+
const overhead = (probe.block.rotation ? probe.block.rotation.height : probe.totalHeight) - probe.block.bodyRect.height;
|
|
409
448
|
// A top band rounds up to the grid: the tallest float it holds within
|
|
410
449
|
// `avail` is the grid multiple below it, less the gap.
|
|
411
450
|
const hMax = Math.floor((avail + 0.01) / baselineGrid) * baselineGrid - floatGapPx;
|
|
412
|
-
let end = planTableSlice(metrics, startRow, hMax - overhead);
|
|
451
|
+
let end = planTableSlice(metrics, startRow, (rotated ? width : hMax) - overhead);
|
|
413
452
|
const floor = firstBody + 1;
|
|
414
453
|
if (end < floor) {
|
|
415
454
|
// Nothing fits. A fresh page carries the smallest slice anyway (it
|
|
@@ -432,9 +471,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
432
471
|
end = e;
|
|
433
472
|
}
|
|
434
473
|
const fits = (slice) => {
|
|
435
|
-
const measure = measureFloat(f.resourceId, width, slice);
|
|
474
|
+
const measure = measureFloat(f.resourceId, width, slice, rotated);
|
|
436
475
|
if (!measure)
|
|
437
476
|
return true;
|
|
477
|
+
if (rotated)
|
|
478
|
+
return (measure.rotatedWidth ?? 0) <= width + 0.01;
|
|
438
479
|
const { need } = measureFloatBand(position, measure, targetCols, contentArea, baselineGrid, floatGapPx, (c) => trueBottom(c, uncappedBottoms));
|
|
439
480
|
return need <= avail + 0.01;
|
|
440
481
|
};
|
|
@@ -478,7 +519,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
478
519
|
const width = pageSpan ? page.contentArea.width : first.bbox.width;
|
|
479
520
|
const xLeft = pageSpan ? page.contentArea.x : first.bbox.x;
|
|
480
521
|
const slice = sliceOf(f);
|
|
481
|
-
const
|
|
522
|
+
const rotated = rotationFor(f, Math.min(...targetCols.map((c) => c.availableHeight)));
|
|
523
|
+
const measure = measureFloat(f.resourceId, width, slice, rotated);
|
|
482
524
|
if (!measure)
|
|
483
525
|
return null;
|
|
484
526
|
// A bottom band normally anchors to the column's true foot (under a
|
|
@@ -487,7 +529,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
487
529
|
// and the box follows both.
|
|
488
530
|
const bottomOf = (c) => anchorToCap ? c.bbox.y + c.bbox.height : trueBottom(c, uncappedBottoms);
|
|
489
531
|
const { need, y } = measureFloatBand(position, measure, targetCols, page.contentArea, baselineGrid, floatGapPx, bottomOf);
|
|
490
|
-
return { need, y, measure, slice, width, xLeft };
|
|
532
|
+
return { need, y, measure, slice, width, xLeft, rotated };
|
|
491
533
|
};
|
|
492
534
|
/** Free height `col` keeps for the flow once a float band `probe` is
|
|
493
535
|
* reserved in it at `position` (mirrors the reservation arithmetic of
|
|
@@ -506,11 +548,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
506
548
|
const probe = probeFloatBand(page, f, targetCols, position, pageSpan, anchorToCap);
|
|
507
549
|
if (!probe)
|
|
508
550
|
return 'skip';
|
|
509
|
-
const { width, xLeft } = probe;
|
|
551
|
+
const { width, xLeft, rotated } = probe;
|
|
510
552
|
let { slice, measure, need, y } = probe;
|
|
511
553
|
let rest;
|
|
512
554
|
/** The float was cut to this slot (a table slice). */
|
|
513
555
|
let cut = false;
|
|
556
|
+
/** A rotated block wider than the band (its upright height). */
|
|
557
|
+
const tooWide = (m) => m.rotatedWidth !== undefined && m.rotatedWidth > width + 0.01;
|
|
514
558
|
if (mode === 'fresh') {
|
|
515
559
|
let minAvail = Infinity;
|
|
516
560
|
let anyReserved = false;
|
|
@@ -522,16 +566,17 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
522
566
|
}
|
|
523
567
|
if (need > minAvail - minTextPx && anyReserved)
|
|
524
568
|
return 'defer';
|
|
525
|
-
if (need > minAvail + 0.01) {
|
|
526
|
-
// Dominating the band: a table is cut to it
|
|
527
|
-
|
|
569
|
+
if (need > minAvail + 0.01 || tooWide(measure)) {
|
|
570
|
+
// Dominating the band: a table is cut to it (a rotated table, to
|
|
571
|
+
// the band's width).
|
|
572
|
+
const split = splitTableFloat(f, width, position, targetCols, page.contentArea, minAvail, 'fresh', rotated);
|
|
528
573
|
if (split === 'skip')
|
|
529
574
|
return 'skip';
|
|
530
575
|
if (split && split !== 'none') {
|
|
531
576
|
slice = split.slice;
|
|
532
577
|
rest = split.rest;
|
|
533
578
|
cut = true;
|
|
534
|
-
const m = measureFloat(f.resourceId, width, slice);
|
|
579
|
+
const m = measureFloat(f.resourceId, width, slice, rotated);
|
|
535
580
|
if (!m)
|
|
536
581
|
return 'skip';
|
|
537
582
|
measure = m;
|
|
@@ -540,6 +585,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
540
585
|
}
|
|
541
586
|
}
|
|
542
587
|
else {
|
|
588
|
+
// A rotated block takes a page of its own: it never shares the
|
|
589
|
+
// current page's foot, and waits for a fresh page to be cut to when
|
|
590
|
+
// too wide.
|
|
591
|
+
if (f.rotate || tooWide(measure))
|
|
592
|
+
return 'defer';
|
|
543
593
|
for (const c of targetCols) {
|
|
544
594
|
if (position === 'bottom' && uncappedBottoms.has(c) && !anchorToCap) {
|
|
545
595
|
// Trailing cap: the band must lie entirely below the level cut.
|
|
@@ -585,7 +635,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
585
635
|
if (rest) {
|
|
586
636
|
rest = { ...rest, notBefore: { pageIndex: page.index, columnIndex: targetCols[targetCols.length - 1].index } };
|
|
587
637
|
}
|
|
588
|
-
const built = buildFloatBlock(f.resourceId, xLeft, width, slice);
|
|
638
|
+
const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, rotated ? rotatedFlushEnd(page) : false);
|
|
589
639
|
if (!built)
|
|
590
640
|
return 'skip';
|
|
591
641
|
for (const col of targetCols) {
|
|
@@ -653,7 +703,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
653
703
|
}
|
|
654
704
|
return false;
|
|
655
705
|
};
|
|
656
|
-
|
|
706
|
+
/** A rotated float takes its page from the top. */
|
|
707
|
+
const positionsFor = (f) => f.rotate ? ['top'] : f.position === 'auto' ? ['top', 'bottom'] : [f.position];
|
|
657
708
|
/** Reserve top/bottom bands on a freshly opened page and position as many
|
|
658
709
|
* pending floats as fit, shrinking the affected columns so body text flows
|
|
659
710
|
* around them. Full-width (page-span) floats reserve the outermost bands
|