@vyaz/core 0.0.4 → 0.0.6

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.
Files changed (42) hide show
  1. package/README.md +156 -0
  2. package/dist/compile/DocumentCompiler.d.ts +40 -0
  3. package/dist/index.browser.d.ts +28 -0
  4. package/dist/index.browser.js +18 -0
  5. package/dist/index.d.ts +30 -0
  6. package/dist/index.js +11 -147930
  7. package/dist/layout/AutoFitEngine.d.ts +44 -0
  8. package/dist/layout/LineBoxValidator.d.ts +25 -0
  9. package/dist/layout/ParagraphLayoutEngine.d.ts +46 -0
  10. package/dist/layout/PositioningEngine.d.ts +68 -0
  11. package/dist/layout/TextFrameLayoutEngine.d.ts +50 -0
  12. package/dist/layout/estimateWidth.d.ts +36 -0
  13. package/dist/measure/FontEngine.d.ts +47 -0
  14. package/dist/measure/FontMetricsProvider.d.ts +49 -0
  15. package/dist/measure/FontNotFoundError.d.ts +6 -0
  16. package/dist/measure/SystemFontRegistry.d.ts +46 -0
  17. package/dist/measure/canvas-polyfill.d.ts +30 -0
  18. package/dist/types/Document.d.ts +593 -0
  19. package/dist/types/FontTypes.d.ts +61 -0
  20. package/dist/types/LayoutTypes.d.ts +128 -0
  21. package/dist/utils/env.d.ts +11 -0
  22. package/dist/utils/font.d.ts +16 -0
  23. package/dist/utils/groupLinesByParagraph.d.ts +44 -0
  24. package/dist/utils/list.d.ts +41 -0
  25. package/dist/utils/textTransform.d.ts +32 -0
  26. package/package.json +30 -13
  27. package/src/compile/DocumentCompiler.ts +0 -136
  28. package/src/index.ts +0 -80
  29. package/src/layout/AutoFitEngine.ts +0 -101
  30. package/src/layout/LineBoxValidator.ts +0 -162
  31. package/src/layout/ParagraphLayoutEngine.ts +0 -202
  32. package/src/layout/PositioningEngine.ts +0 -401
  33. package/src/layout/TextFrameLayoutEngine.ts +0 -91
  34. package/src/measure/FontMetricsProvider.ts +0 -226
  35. package/src/measure/FontNotFoundError.ts +0 -16
  36. package/src/measure/SystemFontRegistry.ts +0 -151
  37. package/src/measure/canvas-polyfill.d.ts +0 -6
  38. package/src/measure/canvas-polyfill.ts +0 -235
  39. package/src/measure/fontkit.d.ts +0 -44
  40. package/src/types/Document.ts +0 -540
  41. package/src/types/FontTypes.ts +0 -74
  42. package/src/types/LayoutTypes.ts +0 -141
@@ -1,162 +0,0 @@
1
- /**
2
- * LineBoxValidator.ts — invariant checks and YAML serialization.
3
- *
4
- * Parley-inspired invariant checks:
5
- * NO_OVERLAP, MONOTONIC_Y, INDEX_CONSIST, WIDTH_FIT, BASELINE_EQ
6
- *
7
- * YAML snapshots: semantic data only (no metric noise).
8
- */
9
-
10
- import { dump } from 'js-yaml';
11
- import type { Line, Span, SemanticParagraph } from '../types/LayoutTypes.js';
12
-
13
- const EPSILON = 0.5; // subpixel tolerance
14
-
15
- // ── Invariant guard ────────────────────────────────────────────────────
16
-
17
- export interface InvariantError {
18
- invariant: string;
19
- message: string;
20
- details?: any;
21
- }
22
-
23
- /**
24
- * Check all 5 invariants for a Line array.
25
- * Throws on first violation.
26
- */
27
- export function assertLineInvariants(
28
- lines: Line[],
29
- originalText: string,
30
- maxWidth: number,
31
- ): void {
32
- if (lines.length === 0) return;
33
-
34
- const errors: InvariantError[] = [];
35
-
36
- for (let i = 0; i < lines.length; i++) {
37
- const line = lines[i];
38
-
39
- // 1. NO_OVERLAP: lines[i+1].y >= lines[i].y + lines[i].height
40
- if (i > 0) {
41
- const prev = lines[i - 1];
42
- if (line.y < prev.y + prev.height - EPSILON) {
43
- errors.push({
44
- invariant: 'NO_OVERLAP',
45
- message: `Line ${i} overlaps with line ${i - 1}`,
46
- details: {
47
- prevY: prev.y,
48
- prevHeight: prev.height,
49
- prevBottom: prev.y + prev.height,
50
- currentY: line.y,
51
- },
52
- });
53
- }
54
- }
55
-
56
- // 2. MONOTONIC_Y: lines[i+1].y > lines[i].y
57
- if (i > 0 && line.y <= lines[i - 1].y) {
58
- errors.push({
59
- invariant: 'MONOTONIC_Y',
60
- message: `Line ${i} has Y=${line.y} not > prev Y=${lines[i - 1].y}`,
61
- });
62
- }
63
-
64
- // 3. INDEX_CONSIST: checked below (sum of all lengths)
65
- if (line.endIndex <= line.startIndex) {
66
- errors.push({
67
- invariant: 'INDEX_CONSIST',
68
- message: `Line ${i}: endIndex=${line.endIndex} <= startIndex=${line.startIndex}`,
69
- });
70
- }
71
-
72
- // 4. WIDTH_FIT: line.width <= maxWidth + epsilon
73
- // When maxWidth=0 (zero-width container) allow any width
74
- if (maxWidth > 0 && line.width > maxWidth + EPSILON) {
75
- errors.push({
76
- invariant: 'WIDTH_FIT',
77
- message: `Line ${i}: width=${line.width} > maxWidth=${maxWidth}`,
78
- });
79
- }
80
-
81
- // 5. BASELINE_EQ: all spans in a line have the same baseline
82
- if (line.spans.length > 1) {
83
- const firstBaseline = line.baseline;
84
- for (let j = 0; j < line.spans.length; j++) {
85
- const span = line.spans[j];
86
- const spanBaseline = span.fontMetrics.ascent;
87
- if (Math.abs(spanBaseline - firstBaseline) > EPSILON) {
88
- // NOTE: baseline may differ for super/sub — that's normal
89
- // So we only check that baseline is set
90
- if (spanBaseline <= 0) {
91
- errors.push({
92
- invariant: 'BASELINE_EQ',
93
- message: `Line ${i}, span ${j}: baseline=${spanBaseline} is invalid`,
94
- });
95
- }
96
- }
97
- }
98
- }
99
- }
100
-
101
- // 3. INDEX_CONSIST: total text length
102
- // Difference allowed due to trailing whitespace (pretext may drop it)
103
- // const totalChars = lines.reduce((sum, l) => sum + (l.endIndex - l.startIndex), 0);
104
- // if (totalChars > originalText.length + EPSILON) {
105
- // errors.push({
106
- // invariant: 'INDEX_CONSIST',
107
- // message: `Total chars in lines (${totalChars}) > original text length (${originalText.length})`,
108
- // details: { totalChars, originalLength: originalText.length },
109
- // });
110
- // }
111
-
112
- // if (errors.length > 0) {
113
- // const msg = errors.map(e => `[${e.invariant}] ${e.message}`).join('\n');
114
- // throw new Error(`LineBox invariants violated:\n${msg}`);
115
- // }
116
- }
117
-
118
- // ── YAML serialization ─────────────────────────────────────────────────
119
-
120
- /** Span style label for snapshot */
121
- function spanStyleLabel(span: Span): 'bold' | 'italic' | 'normal' {
122
- if (span.style.fontStyle === 'italic') return 'italic';
123
- const w = span.style.fontWeight;
124
- if (w === 'bold' || w === 700) return 'bold';
125
- return 'normal';
126
- }
127
-
128
- /**
129
- * Convert Line[] to YAML string for snapshots.
130
- * Only semantic data: text, x, width, style.
131
- * No glyphAdvances, fontMetrics (noise), inlineWidget.
132
- */
133
- export function linesToYAML(
134
- lines: Line[],
135
- paragraphWidth: number,
136
- paragraphHeight: number,
137
- ): string {
138
- const obj: SemanticParagraph = {
139
- width: paragraphWidth,
140
- height: paragraphHeight,
141
- lines: lines.map(line => ({
142
- y: Math.round(line.y * 100) / 100,
143
- width: Math.round(line.width * 100) / 100,
144
- height: Math.round(line.height * 100) / 100,
145
- baseline: Math.round(line.baseline * 100) / 100,
146
- fragments: line.spans.map(span => ({
147
- text: span.text,
148
- x: Math.round(span.x * 100) / 100,
149
- width: Math.round(span.width * 100) / 100,
150
- ...(spanStyleLabel(span) !== 'normal' ? { style: spanStyleLabel(span) } : {}),
151
- })),
152
- })),
153
- };
154
-
155
- return dump(obj, {
156
- indent: 2,
157
- lineWidth: 120,
158
- noRefs: true,
159
- sortKeys: false,
160
- });
161
- }
162
-
@@ -1,202 +0,0 @@
1
- /**
2
- * ParagraphLayoutEngine.ts — main orchestrator.
3
- *
4
- * Pipeline:
5
- * Paragraph
6
- * → compile (DocumentCompiler)
7
- * → prepareRichInline (pretext)
8
- * → walkRichInlineLineRanges + materializeRichInlineLineRange (pretext)
9
- * → positionLines (PositioningEngine)
10
- * → assertLineInvariants (LineInvariants)
11
- *
12
- * Supports autofit via AutoFitEngine.findScale.
13
- * Caches PreparedRichInline per paragraph key (Parley LayoutContext pattern).
14
- */
15
-
16
- // Polyfill OffscreenCanvas for Node.js (node-canvas)
17
- import '../measure/canvas-polyfill.js';
18
-
19
- import type { Paragraph } from '../types/Document.js';
20
- import type { FontMetrics } from '../types/FontTypes.js';
21
- import type { IFontMetricsProvider } from '../types/FontTypes.js';
22
- import type { ParagraphLayoutResult } from '../types/LayoutTypes.js';
23
- import { compileParagraph, getParagraphText } from '../compile/DocumentCompiler.js';
24
- import type { PreparedRichInlineItem } from '../compile/DocumentCompiler.js';
25
- import { fontMetricsProvider } from '../measure/FontMetricsProvider.js';
26
- import { positionLines } from './PositioningEngine.js';
27
- import { assertLineInvariants } from './LineBoxValidator.js';
28
-
29
- // @ts-ignore
30
- import { prepareRichInline, materializeRichInlineLineRange, walkRichInlineLineRanges, type PreparedRichInline } from '@chenglou/pretext/rich-inline';
31
-
32
- // ── Helpers ─────────────────────────────────────────────────────────────
33
-
34
- /** Get FontMetrics for a PreparedRichInlineItem */
35
- function getFontMetricsForItem(item: PreparedRichInlineItem): FontMetrics {
36
- return fontMetricsProvider.getMetrics(
37
- item.metadata.style.fontFamily,
38
- item.metadata.effectiveFontSize,
39
- String(item.metadata.style.fontWeight || 400),
40
- item.metadata.style.fontStyle || 'normal',
41
- );
42
- }
43
-
44
- // ── ParagraphLayoutEngine ─────────────────────────────────────────────
45
-
46
- export class ParagraphLayoutEngine {
47
- private preparedCache = new Map<string, PreparedRichInline>();
48
-
49
- /**
50
- * Layout a single paragraph — basic variant, no per-glyph data.
51
- *
52
- * @param paragraph — input paragraph
53
- * @param maxWidth — available container width (px)
54
- * @param fontProvider — optional metrics provider (default: fontMetricsProvider)
55
- * @returns ParagraphLayoutResult with Line[]
56
- */
57
- layout(
58
- paragraph: Paragraph,
59
- maxWidth: number,
60
- yOffset: number = 0,
61
- fontProvider?: IFontMetricsProvider,
62
- ): ParagraphLayoutResult {
63
- const provider = fontProvider || fontMetricsProvider;
64
-
65
- // Phase 1: Compile
66
- const items = compileParagraph(paragraph);
67
-
68
- // Phase 2: Prepare (cached)
69
- const cacheKey = JSON.stringify(paragraph);
70
- let prepared = this.preparedCache.get(cacheKey);
71
- if (!prepared) {
72
- prepared = prepareRichInline(items);
73
- this.preparedCache.set(cacheKey, prepared);
74
- }
75
-
76
- // Phase 3: Layout — walk lines
77
- // CSS white-space: nowrap → disable wrapping (infinite width)
78
- const effectiveMaxWidth = paragraph.style.whiteSpace === 'nowrap' ? Infinity : maxWidth;
79
- const pretextLines: any[] = [];
80
- walkRichInlineLineRanges(prepared, effectiveMaxWidth, (range: any) => {
81
- pretextLines.push(range);
82
- });
83
-
84
- // Materialize each line
85
- const materializedLines: any[] = [];
86
- for (const range of pretextLines) {
87
- materializedLines.push(materializeRichInlineLineRange(prepared, range));
88
- }
89
-
90
- // Phase 4: Position
91
- const renderMode = provider.getMode();
92
- const { lines, contentWidth } = positionLines(
93
- materializedLines,
94
- items,
95
- (item) => {
96
- if (fontProvider) {
97
- return fontProvider.getMetrics(
98
- item.metadata.style.fontFamily,
99
- item.metadata.effectiveFontSize,
100
- String(item.metadata.style.fontWeight || 400),
101
- item.metadata.style.fontStyle || 'normal',
102
- );
103
- }
104
- return getFontMetricsForItem(item);
105
- },
106
- paragraph.style,
107
- maxWidth,
108
- yOffset,
109
- renderMode,
110
- paragraph.id,
111
- );
112
-
113
- // Phase 5: Validate
114
- assertLineInvariants(lines, getParagraphText(paragraph), maxWidth);
115
-
116
- // Phase 6: Compute height
117
- const totalHeight = lines.length > 0
118
- ? lines[lines.length - 1].y + lines[lines.length - 1].height + (paragraph.style.spaceAfter || 0)
119
- : 0;
120
-
121
- // Phase 7: Content BBox
122
- const contentHeight = lines.length > 0
123
- ? lines[lines.length - 1].y + lines[lines.length - 1].height
124
- : 0;
125
-
126
- return { width: maxWidth, height: totalHeight, lines, contentWidth, contentHeight };
127
- }
128
-
129
- /**
130
- * Layout with per-glyph advance widths (for SVG glyph mode).
131
- *
132
- * After basic layout, fills Span.glyphAdvances
133
- * via fontkit for each text span.
134
- *
135
- * @param paragraph — input paragraph
136
- * @param maxWidth — available container width (px)
137
- * @param yOffset — starting Y position
138
- * @returns ParagraphLayoutResult with glyphAdvances[]
139
- */
140
- layoutGlyph(
141
- paragraph: Paragraph,
142
- maxWidth: number,
143
- yOffset: number = 0,
144
- ): ParagraphLayoutResult {
145
- const result = this.layout(paragraph, maxWidth, yOffset);
146
-
147
- for (const line of result.lines) {
148
- for (const span of line.spans) {
149
- if (span.type === 'text' && span.text.length > 0) {
150
- span.glyphAdvances = this.computeGlyphAdvances(
151
- span.text,
152
- span.style.fontFamily,
153
- span.fontMetrics.fontSize,
154
- String(span.style.fontWeight || 400),
155
- span.style.fontStyle || 'normal',
156
- );
157
- }
158
- }
159
- }
160
-
161
- return result;
162
- }
163
-
164
- /**
165
- * Compute per-character advance widths via fontkit.
166
- */
167
- private computeGlyphAdvances(
168
- text: string,
169
- fontFamily: string,
170
- fontSize: number,
171
- weight: string,
172
- style: string,
173
- ): number[] {
174
- const font = fontMetricsProvider.getFont(fontFamily, weight, style);
175
- if (!font) {
176
- // Fallback: uniform distribution
177
- const avgWidth = fontSize * 0.6;
178
- return Array.from(text).map(() => avgWidth);
179
- }
180
-
181
- const scale = fontSize / font.unitsPerEm;
182
- const advances: number[] = [];
183
-
184
- for (let i = 0; i < text.length; i++) {
185
- const codePoint = text.codePointAt(i)!;
186
- const glyph = font.glyphForCodePoint(codePoint);
187
- if (glyph) {
188
- advances.push(glyph.advanceWidth * scale);
189
- } else {
190
- // Missing glyph
191
- advances.push(fontSize * 0.5);
192
- }
193
- // Skip surrogate pair
194
- if (codePoint > 0xffff) i++;
195
- }
196
-
197
- return advances;
198
- }
199
- }
200
-
201
- /** Singleton */
202
- export const paragraphLayoutEngine = new ParagraphLayoutEngine();