render-tag 0.1.30 → 0.1.32

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/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { RenderConfig, RenderResult, LayoutConfig, LayoutResult, DrawConfig, LayoutLine, AnyCanvas } from './types.js';
2
2
  export type { RenderConfig, RenderResult, LayoutConfig, LayoutResult, DrawConfig, LayoutLine };
3
3
  export { setDOMParser, type DOMParserLike } from './dom.js';
4
- export { FLOORS_LINE_BASELINE } from './layout.js';
4
+ export { lineBaselineOffset } from './layout.js';
5
5
  /**
6
6
  * Compute layout for an HTML string without rendering.
7
7
  * Returns a reusable LayoutResult that can be drawn onto multiple targets via drawLayout().
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EAAE,UAAU,EACtC,UAAU,EAAE,SAAS,EACtB,MAAM,YAAY,CAAC;AAMpB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC/F,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAWnD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAgCzD;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAsCpE;AAID;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CA8BzD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EAAE,UAAU,EACtC,UAAU,EAAE,SAAS,EACtB,MAAM,YAAY,CAAC;AAMpB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC/F,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWjD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAgCzD;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAsCpE;AAID;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CA8BzD"}
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import { resolveStylesFromCSS } from './css-resolver.js';
3
3
  import { buildLayoutTree } from './layout.js';
4
4
  import { renderNode } from './render.js';
5
5
  export { setDOMParser } from './dom.js';
6
- export { FLOORS_LINE_BASELINE } from './layout.js';
6
+ export { lineBaselineOffset } from './layout.js';
7
7
  import { createFallbackMeasureCtx } from './dom.js';
8
8
  // Default measurement context, created lazily and reused across layout()
9
9
  // calls — safe because font/letterSpacing state is set before every
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/layout.d.ts CHANGED
@@ -11,12 +11,25 @@ export declare function buildCanvasFont(style: ResolvedStyle): string;
11
11
  * exact half-leading out — measured over the whole 530-case corpus, giving
12
12
  * Safari the Blink branch cost 214 wins against 223 losses (avg 7.50% ->
13
13
  * 9.21%) where the exact value wins 48 against 3 (7.50% -> 6.52%).
14
- *
15
- * Public API: the parity suites expect per engine, and every renderer that has
16
- * to place a baseline beside a render-tag canvas (@polotno/svg-export) must
17
- * round the same way this does.
18
14
  */
19
15
  export declare const FLOORS_LINE_BASELINE: boolean;
16
+ /**
17
+ * `super` and `sub` are engine constants, not CSS. Blink and WebKit share
18
+ * theirs (`fontSize/3 + 1`, `fontSize/5 + 1`); Gecko raises by 0.34em and
19
+ * lowers by 0.20em. A SEPARATE question from the rounding above — Safari
20
+ * rounds like nobody and shifts like Blink — so never gate one on the other.
21
+ */
22
+ export declare const BLINK_SUPER_SUB: boolean;
23
+ /**
24
+ * Baseline offset from the top of a line box, the way the engine places it:
25
+ * the half-leading `(lineHeight - (ascent + descent)) / 2` below the line top,
26
+ * plus the ascent, rounded as `FLOORS_LINE_BASELINE` says.
27
+ *
28
+ * Public API, because this is the ONE rule every renderer that places a
29
+ * baseline beside a render-tag canvas has to share (@polotno/svg-export, the
30
+ * editor's list marker). Call it rather than restate it, or the two drift.
31
+ */
32
+ export declare function lineBaselineOffset(lineHeight: number, ascent: number, descent: number): number;
20
33
  export declare function isTransparent(color: string): boolean;
21
34
  export declare function getFontMetrics(ctx: CanvasRenderingContext2D, style: ResolvedStyle): {
22
35
  ascent: number;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,SAAS,EAAc,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4C5H;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAGnF;AAgBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAc5D;AA4GD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,SAAW,CAAC;AAmF7C,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD,wBAAgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAWvH;AA0DD,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAiBlF;AA4CD;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAIzD;AA4tED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,kBAAkB,UAAO,EACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,YAAY,EAAE,UAAU,KAAK,IAAI,GACvD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAmD1D"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,SAAS,EAAc,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4C5H;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAGnF;AAgBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAc5D;AAkHD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,SAA0B,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAY,CAAC;AAEzC;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAG9F;AAuDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD,wBAAgB,cAAc,CAAC,GAAG,EAAE,wBAAwB,EAAE,KAAK,EAAE,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAkBvH;AA0DD,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAiBlF;AA4CD;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAIzD;AAytED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,kBAAkB,UAAO,EACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,YAAY,EAAE,UAAU,KAAK,IAAI,GACvD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAmD1D"}
package/lib/layout.js CHANGED
@@ -158,17 +158,22 @@ function getLineHeight(ctx, style, useBulletProbe = false) {
158
158
  }
159
159
  /**
160
160
  * Which engine's line rules to follow. Only the UA string can say, because
161
- * `accuracy: 'performance'` promises not to touch the DOM: Gecko is the one
162
- * engine that still sends a real `Gecko/<date>` product token (Blink and
163
- * WebKit carry only the "like Gecko" comment, which has no slash), and Blink
164
- * is the one that says `Chrome/` matched with NO word boundary, because
165
- * headless Chrome says `HeadlessChrome/`. Safari sends neither token, which is
166
- * how it is told apart. With no navigator (Node, workers) we take the Blink
167
- * branch, for the same target.
161
+ * `accuracy: 'performance'` promises not to touch the DOM.
162
+ *
163
+ * Blink is the DEFAULT, and the other two are what we detect: a server-side
164
+ * render (no navigator, or jsdom) targets headless Chrome, so anything we
165
+ * cannot positively identify has to round the way Chrome does.
166
+ *
167
+ * - Gecko is the one engine that still sends a real `Gecko/<date>` product
168
+ * token; Blink and WebKit carry only the "like Gecko" comment, no slash.
169
+ * - Safari is WebKit that says neither `Chrome/` nor `jsdom/`. jsdom borrows
170
+ * WebKit's UA and would otherwise be mistaken for it.
171
+ * - `Chrome/` is matched with NO word boundary, because headless Chrome sends
172
+ * `HeadlessChrome/`.
168
173
  */
169
174
  const UA = typeof navigator === 'undefined' ? '' : navigator.userAgent;
170
- const IS_BLINK = UA === '' || /Chrome\/\d/.test(UA);
171
175
  const IS_GECKO = /\bGecko\/\d/.test(UA);
176
+ const IS_SAFARI = /AppleWebKit/.test(UA) && !/Chrome\/\d/.test(UA) && !/\bjsdom\//.test(UA);
172
177
  /**
173
178
  * True where the engine floors a line's baseline onto a whole CSS pixel.
174
179
  *
@@ -176,35 +181,25 @@ const IS_GECKO = /\bGecko\/\d/.test(UA);
176
181
  * exact half-leading out — measured over the whole 530-case corpus, giving
177
182
  * Safari the Blink branch cost 214 wins against 223 losses (avg 7.50% ->
178
183
  * 9.21%) where the exact value wins 48 against 3 (7.50% -> 6.52%).
179
- *
180
- * Public API: the parity suites expect per engine, and every renderer that has
181
- * to place a baseline beside a render-tag canvas (@polotno/svg-export) must
182
- * round the same way this does.
183
184
  */
184
- export const FLOORS_LINE_BASELINE = IS_BLINK;
185
+ export const FLOORS_LINE_BASELINE = !IS_GECKO && !IS_SAFARI;
185
186
  /**
186
187
  * `super` and `sub` are engine constants, not CSS. Blink and WebKit share
187
188
  * theirs (`fontSize/3 + 1`, `fontSize/5 + 1`); Gecko raises by 0.34em and
188
- * lowers by 0.20em. This is a separate question from the baseline rounding
189
- * above — Safari rounds like nobody and shifts like Blink.
189
+ * lowers by 0.20em. A SEPARATE question from the rounding above — Safari
190
+ * rounds like nobody and shifts like Blink — so never gate one on the other.
190
191
  */
191
- const BLINK_SUPER_SUB = !IS_GECKO;
192
+ export const BLINK_SUPER_SUB = !IS_GECKO;
192
193
  /**
193
- * Baseline offset from the top of a line box, the way the engine places it.
194
- *
195
- * The CSS half-leading is `(lineHeight - (ascent + descent)) / 2`, and the
196
- * baseline sits that far below the line top, plus the ascent. Blink FLOORS that
197
- * sum to a whole CSS pixel (`FontHeight::AddLeading`), so its DOM text stands up
198
- * to 1px HIGHER than the exact value. Gecko lays the exact value out in app
199
- * units. WebKit floors on 80 of 90 measured size × line-height combinations and
200
- * has no exact rule we can state, so it takes the Blink branch — the one that
201
- * fits it best, not one it matches everywhere.
194
+ * Baseline offset from the top of a line box, the way the engine places it:
195
+ * the half-leading `(lineHeight - (ascent + descent)) / 2` below the line top,
196
+ * plus the ascent, rounded as `FLOORS_LINE_BASELINE` says.
202
197
  *
203
- * So the rounding is the engine's, not a style choice: each browser's canvas
204
- * lands on the baseline that browser's own DOM would use, which is what keeps a
205
- * canvas render and a contenteditable overlay of the same text on one line.
198
+ * Public API, because this is the ONE rule every renderer that places a
199
+ * baseline beside a render-tag canvas has to share (@polotno/svg-export, the
200
+ * editor's list marker). Call it rather than restate it, or the two drift.
206
201
  */
207
- function lineBaselineOffset(lineHeight, ascent, descent) {
202
+ export function lineBaselineOffset(lineHeight, ascent, descent) {
208
203
  const exact = (lineHeight - (ascent + descent)) / 2 + ascent;
209
204
  return FLOORS_LINE_BASELINE ? Math.floor(exact) : exact;
210
205
  }
@@ -263,8 +258,15 @@ export function getFontMetrics(ctx, style) {
263
258
  const cached = _fontMetricsCache.get(font);
264
259
  if (cached)
265
260
  return cached;
261
+ // Restore what the caller had set: this measures with its OWN font, and a
262
+ // measurement must not move the ctx. Leaving it moved made the function
263
+ // behave differently on a cache miss than on a hit, so a caller that set a
264
+ // font and then measured through this was correct only while the cache was
265
+ // warm — `addListMarker` measured its marker on the li's face on a cold one.
266
+ const prev = ctx.font;
266
267
  ctx.font = font;
267
268
  const m = ctx.measureText('M');
269
+ ctx.font = prev;
268
270
  const ascent = m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent;
269
271
  const descent = m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent;
270
272
  const result = { ascent, descent };
@@ -1644,6 +1646,14 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
1644
1646
  const strutBox = leadedBox(ctx, node.style, useBulletProbe);
1645
1647
  let lineAscent = strutBox.ascent;
1646
1648
  let lineDescent = strutBox.descent;
1649
+ // A shift moves the box, not the line's baseline: positive is downward, so
1650
+ // it lifts the box's demand on the ascent side and adds to the descent one.
1651
+ const grow = (b, shift) => {
1652
+ if (b.ascent - shift > lineAscent)
1653
+ lineAscent = b.ascent - shift;
1654
+ if (b.descent + shift > lineDescent)
1655
+ lineDescent = b.descent + shift;
1656
+ };
1647
1657
  for (const word of line.words) {
1648
1658
  if (word.text === '')
1649
1659
  continue;
@@ -1656,14 +1666,7 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
1656
1666
  // the DOM has 40). With the shift it is idempotent — a wrapper with
1657
1667
  // direct text contributes the identical box through its own run.
1658
1668
  if (word.parentStyle) {
1659
- const parentBox = leadedBox(ctx, word.parentStyle, useBulletProbe);
1660
- const parentShift = verticalAlignShift(word.parentStyle.verticalAlign, ctx, word.parentStyle, blockStyle, useBulletProbe);
1661
- if (parentBox.ascent - parentShift > lineAscent) {
1662
- lineAscent = parentBox.ascent - parentShift;
1663
- }
1664
- if (parentBox.descent + parentShift > lineDescent) {
1665
- lineDescent = parentBox.descent + parentShift;
1666
- }
1669
+ grow(leadedBox(ctx, word.parentStyle, useBulletProbe), verticalAlignShift(word.parentStyle.verticalAlign, ctx, word.parentStyle, blockStyle, useBulletProbe));
1667
1670
  }
1668
1671
  const box = leadedBox(ctx, word.style, useBulletProbe);
1669
1672
  // An inline-block joins the line as an ATOMIC box: its own content
@@ -1677,15 +1680,7 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
1677
1680
  box.ascent += extra.top;
1678
1681
  box.descent += extra.bottom;
1679
1682
  }
1680
- // A shift moves the box, not the line's baseline: positive is downward,
1681
- // so it lifts the box's demand on the ascent side and adds to the descent
1682
- // side. The parent-size fallback is the one the emit pass uses, so a line
1683
- // whose only content is shifted still sizes around it.
1684
- const shift = atomic ? 0 : verticalAlignShift(word.style.verticalAlign, ctx, word.style, word.parentStyle ?? blockStyle, useBulletProbe);
1685
- if (box.ascent - shift > lineAscent)
1686
- lineAscent = box.ascent - shift;
1687
- if (box.descent + shift > lineDescent)
1688
- lineDescent = box.descent + shift;
1683
+ grow(box, atomic ? 0 : verticalAlignShift(word.style.verticalAlign, ctx, word.style, word.parentStyle ?? blockStyle, useBulletProbe));
1689
1684
  }
1690
1685
  const lineBoxHeight = lineAscent + lineDescent;
1691
1686
  const lineBaselineY = curY + lineAscent;
@@ -1932,9 +1927,10 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
1932
1927
  text: line.words.map(w => w.text).join(''),
1933
1928
  bounds: {
1934
1929
  x: lineLeftX,
1935
- // The line box starts at curY: shifted content grew the box through
1936
- // `lineAscent`/`lineDescent` above, so nothing on the line reaches
1937
- // outside it.
1930
+ // The line box starts at curY this is the CSS line box, which
1931
+ // `lineAscent`/`lineDescent` grew to cover every box on the line. Ink
1932
+ // can still overflow it (an ascender under `line-height: 1`), exactly
1933
+ // as it does in the DOM; a caller that clips must allow for that.
1938
1934
  y: curY,
1939
1935
  width: lineWidth,
1940
1936
  height: lineBoxHeight,