postext 1.35.1 → 1.37.0
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/__bench__/composedPage.bench.test.d.ts +2 -0
- package/dist/__bench__/composedPage.bench.test.d.ts.map +1 -0
- package/dist/__bench__/composedPage.bench.test.js +95 -0
- package/dist/__bench__/composedPage.bench.test.js.map +1 -0
- package/dist/__tests__/composed/continuation.test.d.ts +2 -0
- package/dist/__tests__/composed/continuation.test.d.ts.map +1 -0
- package/dist/__tests__/composed/continuation.test.js +352 -0
- package/dist/__tests__/composed/continuation.test.js.map +1 -0
- package/dist/__tests__/composed/contrast.test.d.ts +2 -0
- package/dist/__tests__/composed/contrast.test.d.ts.map +1 -0
- package/dist/__tests__/composed/contrast.test.js +124 -0
- package/dist/__tests__/composed/contrast.test.js.map +1 -0
- package/dist/__tests__/composed/corpus.test.js +7 -1
- package/dist/__tests__/composed/corpus.test.js.map +1 -1
- package/dist/__tests__/composed/fit.test.d.ts +2 -0
- package/dist/__tests__/composed/fit.test.d.ts.map +1 -0
- package/dist/__tests__/composed/fit.test.js +408 -0
- package/dist/__tests__/composed/fit.test.js.map +1 -0
- package/dist/__tests__/composed/groups.test.js +14 -0
- package/dist/__tests__/composed/groups.test.js.map +1 -1
- package/dist/__tests__/composed/headingDesigns.test.d.ts +2 -0
- package/dist/__tests__/composed/headingDesigns.test.d.ts.map +1 -0
- package/dist/__tests__/composed/headingDesigns.test.js +262 -0
- package/dist/__tests__/composed/headingDesigns.test.js.map +1 -0
- package/dist/__tests__/composed/labels.test.d.ts +2 -0
- package/dist/__tests__/composed/labels.test.d.ts.map +1 -0
- package/dist/__tests__/composed/labels.test.js +326 -0
- package/dist/__tests__/composed/labels.test.js.map +1 -0
- package/dist/__tests__/composed/layout.test.js +2 -1
- package/dist/__tests__/composed/layout.test.js.map +1 -1
- package/dist/__tests__/composed/outsidePrint.test.js +5 -2
- package/dist/__tests__/composed/outsidePrint.test.js.map +1 -1
- package/dist/__tests__/composed/pageMemo.test.d.ts +2 -0
- package/dist/__tests__/composed/pageMemo.test.d.ts.map +1 -0
- package/dist/__tests__/composed/pageMemo.test.js +128 -0
- package/dist/__tests__/composed/pageMemo.test.js.map +1 -0
- package/dist/__tests__/composed/sourceMap.test.d.ts +2 -0
- package/dist/__tests__/composed/sourceMap.test.d.ts.map +1 -0
- package/dist/__tests__/composed/sourceMap.test.js +395 -0
- package/dist/__tests__/composed/sourceMap.test.js.map +1 -0
- package/dist/__tests__/composed/spreadLabels.test.d.ts +2 -0
- package/dist/__tests__/composed/spreadLabels.test.d.ts.map +1 -0
- package/dist/__tests__/composed/spreadLabels.test.js +131 -0
- package/dist/__tests__/composed/spreadLabels.test.js.map +1 -0
- package/dist/__tests__/composed/templateFrameRects.test.d.ts +2 -0
- package/dist/__tests__/composed/templateFrameRects.test.d.ts.map +1 -0
- package/dist/__tests__/composed/templateFrameRects.test.js +37 -0
- package/dist/__tests__/composed/templateFrameRects.test.js.map +1 -0
- package/dist/__tests__/design/imageAltText.test.js +2 -1
- package/dist/__tests__/design/imageAltText.test.js.map +1 -1
- package/dist/__tests__/exports.test.js +33 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/migrate/fixtures/textbookPort.d.ts +14 -0
- package/dist/__tests__/migrate/fixtures/textbookPort.d.ts.map +1 -0
- package/dist/__tests__/migrate/fixtures/textbookPort.js +116 -0
- package/dist/__tests__/migrate/fixtures/textbookPort.js.map +1 -0
- package/dist/__tests__/migrate/textToMarkdown.test.d.ts +2 -0
- package/dist/__tests__/migrate/textToMarkdown.test.d.ts.map +1 -0
- package/dist/__tests__/migrate/textToMarkdown.test.js +268 -0
- package/dist/__tests__/migrate/textToMarkdown.test.js.map +1 -0
- package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts +2 -0
- package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts.map +1 -0
- package/dist/__tests__/paragraphStyleLetterSpacing.test.js +89 -0
- package/dist/__tests__/paragraphStyleLetterSpacing.test.js.map +1 -0
- package/dist/__tests__/pictureBelowPrintSize.test.d.ts +2 -0
- package/dist/__tests__/pictureBelowPrintSize.test.d.ts.map +1 -0
- package/dist/__tests__/pictureBelowPrintSize.test.js +82 -0
- package/dist/__tests__/pictureBelowPrintSize.test.js.map +1 -0
- package/dist/__tests__/pipeline/contourWrap.test.d.ts +2 -0
- package/dist/__tests__/pipeline/contourWrap.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/contourWrap.test.js +412 -0
- package/dist/__tests__/pipeline/contourWrap.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -1
- package/dist/__tests__/toc.test.js +3 -2
- package/dist/__tests__/toc.test.js.map +1 -1
- package/dist/bundle/codec.d.ts +16 -0
- package/dist/bundle/codec.d.ts.map +1 -1
- package/dist/bundle/codec.js +44 -2
- package/dist/bundle/codec.js.map +1 -1
- package/dist/bundle/index.d.ts +1 -1
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/index.js.map +1 -1
- package/dist/canvas-backend/composed.d.ts +5 -2
- package/dist/canvas-backend/composed.d.ts.map +1 -1
- package/dist/canvas-backend/composed.js +10 -5
- package/dist/canvas-backend/composed.js.map +1 -1
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js +37 -19
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts +5 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +60 -1
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/composed/continuation.d.ts +102 -0
- package/dist/composed/continuation.d.ts.map +1 -0
- package/dist/composed/continuation.js +87 -0
- package/dist/composed/continuation.js.map +1 -0
- package/dist/composed/contour.d.ts +21 -0
- package/dist/composed/contour.d.ts.map +1 -0
- package/dist/composed/contour.js +34 -0
- package/dist/composed/contour.js.map +1 -0
- package/dist/composed/geometry.d.ts +8 -0
- package/dist/composed/geometry.d.ts.map +1 -1
- package/dist/composed/geometry.js +26 -0
- package/dist/composed/geometry.js.map +1 -1
- package/dist/composed/ids.d.ts +22 -0
- package/dist/composed/ids.d.ts.map +1 -0
- package/dist/composed/ids.js +32 -0
- package/dist/composed/ids.js.map +1 -0
- package/dist/composed/labels.d.ts +154 -0
- package/dist/composed/labels.d.ts.map +1 -0
- package/dist/composed/labels.js +524 -0
- package/dist/composed/labels.js.map +1 -0
- package/dist/composed/pageMemo.d.ts +73 -0
- package/dist/composed/pageMemo.d.ts.map +1 -0
- package/dist/composed/pageMemo.js +289 -0
- package/dist/composed/pageMemo.js.map +1 -0
- package/dist/composed/paint.d.ts +29 -9
- package/dist/composed/paint.d.ts.map +1 -1
- package/dist/composed/paint.js +41 -7
- package/dist/composed/paint.js.map +1 -1
- package/dist/composed/reflowLog.d.ts +14 -2
- package/dist/composed/reflowLog.d.ts.map +1 -1
- package/dist/composed/reflowLog.js +64 -7
- package/dist/composed/reflowLog.js.map +1 -1
- package/dist/composed/slotChecks.d.ts.map +1 -1
- package/dist/composed/slotChecks.js +3 -1
- package/dist/composed/slotChecks.js.map +1 -1
- package/dist/composed/sourceMap.d.ts +97 -0
- package/dist/composed/sourceMap.d.ts.map +1 -0
- package/dist/composed/sourceMap.js +748 -0
- package/dist/composed/sourceMap.js.map +1 -0
- package/dist/composed/spread.d.ts +3 -0
- package/dist/composed/spread.d.ts.map +1 -1
- package/dist/composed/spread.js +11 -12
- package/dist/composed/spread.js.map +1 -1
- package/dist/composed/templates.d.ts +1 -0
- package/dist/composed/templates.d.ts.map +1 -1
- package/dist/composed/templates.js +4 -0
- package/dist/composed/templates.js.map +1 -1
- package/dist/composed/types.d.ts +15 -32
- package/dist/composed/types.d.ts.map +1 -1
- package/dist/composed/validate.d.ts.map +1 -1
- package/dist/composed/validate.js +3 -2
- package/dist/composed/validate.js.map +1 -1
- package/dist/composed/wrap.d.ts.map +1 -1
- package/dist/composed/wrap.js +7 -1
- package/dist/composed/wrap.js.map +1 -1
- package/dist/configWarnings.d.ts +15 -0
- package/dist/configWarnings.d.ts.map +1 -1
- package/dist/configWarnings.js +29 -17
- package/dist/configWarnings.js.map +1 -1
- package/dist/contour/index.d.ts +134 -0
- package/dist/contour/index.d.ts.map +1 -0
- package/dist/contour/index.js +354 -0
- package/dist/contour/index.js.map +1 -0
- package/dist/contour/png.d.ts +16 -0
- package/dist/contour/png.d.ts.map +1 -0
- package/dist/contour/png.js +173 -0
- package/dist/contour/png.js.map +1 -0
- package/dist/contrast.d.ts +5 -3
- package/dist/contrast.d.ts.map +1 -1
- package/dist/contrast.js +46 -5
- package/dist/contrast.js.map +1 -1
- package/dist/defaults/paragraphStyles.d.ts +1 -1
- package/dist/defaults/paragraphStyles.d.ts.map +1 -1
- package/dist/defaults/paragraphStyles.js +5 -1
- package/dist/defaults/paragraphStyles.js.map +1 -1
- package/dist/design/slots.d.ts +12 -10
- package/dist/design/slots.d.ts.map +1 -1
- package/dist/design/slots.js +26 -2
- package/dist/design/slots.js.map +1 -1
- package/dist/fonts/faces.d.ts.map +1 -1
- package/dist/fonts/faces.js +6 -0
- package/dist/fonts/faces.js.map +1 -1
- package/dist/highlightMarks.d.ts.map +1 -1
- package/dist/highlightMarks.js +6 -0
- package/dist/highlightMarks.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +42 -4
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +2 -0
- package/dist/measure/rich.js.map +1 -1
- package/dist/migrate/markdownRuns.d.ts +70 -0
- package/dist/migrate/markdownRuns.d.ts.map +1 -0
- package/dist/migrate/markdownRuns.js +215 -0
- package/dist/migrate/markdownRuns.js.map +1 -0
- package/dist/migrate/pageDesign.d.ts +119 -0
- package/dist/migrate/pageDesign.d.ts.map +1 -0
- package/dist/migrate/pageDesign.js +446 -0
- package/dist/migrate/pageDesign.js.map +1 -0
- package/dist/migrate/textToMarkdown.d.ts +124 -0
- package/dist/migrate/textToMarkdown.d.ts.map +1 -0
- package/dist/migrate/textToMarkdown.js +716 -0
- package/dist/migrate/textToMarkdown.js.map +1 -0
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +44 -3
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/types.d.ts +20 -0
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +298 -30
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildMeasurement.d.ts +7 -2
- package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
- package/dist/pipeline/buildMeasurement.js +21 -5
- package/dist/pipeline/buildMeasurement.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts +39 -14
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +50 -4
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/composedLayout.d.ts +51 -6
- package/dist/pipeline/composedLayout.d.ts.map +1 -1
- package/dist/pipeline/composedLayout.js +633 -78
- package/dist/pipeline/composedLayout.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts +15 -0
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +98 -4
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/pipeline/floatPlacement.d.ts +2 -0
- package/dist/pipeline/floatPlacement.d.ts.map +1 -1
- package/dist/pipeline/floatPlacement.js +1 -0
- package/dist/pipeline/floatPlacement.js.map +1 -1
- package/dist/pipeline/frameLayout.d.ts +7 -4
- package/dist/pipeline/frameLayout.d.ts.map +1 -1
- package/dist/pipeline/frameLayout.js +5 -2
- package/dist/pipeline/frameLayout.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +20 -1
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +225 -70
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +1 -0
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/mirrorFrame.d.ts.map +1 -1
- package/dist/pipeline/mirrorFrame.js +25 -2
- package/dist/pipeline/mirrorFrame.js.map +1 -1
- package/dist/pipeline/outline.d.ts.map +1 -1
- package/dist/pipeline/outline.js +2 -0
- package/dist/pipeline/outline.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts +28 -0
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +21 -0
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/pipeline/styles.d.ts +1 -1
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +4 -0
- package/dist/pipeline/styles.js.map +1 -1
- package/dist/pipeline/textWrap.d.ts +4 -1
- package/dist/pipeline/textWrap.d.ts.map +1 -1
- package/dist/pipeline/textWrap.js +17 -3
- package/dist/pipeline/textWrap.js.map +1 -1
- package/dist/pipeline/toc.d.ts.map +1 -1
- package/dist/pipeline/toc.js +1 -0
- package/dist/pipeline/toc.js.map +1 -1
- package/dist/types.d.ts +77 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +290 -15
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/dist/word/toDocx.d.ts.map +1 -1
- package/dist/word/toDocx.js +14 -0
- package/dist/word/toDocx.js.map +1 -1
- package/dist/word/word.test.js +11 -1
- package/dist/word/word.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildDocument } from '../../pipeline/build';
|
|
3
|
+
import { createMeasurementCache } from '../../measure';
|
|
4
|
+
import { contrastDocument, contrastPairs } from '../../contrast';
|
|
5
|
+
class StubCtx {
|
|
6
|
+
font = '';
|
|
7
|
+
measureText(s) {
|
|
8
|
+
return { width: s.length * 7 };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
globalThis.OffscreenCanvas = class {
|
|
12
|
+
getContext() {
|
|
13
|
+
return new StubCtx();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
import { PAGE8_CONFIG, PAGE8_RESOURCES, page8Fixture } from './fixtures/page8';
|
|
17
|
+
// The contrast check on a composed page (#908): frame text is read on what
|
|
18
|
+
// is painted under it, in the page's paint order — the frame's own box, the
|
|
19
|
+
// template's decoration (boxes and pictures), then the paper.
|
|
20
|
+
const mm = (value) => ({ value, unit: 'mm' });
|
|
21
|
+
const pt = (value) => ({ value, unit: 'pt' });
|
|
22
|
+
const hex = (h) => ({ hex: h, model: 'hex' });
|
|
23
|
+
const BLUE = '#005a8c';
|
|
24
|
+
const GREEN = '#2e6417';
|
|
25
|
+
const NAVY = '#102a43';
|
|
26
|
+
/** The p. 8 stand-in's template with three tabs of white lettering: on a
|
|
27
|
+
* blue and a green box of the decoration, on bare paper, and in a frame
|
|
28
|
+
* that paints its own navy box over a white decoration box. */
|
|
29
|
+
function tabsConfig(extra = {}) {
|
|
30
|
+
const base = PAGE8_CONFIG.pageTemplates[0];
|
|
31
|
+
const tab = (name, x, style = 'pestana', layer = 1) => ({
|
|
32
|
+
name, style, layer, width: mm(50), ...(extra.rotate ? { rotate: extra.rotate } : {}),
|
|
33
|
+
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(x), y: mm(8) } },
|
|
34
|
+
});
|
|
35
|
+
const band = (id, x, color) => ({
|
|
36
|
+
kind: 'box', id, placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(x), y: mm(6) }, size: { width: mm(54), height: mm(14) } }, style: { backgroundColor: hex(color) },
|
|
37
|
+
});
|
|
38
|
+
const decoration = base.decoration.elements
|
|
39
|
+
// Keep the white card; drop the full-bleed picture (and the theme name
|
|
40
|
+
// set on it) so the paper is what lies under the bare tab.
|
|
41
|
+
.filter((e) => !['fondo', 'tema'].includes(e.id));
|
|
42
|
+
return {
|
|
43
|
+
...PAGE8_CONFIG,
|
|
44
|
+
locale: 'es',
|
|
45
|
+
calloutStyles: [
|
|
46
|
+
...(PAGE8_CONFIG.calloutStyles ?? []),
|
|
47
|
+
// Frameless lettering: no fill of its own, white text.
|
|
48
|
+
{ id: 'pestana', backgroundEnabled: false, border: { enabled: false }, padding: { top: mm(1), right: mm(2), bottom: mm(1), left: mm(2) }, body: { color: hex('#ffffff'), fontSize: pt(11), lineHeight: pt(14) } },
|
|
49
|
+
{ id: 'pestana-propia', backgroundEnabled: true, background: hex(NAVY), border: { enabled: false }, body: { color: hex('#ffffff'), fontSize: pt(11), lineHeight: pt(14) } },
|
|
50
|
+
],
|
|
51
|
+
pageTemplates: [{
|
|
52
|
+
...base,
|
|
53
|
+
decoration: { elements: [...decoration, band('azul', 8, BLUE), band('verde', 68, GREEN), band('blanca', 188, '#ffffff')] },
|
|
54
|
+
frames: [...(base.frames ?? []), tab('t-azul', 10), tab('t-verde', 70), tab('t-papel', 130), tab('t-propia', 190, 'pestana-propia')],
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const MARKDOWN = `::::page{template="tema-cajas" tema="Retos del planeta"}
|
|
59
|
+
# El dinero y la vida de cada día
|
|
60
|
+
|
|
61
|
+
Cada familia necesita alimentos, ropa y una casa.
|
|
62
|
+
|
|
63
|
+
:::frame{slot="t-azul"}
|
|
64
|
+
Pestaña azul
|
|
65
|
+
:::
|
|
66
|
+
|
|
67
|
+
:::frame{slot="t-verde"}
|
|
68
|
+
Pestaña verde
|
|
69
|
+
:::
|
|
70
|
+
|
|
71
|
+
:::frame{slot="t-papel"}
|
|
72
|
+
Pestaña suelta
|
|
73
|
+
:::
|
|
74
|
+
|
|
75
|
+
:::frame{slot="t-propia"}
|
|
76
|
+
Pestaña propia
|
|
77
|
+
:::
|
|
78
|
+
::::
|
|
79
|
+
`;
|
|
80
|
+
function build(config) {
|
|
81
|
+
return buildDocument({ markdown: MARKDOWN, resources: PAGE8_RESOURCES }, config, createMeasurementCache());
|
|
82
|
+
}
|
|
83
|
+
describe('contrast of frame text over template decoration (#908)', () => {
|
|
84
|
+
it('reads white tab lettering on the decoration box under it, not on the paper', () => {
|
|
85
|
+
const doc = build(tabsConfig());
|
|
86
|
+
const pairs = contrastPairs(doc).filter((p) => p.color === '#ffffff');
|
|
87
|
+
const ground = (text) => pairs.find((p) => p.text.startsWith(text))?.background;
|
|
88
|
+
expect(ground('Pestaña azul')).toBe(BLUE);
|
|
89
|
+
expect(ground('Pestaña verde')).toBe(GREEN);
|
|
90
|
+
// Nothing under this one but the paper: white on white, as before.
|
|
91
|
+
expect(ground('Pestaña suelta')).toBe('#ffffff');
|
|
92
|
+
// A frame that paints its own box reads on it, whatever lies beneath.
|
|
93
|
+
expect(ground('Pestaña propia')).toBe(NAVY);
|
|
94
|
+
const low = contrastDocument(doc).filter((i) => i.kind === 'lowContrast');
|
|
95
|
+
expect(low.map((i) => i.text)).toEqual(['Pestaña suelta']);
|
|
96
|
+
expect(low[0]).toMatchObject({ color: '#ffffff', background: '#ffffff' });
|
|
97
|
+
});
|
|
98
|
+
it('follows a turned frame onto the sheet before it reads the ground', () => {
|
|
99
|
+
const doc = build(tabsConfig({ rotate: 4 }));
|
|
100
|
+
const pairs = contrastPairs(doc).filter((p) => p.color === '#ffffff');
|
|
101
|
+
expect(pairs.find((p) => p.text.startsWith('Pestaña azul'))?.background).toBe(BLUE);
|
|
102
|
+
expect(pairs.find((p) => p.text.startsWith('Pestaña verde'))?.background).toBe(GREEN);
|
|
103
|
+
});
|
|
104
|
+
it('reads the decoration’s own text on the decoration under it', () => {
|
|
105
|
+
const config = tabsConfig();
|
|
106
|
+
const tpl = config.pageTemplates[0];
|
|
107
|
+
const elements = [...tpl.decoration.elements, {
|
|
108
|
+
kind: 'text', id: 'sello', content: 'Sello', fontSize: pt(9), color: hex('#ffffff'),
|
|
109
|
+
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(70), y: mm(15) } },
|
|
110
|
+
}];
|
|
111
|
+
const doc = build({ ...config, pageTemplates: [{ ...tpl, decoration: { elements } }] });
|
|
112
|
+
expect(contrastPairs(doc).find((p) => p.text === 'Sello')?.background).toBe(GREEN);
|
|
113
|
+
});
|
|
114
|
+
it('leaves the p. 8 stand-in with no low-contrast frame text', () => {
|
|
115
|
+
for (const locale of ['es', 'en']) {
|
|
116
|
+
const doc = buildDocument({ markdown: page8Fixture(locale).markdown, resources: PAGE8_RESOURCES }, { ...PAGE8_CONFIG, locale }, createMeasurementCache());
|
|
117
|
+
// The white theme name stands on the full-bleed picture: skipped, as
|
|
118
|
+
// any text over a picture is.
|
|
119
|
+
expect(contrastPairs(doc).some((p) => p.text.startsWith(locale === 'es' ? 'Retos' : 'Planet'))).toBe(false);
|
|
120
|
+
expect(contrastDocument(doc).filter((i) => i.kind === 'lowContrast')).toEqual([]);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=contrast.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.test.js","sourceRoot":"","sources":["../../../src/__tests__/composed/contrast.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,9 +22,15 @@ describe('corpus (#854)', () => {
|
|
|
22
22
|
}
|
|
23
23
|
})
|
|
24
24
|
: [];
|
|
25
|
+
// The composed-page recipes (#875–#879) write page fences on purpose; every other text
|
|
26
|
+
// must read as it did before the syntax existed.
|
|
27
|
+
const COMPOSED = /^::::+\s*page\s*\{/m;
|
|
25
28
|
it.runIf(files.length > 0)('no Cookbook text reads as a composed page', () => {
|
|
26
29
|
for (const f of files) {
|
|
27
|
-
const
|
|
30
|
+
const text = readFileSync(f, 'utf8');
|
|
31
|
+
if (COMPOSED.test(text))
|
|
32
|
+
continue;
|
|
33
|
+
const { blocks } = parseMarkdownWithIssues(extractFrontmatter(text).content);
|
|
28
34
|
const hit = blocks.find((b) => b.composedPage || b.slot || b.figure || b.containerName === 'frame');
|
|
29
35
|
expect(hit, f).toBeUndefined();
|
|
30
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corpus.test.js","sourceRoot":"","sources":["../../../src/__tests__/composed/corpus.test.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,cAAc;AACd;;;oEAGoE;AACpE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC;QAC5B,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5F,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC;QACF,CAAC,CAAC,EAAE,CAAC;IACP,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,2CAA2C,EAAE,GAAG,EAAE;QAC3E,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,
|
|
1
|
+
{"version":3,"file":"corpus.test.js","sourceRoot":"","sources":["../../../src/__tests__/composed/corpus.test.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,cAAc;AACd;;;oEAGoE;AACpE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC;QAC5B,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC;gBACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5F,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC;QACF,CAAC,CAAC,EAAE,CAAC;IACP,uFAAuF;IACvF,iDAAiD;IACjD,MAAM,QAAQ,GAAG,qBAAqB,CAAC;IACvC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,2CAA2C,EAAE,GAAG,EAAE;QAC3E,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACrC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAClC,MAAM,EAAE,MAAM,EAAE,GAAG,uBAAuB,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;YAC7E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,aAAa,KAAK,OAAO,CAAC,CAAC;YACpG,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACjC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fit.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/composed/fit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildDocument } from '../../pipeline/build';
|
|
3
|
+
import { createMeasurementCache } from '../../measure';
|
|
4
|
+
import { formatWarning } from '../../pipeline/contentWarnings';
|
|
5
|
+
import { describeFitStep, describeFrameOverflowProposal, describeReflowEvent } from '../../composed/reflowLog';
|
|
6
|
+
import { frameBounds, frameContentRect, frameMatrix } from '../../composed/paint';
|
|
7
|
+
import { clearComposedPageMemo, composedPageMemoStats, setComposedPageMemo } from '../../composed/pageMemo';
|
|
8
|
+
import { elementsForSource, sourceAt } from '../../composed/sourceMap';
|
|
9
|
+
import { page8Fixture } from './fixtures/page8';
|
|
10
|
+
// Deterministic text measurement stub (no DOM in the node test env): seven
|
|
11
|
+
// px per character, whatever the face.
|
|
12
|
+
class StubCtx {
|
|
13
|
+
font = '';
|
|
14
|
+
measureText(s) {
|
|
15
|
+
return { width: s.length * 7 };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
globalThis.OffscreenCanvas = class {
|
|
19
|
+
getContext() {
|
|
20
|
+
return new StubCtx();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const mm = (value) => ({ value, unit: 'mm' });
|
|
24
|
+
const DPI = 300;
|
|
25
|
+
const MM = DPI / 25.4;
|
|
26
|
+
const toMm = (v) => v / MM;
|
|
27
|
+
/** The stand-in (#855) with its frames patched, its Markdown changed, and
|
|
28
|
+
* other templates added after its own. */
|
|
29
|
+
function standIn(opts = {}) {
|
|
30
|
+
const { markdown, config, resources } = page8Fixture(opts.locale ?? 'es');
|
|
31
|
+
const base = config.pageTemplates[0];
|
|
32
|
+
const patch = opts.frames ?? {};
|
|
33
|
+
const tpl = {
|
|
34
|
+
...base,
|
|
35
|
+
frames: base.frames.map((f) => (patch[f.name] ? { ...f, ...patch[f.name] } : f)),
|
|
36
|
+
...(opts.groups ? { groups: opts.groups } : {}),
|
|
37
|
+
};
|
|
38
|
+
return { markdown: (opts.markdown ?? ((m) => m))(markdown), config: { ...config, pageTemplates: [tpl, ...(opts.more ?? [])] }, resources };
|
|
39
|
+
}
|
|
40
|
+
const build = (f) => buildDocument({ markdown: f.markdown, resources: f.resources }, f.config, createMeasurementCache());
|
|
41
|
+
const page0 = (doc) => doc.pages.find((p) => p.composed);
|
|
42
|
+
const frameOf = (doc, name) => page0(doc).composed.frames.find((f) => f.name === name);
|
|
43
|
+
const overflowsOf = (doc) => (doc.contentWarnings ?? []).filter((w) => w.kind === 'frameOverflow');
|
|
44
|
+
const eventsOf = (doc, frame) => page0(doc).composed.log.filter((e) => !frame || e.frame === frame).map((e) => `${e.frame} ${e.kind}`);
|
|
45
|
+
/** `n` more paragraphs at the head of the first box (`dinero`). */
|
|
46
|
+
const moreMoney = (n, word = 'Más dinero') => (md) => md.replace(/(:::frame\{slot="dinero"[^\n]*\n)/, `$1${Array.from({ length: n }, (_, i) => `${word} ${i} `.repeat(30).trim()).join('\n\n')}\n\n`);
|
|
47
|
+
/** `k` more words at the head of the first box (one short paragraph). */
|
|
48
|
+
const moreWords = (k) => (md) => md.replace(/(:::frame\{slot="dinero"[^\n]*\n)/, `$1${'Más dinero '.repeat(k).trim()}\n\n`);
|
|
49
|
+
/** The first box's photo set inside the box (its story) instead of in a
|
|
50
|
+
* frame of its own. */
|
|
51
|
+
const photoInBox = (md) => md.replace('slot="dinero-fotos"', 'slot="dinero"');
|
|
52
|
+
describe('the reflow ladder on the page-8 stand-in (#858)', () => {
|
|
53
|
+
it('1. free space: a fixed height grows to its maxHeight, and the ladder stops there', () => {
|
|
54
|
+
const fixed = { height: mm(36), minHeight: undefined };
|
|
55
|
+
const capped = build(standIn({ frames: { dinero: fixed }, markdown: moreMoney(3) }));
|
|
56
|
+
// Without a maximum the height is the template's: the text shrinks.
|
|
57
|
+
expect(frameOf(capped, 'dinero').rect.height).toBeCloseTo(36 * MM, 3);
|
|
58
|
+
expect(frameOf(capped, 'dinero').scale).toBeLessThan(1);
|
|
59
|
+
const free = build(standIn({ frames: { dinero: { ...fixed, maxHeight: mm(90) } }, markdown: moreMoney(3) }));
|
|
60
|
+
const d = frameOf(free, 'dinero');
|
|
61
|
+
expect(toMm(d.rect.height)).toBeGreaterThan(36.5);
|
|
62
|
+
expect(toMm(d.rect.height)).toBeLessThanOrEqual(90);
|
|
63
|
+
expect(d.scale).toBe(1);
|
|
64
|
+
expect(d.overflowPx).toBe(0);
|
|
65
|
+
expect(eventsOf(free, 'dinero')).toEqual(['dinero grew']);
|
|
66
|
+
expect(eventsOf(free)).toContain('gasto moved');
|
|
67
|
+
expect(overflowsOf(free)).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it('2. push siblings: a longer box moves the boxes below; nothing is scaled', () => {
|
|
70
|
+
const doc = build(standIn({ markdown: moreMoney(3) }));
|
|
71
|
+
expect(eventsOf(doc).some((e) => /imageScaled|textScaled/.test(e))).toBe(false);
|
|
72
|
+
expect(eventsOf(doc)).toContain('gasto moved');
|
|
73
|
+
for (const f of page0(doc).composed.frames)
|
|
74
|
+
expect(f.scale).toBe(1);
|
|
75
|
+
expect(overflowsOf(doc)).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
it('3. pictures: the box’s photo is set smaller, the largest 2.5 % step that fits, within imageMinScale', () => {
|
|
78
|
+
const atNatural = build(standIn({ markdown: photoInBox }));
|
|
79
|
+
const natural = frameOf(atNatural, 'dinero').rect.height;
|
|
80
|
+
// The box 4 mm shorter than its text and photo need.
|
|
81
|
+
const height = mm(Math.floor(toMm(natural)) - 4);
|
|
82
|
+
const doc = build(standIn({ frames: { dinero: { height, minHeight: undefined } }, markdown: photoInBox }));
|
|
83
|
+
const d = frameOf(doc, 'dinero');
|
|
84
|
+
expect(d.overflowPx).toBe(0);
|
|
85
|
+
expect(d.scale).toBe(1);
|
|
86
|
+
expect(d.imageScale).toBeGreaterThanOrEqual(0.8);
|
|
87
|
+
expect(d.imageScale).toBeLessThan(1);
|
|
88
|
+
expect(Math.round(((1 - d.imageScale) / 0.025) * 1e6) % 1e6).toBe(0);
|
|
89
|
+
expect(page0(doc).composed.log).toContainEqual({ frame: 'dinero', kind: 'imageScaled', scale: d.imageScale });
|
|
90
|
+
// One step larger does not fit: the box would overflow at imageMinScale + 2.5 %.
|
|
91
|
+
const larger = build(standIn({ frames: { dinero: { height, minHeight: undefined, fit: { imageMinScale: d.imageScale + 0.025, shrinkText: false } } }, markdown: photoInBox }));
|
|
92
|
+
expect(frameOf(larger, 'dinero').overflowPx).toBeGreaterThan(0);
|
|
93
|
+
// With scaleImages off, the text shrinks instead.
|
|
94
|
+
const off = build(standIn({ frames: { dinero: { height, minHeight: undefined, fit: { scaleImages: false } } }, markdown: photoInBox }));
|
|
95
|
+
expect(frameOf(off, 'dinero').imageScale).toBeUndefined();
|
|
96
|
+
expect(frameOf(off, 'dinero').scale).toBeLessThan(1);
|
|
97
|
+
});
|
|
98
|
+
it('4. redistribute: a sibling above its minimum gives space back, down to its minimum', () => {
|
|
99
|
+
const gasto = { height: mm(62), minHeight: mm(40) };
|
|
100
|
+
const doc = build(standIn({ frames: { gasto }, markdown: moreMoney(6) }));
|
|
101
|
+
expect(overflowsOf(doc)).toEqual([]);
|
|
102
|
+
expect(toMm(frameOf(doc, 'gasto').rect.height)).toBeLessThan(62);
|
|
103
|
+
expect(toMm(frameOf(doc, 'gasto').rect.height)).toBeGreaterThanOrEqual(40 - 1e-6);
|
|
104
|
+
expect(frameOf(doc, 'ahorro').scale).toBe(1);
|
|
105
|
+
expect(eventsOf(doc, 'gasto')).toContain('gasto resized');
|
|
106
|
+
// Without the step (a fixed height of 62 with nothing under it), ahorro
|
|
107
|
+
// would not fit: the same text with the minimum at the height does not.
|
|
108
|
+
const rigid = build(standIn({ frames: { gasto: { height: mm(62), minHeight: mm(62) } }, markdown: moreMoney(6) }));
|
|
109
|
+
expect(frameOf(rigid, 'ahorro').scale < 1 || frameOf(rigid, 'ahorro').overflowPx > 0).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it('5. shrink text: down to fit.minScale in 2.5 % steps, the largest that fits; shrinkText: false turns it off', () => {
|
|
112
|
+
const fixed = { height: mm(36), minHeight: undefined };
|
|
113
|
+
const doc = build(standIn({ frames: { dinero: fixed }, markdown: moreWords(40) }));
|
|
114
|
+
const d = frameOf(doc, 'dinero');
|
|
115
|
+
expect(d.overflowPx).toBe(0);
|
|
116
|
+
expect(d.scale).toBeLessThan(1);
|
|
117
|
+
expect(d.scale).toBeGreaterThanOrEqual(0.9);
|
|
118
|
+
expect(Math.round(((1 - d.scale) / 0.025) * 1e6) % 1e6).toBe(0);
|
|
119
|
+
expect(page0(doc).composed.log).toContainEqual({ frame: 'dinero', kind: 'textScaled', scale: d.scale });
|
|
120
|
+
// Laid out larger and painted at the scale: the matrix maps its content box onto its box.
|
|
121
|
+
expect(frameContentRect(d).width).toBeCloseTo(d.rect.width / d.scale, 6);
|
|
122
|
+
expect(frameBounds(d).x).toBeCloseTo(d.rect.x, 6);
|
|
123
|
+
expect(frameBounds(d).y).toBeCloseTo(d.rect.y, 6);
|
|
124
|
+
expect(frameBounds(d).width).toBeCloseTo(d.rect.width, 6);
|
|
125
|
+
expect(frameBounds(d).height).toBeCloseTo(d.rect.height, 6);
|
|
126
|
+
expect(frameMatrix(d)).toEqual([d.scale, 0, 0, d.scale, d.rect.x * (1 - d.scale), d.rect.y * (1 - d.scale)]);
|
|
127
|
+
// The box keeps its drawn padding: the text stays clear of the photos
|
|
128
|
+
// set over the box's right 62 mm, whatever the scale.
|
|
129
|
+
const blocks = d.blockIds.map((id) => page0(doc).floats.find((b) => b.id === id));
|
|
130
|
+
const rightOnSheet = Math.max(...blocks.flatMap((b) => b.lines.filter((l) => l.text.length > 0).map((l) => d.rect.x + (l.bbox.x + l.bbox.width - d.rect.x) * d.scale)));
|
|
131
|
+
// (A line's box may carry its trailing space: about one stub character.)
|
|
132
|
+
expect(rightOnSheet).toBeLessThanOrEqual(d.rect.x + d.rect.width - 62 * MM + 8 * d.scale);
|
|
133
|
+
expect(rightOnSheet).toBeGreaterThan(d.rect.x + d.rect.width - 62 * MM - 10 * MM);
|
|
134
|
+
// One step larger overflows.
|
|
135
|
+
if (d.scale < 0.975) {
|
|
136
|
+
const larger = build(standIn({ frames: { dinero: { ...fixed, fit: { minScale: d.scale + 0.025 } } }, markdown: moreWords(40) }));
|
|
137
|
+
expect(frameOf(larger, 'dinero').overflowPx).toBeGreaterThan(0);
|
|
138
|
+
}
|
|
139
|
+
const off = build(standIn({ frames: { dinero: { ...fixed, fit: { shrinkText: false } } }, markdown: moreWords(40) }));
|
|
140
|
+
expect(frameOf(off, 'dinero').scale).toBe(1);
|
|
141
|
+
expect(frameOf(off, 'dinero').overflowPx).toBeGreaterThan(0);
|
|
142
|
+
expect(overflowsOf(off)[0].stepsTried).not.toContain('shrinkText');
|
|
143
|
+
});
|
|
144
|
+
it('6. continuation: a frame that continues on another page is cut there, at full size, and warns no more (#859)', () => {
|
|
145
|
+
const doc = build(standIn({ frames: { dinero: { height: mm(36), minHeight: undefined, overflow: { continueIn: 'next-page' } } }, markdown: moreMoney(4) }));
|
|
146
|
+
expect(overflowsOf(doc)).toEqual([]);
|
|
147
|
+
const d = frameOf(doc, 'dinero');
|
|
148
|
+
expect(d.overflowPx).toBe(0);
|
|
149
|
+
expect(d.scale).toBe(1);
|
|
150
|
+
expect(d.continuesTo).toBe('cp0:dinero~1');
|
|
151
|
+
expect(eventsOf(doc, 'dinero')).toEqual(['dinero continued']);
|
|
152
|
+
expect(doc.pages.filter((p) => p.composed).length).toBeGreaterThan(1);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('frameOverflow: the steps tried and the proposals (#858)', () => {
|
|
156
|
+
const tooLong = { frames: { dinero: { height: mm(36), minHeight: undefined } }, markdown: moreMoney(4) };
|
|
157
|
+
const proposal = (w, kind) => w.proposals.find((p) => p.kind === kind);
|
|
158
|
+
for (const locale of ['es', 'en']) {
|
|
159
|
+
it(`${locale}: names page, template, frame and locale, the steps tried and the final scale`, () => {
|
|
160
|
+
const doc = build(standIn({ ...tooLong, locale, markdown: moreMoney(4, locale === 'es' ? 'Más dinero' : 'More money') }));
|
|
161
|
+
const ws = overflowsOf(doc);
|
|
162
|
+
expect(ws).toHaveLength(1);
|
|
163
|
+
const w = ws[0];
|
|
164
|
+
expect(w).toMatchObject({ kind: 'frameOverflow', template: 'tema-cajas', frame: 'dinero', fenceIndex: 0, pageIndex: 0, locale, scale: 0.9, configPath: '/pageTemplates/0/frames/2' });
|
|
165
|
+
expect(w.stepsTried).toEqual(['free', 'push', 'redistribute', 'shrinkText']);
|
|
166
|
+
expect(w.overflowMm).toBeCloseTo(toMm(frameOf(doc, 'dinero').overflowPx), 1);
|
|
167
|
+
expect(w.sourceStart).toBeGreaterThan(0);
|
|
168
|
+
const text = formatWarning(w);
|
|
169
|
+
expect(text).toContain(`Frame "dinero" of template "tema-cajas" (${locale})`);
|
|
170
|
+
expect(text).toContain('text down to 90 %');
|
|
171
|
+
expect(text).toContain('to fit:');
|
|
172
|
+
expect(page0(doc).composed.log.slice(-2)).toEqual([{ frame: 'dinero', kind: 'textScaled', scale: 0.9 }, { frame: 'dinero', kind: 'overflow', toMm: w.overflowMm }]);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
it('allowShrink: the largest text scale that fits, worked out on the page', () => {
|
|
176
|
+
const w = overflowsOf(build(standIn(tooLong)))[0];
|
|
177
|
+
const p = proposal(w, 'allowShrink');
|
|
178
|
+
expect(p.path).toBe('/pageTemplates/0/frames/2/fit/minScale');
|
|
179
|
+
expect(p.value).toBeLessThan(0.9);
|
|
180
|
+
const fitted = build(standIn({ ...tooLong, frames: { dinero: { ...tooLong.frames.dinero, fit: { minScale: p.value } } } }));
|
|
181
|
+
expect(overflowsOf(fitted)).toEqual([]);
|
|
182
|
+
expect(frameOf(fitted, 'dinero').scale).toBe(p.value);
|
|
183
|
+
const short = build(standIn({ ...tooLong, frames: { dinero: { ...tooLong.frames.dinero, fit: { minScale: p.value + 0.025 } } } }));
|
|
184
|
+
expect(overflowsOf(short)).toHaveLength(1);
|
|
185
|
+
// With shrinkText off, the proposal turns it on as well.
|
|
186
|
+
const off = overflowsOf(build(standIn({ ...tooLong, frames: { dinero: { ...tooLong.frames.dinero, fit: { shrinkText: false } } } })))[0];
|
|
187
|
+
expect(proposal(off, 'allowShrink').also).toEqual([{ path: '/pageTemplates/0/frames/2/fit/shrinkText', value: true }]);
|
|
188
|
+
});
|
|
189
|
+
it('raiseMax: the frame’s height plus the shortfall, in whole millimetres, and it fits', () => {
|
|
190
|
+
const w = overflowsOf(build(standIn(tooLong)))[0];
|
|
191
|
+
const p = proposal(w, 'raiseMax');
|
|
192
|
+
expect(p.path).toBe('/pageTemplates/0/frames/2/maxHeight');
|
|
193
|
+
expect(p.value.unit).toBe('mm');
|
|
194
|
+
expect(Number.isInteger(p.value.value)).toBe(true);
|
|
195
|
+
expect(p.value.value).toBe(Math.ceil(36 + w.overflowMm - 0.05));
|
|
196
|
+
const fitted = build(standIn({ ...tooLong, frames: { dinero: { ...tooLong.frames.dinero, maxHeight: p.value } } }));
|
|
197
|
+
expect(overflowsOf(fitted)).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
it('lowerMin: a sibling at its minimum with less content gives the shortfall; fits once applied', () => {
|
|
200
|
+
// An own-box stack: the three boxes share 140 mm; gasto stands at its 62 mm minimum.
|
|
201
|
+
const groups = [{ name: 'cajas', kind: 'stack', frames: ['dinero', 'gasto', 'ahorro'], gap: mm(12), placement: { anchor: { to: '#texto', edge: 'below' }, offset: { y: mm(9) }, size: { width: mm(186), height: mm(150) } } }];
|
|
202
|
+
const opts = { groups, frames: { dinero: { minHeight: mm(36) }, gasto: { minHeight: mm(62) }, ahorro: { minHeight: mm(30), fit: { shrinkText: false } } }, markdown: moreMoney(2) };
|
|
203
|
+
const w = overflowsOf(build(standIn(opts))).find((x) => x.frame === 'dinero' || x.frame === 'ahorro');
|
|
204
|
+
expect(w).toBeDefined();
|
|
205
|
+
const p = proposal(w, 'lowerMin');
|
|
206
|
+
expect(p).toMatchObject({ frame: 'gasto', path: '/pageTemplates/0/frames/4/minHeight' });
|
|
207
|
+
expect(p.value.value).toBeLessThan(62);
|
|
208
|
+
const fitted = build(standIn({ ...opts, frames: { ...opts.frames, gasto: { minHeight: p.value } } }));
|
|
209
|
+
expect(overflowsOf(fitted).filter((x) => x.frame === w.frame)).toEqual([]);
|
|
210
|
+
});
|
|
211
|
+
it('useTemplate: another template of the book with a frame of that name where it fits', () => {
|
|
212
|
+
const roomy = {
|
|
213
|
+
id: 'tema-amplio',
|
|
214
|
+
frames: [
|
|
215
|
+
{ name: 'titulo', takes: 'h1' },
|
|
216
|
+
{ name: 'texto', flow: true, placement: { anchor: { to: '#titulo', edge: 'below' } } },
|
|
217
|
+
{ name: 'dinero', placement: { anchor: { to: '#texto', edge: 'below' } } },
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
const tight = { id: 'otra', frames: [{ name: 'dinero', height: mm(10) }] };
|
|
221
|
+
const w = overflowsOf(build(standIn({ ...tooLong, more: [tight, roomy] })))[0];
|
|
222
|
+
expect(proposal(w, 'useTemplate')).toMatchObject({ kind: 'useTemplate', value: 'tema-amplio' });
|
|
223
|
+
const at = proposal(w, 'useTemplate');
|
|
224
|
+
const md = standIn(tooLong).markdown;
|
|
225
|
+
expect(md.slice(at.sourceStart, at.sourceEnd)).toBe('tema-cajas');
|
|
226
|
+
});
|
|
227
|
+
it('shortenText: about the characters of the lines that do not fit; cutting them makes it fit', () => {
|
|
228
|
+
const f = standIn(tooLong);
|
|
229
|
+
const w = overflowsOf(build(f))[0];
|
|
230
|
+
const p = proposal(w, 'shortenText');
|
|
231
|
+
expect(p.value).toBeGreaterThan(0);
|
|
232
|
+
expect(p.sourceStart).toBe(w.sourceStart);
|
|
233
|
+
// The same box with that many characters cut from the end of its added text.
|
|
234
|
+
const added = Array.from({ length: 4 }, (_, i) => `Más dinero ${i} `.repeat(30).trim()).join('\n\n');
|
|
235
|
+
const cut = added.slice(0, added.length - p.value).trim();
|
|
236
|
+
const after = build(standIn({ ...tooLong, markdown: (md) => md.replace(/(:::frame\{slot="dinero"[^\n]*\n)/, `$1${cut}\n\n`) }));
|
|
237
|
+
expect(overflowsOf(after)).toEqual([]);
|
|
238
|
+
});
|
|
239
|
+
it('continueNextPage: overflow.continueIn of the frame', () => {
|
|
240
|
+
const w = overflowsOf(build(standIn(tooLong)))[0];
|
|
241
|
+
expect(proposal(w, 'continueNextPage')).toEqual({ kind: 'continueNextPage', path: '/pageTemplates/0/frames/2/overflow/continueIn', value: 'next-page' });
|
|
242
|
+
});
|
|
243
|
+
it('words every step and proposal', () => {
|
|
244
|
+
expect(describeFitStep('shrinkText', { scale: 0.9 })).toBe('text down to 90 %');
|
|
245
|
+
expect(describeFitStep('scaleImages', { imageScale: 0.825 })).toBe('pictures down to 82.5 %');
|
|
246
|
+
expect(describeFitStep('free')).toBe('free space');
|
|
247
|
+
expect(describeReflowEvent({ frame: 'a', kind: 'textScaled', scale: 0.925 })).toBe('a: text at 92.5 %');
|
|
248
|
+
expect(describeReflowEvent({ frame: 'a', kind: 'imageScaled', scale: 0.8 })).toBe('a: picture at 80 %');
|
|
249
|
+
expect(describeFitStep('redistribute')).toBe('the other frames of its group down to their minimum');
|
|
250
|
+
expect(describeFitStep('continue')).toContain('another page');
|
|
251
|
+
expect(describeFrameOverflowProposal({ kind: 'allowShrink', path: '/x', value: 0.85 })).toBe('let its text shrink to 85 % (fit.minScale: 0.85)');
|
|
252
|
+
expect(describeFrameOverflowProposal({ kind: 'raiseMax', path: '/x', value: mm(64) })).toBe('raise maxHeight to 64 mm');
|
|
253
|
+
expect(describeFrameOverflowProposal({ kind: 'lowerMin', path: '/x', value: mm(40), frame: 'gasto' })).toBe('lower the minHeight of "gasto" to 40 mm');
|
|
254
|
+
expect(describeFrameOverflowProposal({ kind: 'continueNextPage', path: '/x', value: 'next-page' })).toBe('continue on the next page (overflow.continueIn: "next-page")');
|
|
255
|
+
expect(describeFrameOverflowProposal({ kind: 'useTemplate', value: 'b' })).toBe('set the page with template "b"');
|
|
256
|
+
expect(describeFrameOverflowProposal({ kind: 'shortenText', value: 120 })).toBe('cut about 120 characters');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe('the ladder is deterministic and stays on its page (#858)', () => {
|
|
260
|
+
it('the same input gives the same VDT, scales included', () => {
|
|
261
|
+
const f = standIn({ frames: { dinero: { height: mm(36), minHeight: undefined } }, markdown: moreWords(40) });
|
|
262
|
+
const a = build(f);
|
|
263
|
+
const b = build(f);
|
|
264
|
+
expect(frameOf(a, 'dinero').scale).toBeLessThan(1);
|
|
265
|
+
expect(JSON.stringify(b.pages)).toBe(JSON.stringify(a.pages));
|
|
266
|
+
expect(JSON.stringify(b.contentWarnings)).toBe(JSON.stringify(a.contentWarnings));
|
|
267
|
+
});
|
|
268
|
+
it('frames that overflow are taken in template order', () => {
|
|
269
|
+
// Two frames that both overflow: the warnings follow the template.
|
|
270
|
+
const t = {
|
|
271
|
+
id: 'two',
|
|
272
|
+
frames: [
|
|
273
|
+
{ name: 'b', height: mm(10), width: mm(60), placement: { anchor: { to: 'container', edge: 'top-right' } } },
|
|
274
|
+
{ name: 'a', height: mm(10), width: mm(60), placement: { anchor: { to: 'container', edge: 'top-left' } } },
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
const long = 'word '.repeat(300);
|
|
278
|
+
const md = `::::page{template="two"}\n:::frame{slot="a"}\n${long}\n:::\n\n:::frame{slot="b"}\n${long}\n:::\n::::`;
|
|
279
|
+
const doc = buildDocument({ markdown: md }, { page: { width: mm(200), height: mm(260), margins: { top: mm(20), bottom: mm(20), left: mm(20), right: mm(20) } }, pageTemplates: [t] }, createMeasurementCache());
|
|
280
|
+
expect(overflowsOf(doc).map((w) => w.frame)).toEqual(['b', 'a']);
|
|
281
|
+
});
|
|
282
|
+
it('what one composed page scales leaves the next one as it was', () => {
|
|
283
|
+
const one = standIn({ frames: { dinero: { height: mm(36), minHeight: undefined } } });
|
|
284
|
+
const page = one.markdown;
|
|
285
|
+
const longer = moreWords(40)(page);
|
|
286
|
+
const twice = (first) => ({ ...one, markdown: `${first}\n\n${page}` });
|
|
287
|
+
const before = build(twice(page));
|
|
288
|
+
const after = build(twice(longer));
|
|
289
|
+
const pages = (d) => d.pages.filter((p) => p.composed);
|
|
290
|
+
expect(pages(before)).toHaveLength(2);
|
|
291
|
+
expect(pages(after)).toHaveLength(2);
|
|
292
|
+
expect(pages(after)[0].composed.frames.find((f) => f.name === 'dinero').scale).toBeLessThan(1);
|
|
293
|
+
const boxes = (p) => p.composed.frames.map((f) => ({ name: f.name, rect: f.rect, scale: f.scale, imageScale: f.imageScale, overflowPx: f.overflowPx }));
|
|
294
|
+
expect(boxes(pages(after)[1])).toEqual(boxes(pages(before)[1]));
|
|
295
|
+
expect(pages(after)[1].composed.log).toEqual(pages(before)[1].composed.log);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
describe('slot warnings carry their location (#858)', () => {
|
|
299
|
+
it('slotUnknown names the flow frame that takes the content; both have page, frame and source range', () => {
|
|
300
|
+
const t = { id: 't', frames: [{ name: 'a', flow: true }, { name: 'b' }, { name: 'c' }] };
|
|
301
|
+
const md = `# Intro\n\n${'Some text. '.repeat(400)}\n\n::::page{template="t"}\nflow\n\n:::frame{slot="nope"}\nStray.\n:::\n\n:::frame{slot="b"}\nB.\n:::\n::::\n`;
|
|
302
|
+
const doc = buildDocument({ markdown: md }, { page: { width: mm(200), height: mm(260), margins: { top: mm(20), bottom: mm(20), left: mm(20), right: mm(20) } }, pageTemplates: [t] }, createMeasurementCache());
|
|
303
|
+
const composed = doc.pages.find((p) => p.composed);
|
|
304
|
+
expect(composed.index).toBeGreaterThan(0);
|
|
305
|
+
const unknown = doc.contentWarnings.find((w) => w.kind === 'slotUnknown');
|
|
306
|
+
expect(unknown).toMatchObject({ kind: 'slotUnknown', slot: 'nope', frame: 'a', template: 't', pageIndex: composed.index });
|
|
307
|
+
expect(md.slice(unknown.sourceStart, unknown.sourceEnd)).toBe('nope');
|
|
308
|
+
const empty = doc.contentWarnings.find((w) => w.kind === 'slotEmpty');
|
|
309
|
+
expect(empty).toMatchObject({ kind: 'slotEmpty', frame: 'c', template: 't', pageIndex: composed.index });
|
|
310
|
+
expect(md.slice(empty.sourceStart, empty.sourceEnd)).toMatch(/^::::page\{template="t"\}/);
|
|
311
|
+
expect(formatWarning(unknown)).toContain('the flow frame "a"');
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
describe('the ladder with spreads, the page memo and the source map (#858)', () => {
|
|
315
|
+
const PAGE = {
|
|
316
|
+
page: { width: mm(200), height: mm(260), margins: { top: mm(20), bottom: mm(20), left: mm(20), right: mm(20) } },
|
|
317
|
+
headings: { balancing: { enabled: false }, levels: [{ level: 1, breakBefore: { enabled: false } }] },
|
|
318
|
+
};
|
|
319
|
+
const W = 200 * MM;
|
|
320
|
+
const buildWith = (md, templates) => buildDocument({ markdown: md }, { ...PAGE, pageTemplates: templates }, createMeasurementCache());
|
|
321
|
+
const words = (n, w = 'word') => Array.from({ length: n }, (_, i) => `${w}${i}`).join(' ');
|
|
322
|
+
it('a spread: the gutter cut narrows a frame, the ladder shrinks its text; a frame of the second page is scaled on its own sheet', () => {
|
|
323
|
+
const t = {
|
|
324
|
+
id: 'sp', spread: true, gutter: mm(20),
|
|
325
|
+
frames: [
|
|
326
|
+
// Centred on the spine: cut at the band's edge, on the first page.
|
|
327
|
+
{ name: 'cut', height: mm(40), placement: { anchor: { to: 'container', edge: 'top-left' } } },
|
|
328
|
+
{ name: 'right', width: mm(60), height: mm(30), placement: { anchor: { to: 'container', edge: 'bottom-right' } } },
|
|
329
|
+
],
|
|
330
|
+
};
|
|
331
|
+
const md = (cut, right) => `::::page{template="sp" spread}\n:::frame{slot="cut"}\n${words(cut, 'cut')}\n:::\n\n:::frame{slot="right"}\n${words(right, 'right')}\n:::\n::::\n`;
|
|
332
|
+
const doc = buildWith(md(400, 94), [t]);
|
|
333
|
+
const [verso, recto] = doc.pages.filter((p) => p.composed?.spread);
|
|
334
|
+
const cut = verso.composed.frames.find((f) => f.name === 'cut' && f.blockIds.length > 0);
|
|
335
|
+
expect(verso.composed.log.find((e) => e.frame === 'cut' && e.cause === 'gutter')).toMatchObject({ kind: 'resized' });
|
|
336
|
+
expect(cut.scale).toBeLessThan(1);
|
|
337
|
+
expect(cut.overflowPx).toBe(0);
|
|
338
|
+
expect(frameBounds(cut).x + frameBounds(cut).width).toBeLessThanOrEqual(W - 10 * MM + 1e-6);
|
|
339
|
+
expect(verso.composed.log).toContainEqual({ frame: 'cut', kind: 'textScaled', scale: cut.scale });
|
|
340
|
+
const right = recto.composed.frames.find((f) => f.name === 'right');
|
|
341
|
+
expect(right.scale).toBeLessThan(1);
|
|
342
|
+
expect(right.overflowPx).toBe(0);
|
|
343
|
+
const b = frameBounds(right);
|
|
344
|
+
expect(b.x).toBeGreaterThan(0);
|
|
345
|
+
expect(b.x + b.width).toBeLessThanOrEqual(W + 1e-6);
|
|
346
|
+
// Its blocks are on its own page, laid out in its larger content box.
|
|
347
|
+
const content = frameContentRect(right);
|
|
348
|
+
for (const id of right.blockIds) {
|
|
349
|
+
const blk = recto.floats.find((x) => x.id === id);
|
|
350
|
+
expect(blk.pageIndex).toBe(recto.index);
|
|
351
|
+
expect(blk.bbox.x).toBeGreaterThanOrEqual(content.x - 0.5);
|
|
352
|
+
expect(blk.bbox.x + blk.bbox.width).toBeLessThanOrEqual(content.x + content.width + 0.5);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
it('the page memo keys the ladder: a reused page with scaled frames and a warning is the page laid out afresh', () => {
|
|
356
|
+
const f = standIn({ frames: { dinero: { height: mm(36), minHeight: undefined }, gasto: { height: mm(30), minHeight: undefined } }, markdown: moreWords(40) });
|
|
357
|
+
const md = `${f.markdown}\n${f.markdown.replace('tema="Retos del planeta"', 'tema="Otro"')}`;
|
|
358
|
+
const shape = (doc) => JSON.stringify({ pages: doc.pages, blocks: doc.blocks, warnings: doc.contentWarnings ?? [] }, (k, v) => (k === 'layoutKey' ? undefined : v));
|
|
359
|
+
const run = (text) => buildDocument({ markdown: text, resources: f.resources }, f.config, createMeasurementCache());
|
|
360
|
+
setComposedPageMemo(false);
|
|
361
|
+
let expected;
|
|
362
|
+
try {
|
|
363
|
+
expected = shape(run(md));
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
setComposedPageMemo(true);
|
|
367
|
+
}
|
|
368
|
+
clearComposedPageMemo();
|
|
369
|
+
// Laid out first where every offset is elsewhere, then met again.
|
|
370
|
+
run(`Una línea antes.\n\n:::pagebreak\n\nOtra línea.\n\n${md}`);
|
|
371
|
+
const before = composedPageMemoStats().hits;
|
|
372
|
+
const doc = run(md);
|
|
373
|
+
expect(composedPageMemoStats().hits).toBeGreaterThan(before);
|
|
374
|
+
expect(shape(doc)).toBe(expected);
|
|
375
|
+
const pages = doc.pages.filter((p) => p.composed);
|
|
376
|
+
expect(pages[0].composed.frames.find((x) => x.name === 'dinero').scale).toBeLessThan(1);
|
|
377
|
+
const w = overflowsOf(doc).find((x) => x.frame === 'gasto');
|
|
378
|
+
expect(w.proposals.find((p) => p.kind === 'shortenText').sourceStart).toBe(w.sourceStart);
|
|
379
|
+
expect(md.slice(w.sourceStart, w.sourceStart + 3)).toBe(md.slice(frameOf(doc, 'gasto').sourceStart, frameOf(doc, 'gasto').sourceStart + 3));
|
|
380
|
+
clearComposedPageMemo();
|
|
381
|
+
});
|
|
382
|
+
it('the source map reads a scaled frame where it is painted', () => {
|
|
383
|
+
const doc = build(standIn({ frames: { dinero: { height: mm(36), minHeight: undefined } }, markdown: moreWords(40) }));
|
|
384
|
+
const page = page0(doc);
|
|
385
|
+
const d = frameOf(doc, 'dinero');
|
|
386
|
+
expect(d.scale).toBeLessThan(1);
|
|
387
|
+
const para = d.blockIds.map((id) => page.floats.find((b) => b.id === id)).find((b) => b.type === 'paragraph' && b.lines.length > 1);
|
|
388
|
+
const line = para.lines[para.lines.length - 1];
|
|
389
|
+
// Where the line is painted: its laid box scaled about the frame's top-left.
|
|
390
|
+
const painted = { x: d.rect.x + (line.bbox.x + 10 - d.rect.x) * d.scale, y: d.rect.y + (line.bbox.y + line.bbox.height / 2 - d.rect.y) * d.scale };
|
|
391
|
+
const hit = sourceAt(doc, page.index, painted.x, painted.y);
|
|
392
|
+
const lineHit = hit.find((e) => e.level === 'line');
|
|
393
|
+
expect(lineHit).toBeDefined();
|
|
394
|
+
expect(lineHit.rect.x).toBeLessThanOrEqual(painted.x);
|
|
395
|
+
expect(lineHit.rect.x + lineHit.rect.width).toBeGreaterThanOrEqual(painted.x);
|
|
396
|
+
expect(lineHit.rect.y).toBeLessThanOrEqual(painted.y);
|
|
397
|
+
expect(lineHit.rect.y + lineHit.rect.height).toBeGreaterThanOrEqual(painted.y);
|
|
398
|
+
expect(lineHit.rect.height).toBeCloseTo(line.bbox.height * d.scale, 6);
|
|
399
|
+
expect(hit.find((e) => e.level === 'frame').id).toBe(d.id);
|
|
400
|
+
// The same line found from its Markdown has the same box.
|
|
401
|
+
const found = elementsForSource(doc, lineHit.source.kind === 'markdown' ? lineHit.source.start : 0, lineHit.source.kind === 'markdown' ? lineHit.source.start : 0).find((m) => m.element.id === lineHit.id);
|
|
402
|
+
expect(found.element.rect).toEqual(lineHit.rect);
|
|
403
|
+
// Under the painted box, nothing of the frame's content: the frame's foot is its rect's.
|
|
404
|
+
const below = sourceAt(doc, page.index, d.rect.x + 20, d.rect.y + d.rect.height + 2);
|
|
405
|
+
expect(below.find((e) => e.id === d.id)).toBeUndefined();
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
//# sourceMappingURL=fit.test.js.map
|