render-tag 0.1.31 → 0.1.33
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 +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/layout.d.ts +17 -4
- package/lib/layout.d.ts.map +1 -1
- package/lib/layout.js +59 -55
- package/lib/layout.js.map +1 -1
- package/lib/render-tag.umd.js +7 -7
- package/lib/render-tag.umd.js.map +1 -1
- package/package.json +1 -1
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 {
|
|
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().
|
package/lib/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 {
|
|
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":"AAKA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,YAAY,EAAsB,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,
|
|
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;
|
package/lib/layout.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAwuED;;;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
|
@@ -174,7 +174,6 @@ function getLineHeight(ctx, style, useBulletProbe = false) {
|
|
|
174
174
|
const UA = typeof navigator === 'undefined' ? '' : navigator.userAgent;
|
|
175
175
|
const IS_GECKO = /\bGecko\/\d/.test(UA);
|
|
176
176
|
const IS_SAFARI = /AppleWebKit/.test(UA) && !/Chrome\/\d/.test(UA) && !/\bjsdom\//.test(UA);
|
|
177
|
-
const IS_BLINK = !IS_GECKO && !IS_SAFARI;
|
|
178
177
|
/**
|
|
179
178
|
* True where the engine floors a line's baseline onto a whole CSS pixel.
|
|
180
179
|
*
|
|
@@ -182,35 +181,25 @@ const IS_BLINK = !IS_GECKO && !IS_SAFARI;
|
|
|
182
181
|
* exact half-leading out — measured over the whole 530-case corpus, giving
|
|
183
182
|
* Safari the Blink branch cost 214 wins against 223 losses (avg 7.50% ->
|
|
184
183
|
* 9.21%) where the exact value wins 48 against 3 (7.50% -> 6.52%).
|
|
185
|
-
*
|
|
186
|
-
* Public API: the parity suites expect per engine, and every renderer that has
|
|
187
|
-
* to place a baseline beside a render-tag canvas (@polotno/svg-export) must
|
|
188
|
-
* round the same way this does.
|
|
189
184
|
*/
|
|
190
|
-
export const FLOORS_LINE_BASELINE =
|
|
185
|
+
export const FLOORS_LINE_BASELINE = !IS_GECKO && !IS_SAFARI;
|
|
191
186
|
/**
|
|
192
187
|
* `super` and `sub` are engine constants, not CSS. Blink and WebKit share
|
|
193
188
|
* theirs (`fontSize/3 + 1`, `fontSize/5 + 1`); Gecko raises by 0.34em and
|
|
194
|
-
* lowers by 0.20em.
|
|
195
|
-
*
|
|
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.
|
|
196
191
|
*/
|
|
197
|
-
const BLINK_SUPER_SUB = !IS_GECKO;
|
|
192
|
+
export const BLINK_SUPER_SUB = !IS_GECKO;
|
|
198
193
|
/**
|
|
199
|
-
* Baseline offset from the top of a line box, the way the engine places it
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* baseline sits that far below the line top, plus the ascent. Blink FLOORS that
|
|
203
|
-
* sum to a whole CSS pixel (`FontHeight::AddLeading`), so its DOM text stands up
|
|
204
|
-
* to 1px HIGHER than the exact value. Gecko lays the exact value out in app
|
|
205
|
-
* units. WebKit floors on 80 of 90 measured size × line-height combinations and
|
|
206
|
-
* has no exact rule we can state, so it takes the Blink branch — the one that
|
|
207
|
-
* 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.
|
|
208
197
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
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.
|
|
212
201
|
*/
|
|
213
|
-
function lineBaselineOffset(lineHeight, ascent, descent) {
|
|
202
|
+
export function lineBaselineOffset(lineHeight, ascent, descent) {
|
|
214
203
|
const exact = (lineHeight - (ascent + descent)) / 2 + ascent;
|
|
215
204
|
return FLOORS_LINE_BASELINE ? Math.floor(exact) : exact;
|
|
216
205
|
}
|
|
@@ -269,8 +258,15 @@ export function getFontMetrics(ctx, style) {
|
|
|
269
258
|
const cached = _fontMetricsCache.get(font);
|
|
270
259
|
if (cached)
|
|
271
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;
|
|
272
267
|
ctx.font = font;
|
|
273
268
|
const m = ctx.measureText('M');
|
|
269
|
+
ctx.font = prev;
|
|
274
270
|
const ascent = m.fontBoundingBoxAscent ?? m.actualBoundingBoxAscent;
|
|
275
271
|
const descent = m.fontBoundingBoxDescent ?? m.actualBoundingBoxDescent;
|
|
276
272
|
const result = { ascent, descent };
|
|
@@ -1650,6 +1646,14 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
|
|
|
1650
1646
|
const strutBox = leadedBox(ctx, node.style, useBulletProbe);
|
|
1651
1647
|
let lineAscent = strutBox.ascent;
|
|
1652
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
|
+
};
|
|
1653
1657
|
for (const word of line.words) {
|
|
1654
1658
|
if (word.text === '')
|
|
1655
1659
|
continue;
|
|
@@ -1662,14 +1666,7 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
|
|
|
1662
1666
|
// the DOM has 40). With the shift it is idempotent — a wrapper with
|
|
1663
1667
|
// direct text contributes the identical box through its own run.
|
|
1664
1668
|
if (word.parentStyle) {
|
|
1665
|
-
|
|
1666
|
-
const parentShift = verticalAlignShift(word.parentStyle.verticalAlign, ctx, word.parentStyle, blockStyle, useBulletProbe);
|
|
1667
|
-
if (parentBox.ascent - parentShift > lineAscent) {
|
|
1668
|
-
lineAscent = parentBox.ascent - parentShift;
|
|
1669
|
-
}
|
|
1670
|
-
if (parentBox.descent + parentShift > lineDescent) {
|
|
1671
|
-
lineDescent = parentBox.descent + parentShift;
|
|
1672
|
-
}
|
|
1669
|
+
grow(leadedBox(ctx, word.parentStyle, useBulletProbe), verticalAlignShift(word.parentStyle.verticalAlign, ctx, word.parentStyle, blockStyle, useBulletProbe));
|
|
1673
1670
|
}
|
|
1674
1671
|
const box = leadedBox(ctx, word.style, useBulletProbe);
|
|
1675
1672
|
// An inline-block joins the line as an ATOMIC box: its own content
|
|
@@ -1683,15 +1680,7 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
|
|
|
1683
1680
|
box.ascent += extra.top;
|
|
1684
1681
|
box.descent += extra.bottom;
|
|
1685
1682
|
}
|
|
1686
|
-
|
|
1687
|
-
// so it lifts the box's demand on the ascent side and adds to the descent
|
|
1688
|
-
// side. The parent-size fallback is the one the emit pass uses, so a line
|
|
1689
|
-
// whose only content is shifted still sizes around it.
|
|
1690
|
-
const shift = atomic ? 0 : verticalAlignShift(word.style.verticalAlign, ctx, word.style, word.parentStyle ?? blockStyle, useBulletProbe);
|
|
1691
|
-
if (box.ascent - shift > lineAscent)
|
|
1692
|
-
lineAscent = box.ascent - shift;
|
|
1693
|
-
if (box.descent + shift > lineDescent)
|
|
1694
|
-
lineDescent = box.descent + shift;
|
|
1683
|
+
grow(box, atomic ? 0 : verticalAlignShift(word.style.verticalAlign, ctx, word.style, word.parentStyle ?? blockStyle, useBulletProbe));
|
|
1695
1684
|
}
|
|
1696
1685
|
const lineBoxHeight = lineAscent + lineDescent;
|
|
1697
1686
|
const lineBaselineY = curY + lineAscent;
|
|
@@ -1938,9 +1927,10 @@ function layoutInlineContent(ctx, node, x, y, contentWidth, useBulletProbe = fal
|
|
|
1938
1927
|
text: line.words.map(w => w.text).join(''),
|
|
1939
1928
|
bounds: {
|
|
1940
1929
|
x: lineLeftX,
|
|
1941
|
-
// The line box starts at curY
|
|
1942
|
-
// `lineAscent`/`lineDescent`
|
|
1943
|
-
//
|
|
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.
|
|
1944
1934
|
y: curY,
|
|
1945
1935
|
width: lineWidth,
|
|
1946
1936
|
height: lineBoxHeight,
|
|
@@ -2038,6 +2028,23 @@ function isBlock(node) {
|
|
|
2038
2028
|
d === 'table-row' || d === 'table-cell' || d === 'table-row-group' ||
|
|
2039
2029
|
d === 'table-header-group' || d === 'table-footer-group';
|
|
2040
2030
|
}
|
|
2031
|
+
function allowsMarginCollapseThrough(node) {
|
|
2032
|
+
const display = node.style.display;
|
|
2033
|
+
return (display === 'block' || display === 'list-item') &&
|
|
2034
|
+
(node.tagName === 'li' || node.tagName === 'ul' || node.tagName === 'ol' ||
|
|
2035
|
+
node.tagName === 'dd' || node.tagName === 'dt');
|
|
2036
|
+
}
|
|
2037
|
+
function collapsibleMarginTop(node) {
|
|
2038
|
+
const marginTop = node.style.marginTop;
|
|
2039
|
+
if (!allowsMarginCollapseThrough(node) || node.style.paddingTop !== 0 ||
|
|
2040
|
+
node.style.borderTopWidth !== 0) {
|
|
2041
|
+
return marginTop;
|
|
2042
|
+
}
|
|
2043
|
+
const firstChild = node.children[0];
|
|
2044
|
+
return firstChild && isBlock(firstChild)
|
|
2045
|
+
? collapseMargins(marginTop, collapsibleMarginTop(firstChild))
|
|
2046
|
+
: marginTop;
|
|
2047
|
+
}
|
|
2041
2048
|
/**
|
|
2042
2049
|
* Layout a block-level element and all its children.
|
|
2043
2050
|
* Returns the LayoutBox and total height consumed (including margins).
|
|
@@ -2118,8 +2125,7 @@ function layoutBlock(ctx, node, x, y, availableWidth, clamp) {
|
|
|
2118
2125
|
let prevMarginBottom = 0;
|
|
2119
2126
|
let hasContent = false; // tracks whether we've placed any content
|
|
2120
2127
|
// Margin collapsing through parent: only for list elements.
|
|
2121
|
-
const allowCollapseThrough = node
|
|
2122
|
-
node.tagName === 'dd' || node.tagName === 'dt';
|
|
2128
|
+
const allowCollapseThrough = allowsMarginCollapseThrough(node);
|
|
2123
2129
|
for (let ci = 0; ci < node.children.length; ci++) {
|
|
2124
2130
|
const child = node.children[ci];
|
|
2125
2131
|
// Line-clamp budget exhausted — everything below the cut is dropped,
|
|
@@ -2143,10 +2149,8 @@ function layoutBlock(ctx, node, x, y, availableWidth, clamp) {
|
|
|
2143
2149
|
}
|
|
2144
2150
|
}
|
|
2145
2151
|
// Apply pending margin before inline content
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
prevMarginBottom = 0;
|
|
2149
|
-
}
|
|
2152
|
+
curY += prevMarginBottom;
|
|
2153
|
+
prevMarginBottom = 0;
|
|
2150
2154
|
const inlineGroup = {
|
|
2151
2155
|
element: null,
|
|
2152
2156
|
tagName: 'div',
|
|
@@ -2163,9 +2167,7 @@ function layoutBlock(ctx, node, x, y, availableWidth, clamp) {
|
|
|
2163
2167
|
continue;
|
|
2164
2168
|
}
|
|
2165
2169
|
// Block child — collapse margins
|
|
2166
|
-
const childMarginTop = child
|
|
2167
|
-
// First child margin-top collapses through parent if parent has no top border/padding
|
|
2168
|
-
// Only for elements that don't establish a new BFC (not root, not flex, not overflow)
|
|
2170
|
+
const childMarginTop = collapsibleMarginTop(child);
|
|
2169
2171
|
// First child margin-top collapses through parent if parent has no
|
|
2170
2172
|
// top padding/border and doesn't establish a new BFC.
|
|
2171
2173
|
if (!hasContent && padTop === 0 && borderTop === 0 && allowCollapseThrough) {
|
|
@@ -2185,16 +2187,18 @@ function layoutBlock(ctx, node, x, y, availableWidth, clamp) {
|
|
|
2185
2187
|
// Last child's margin-bottom collapses through parent if no bottom border/padding.
|
|
2186
2188
|
// Root container does NOT collapse last-child margin (it defines the content height).
|
|
2187
2189
|
let marginBottomOut = style.marginBottom;
|
|
2188
|
-
const canCollapseThrough = padBottom === 0 && borderBottom === 0 &&
|
|
2189
|
-
|
|
2190
|
+
const canCollapseThrough = padBottom === 0 && borderBottom === 0 &&
|
|
2191
|
+
style.minHeight === 0 && allowCollapseThrough;
|
|
2192
|
+
if (canCollapseThrough) {
|
|
2190
2193
|
// Last child's margin passes through to become parent's effective margin-bottom
|
|
2191
|
-
marginBottomOut =
|
|
2194
|
+
marginBottomOut = collapseMargins(style.marginBottom, prevMarginBottom);
|
|
2192
2195
|
}
|
|
2193
2196
|
// Include last child's margin-bottom in parent height when it can't collapse through
|
|
2194
2197
|
let contentEnd = curY - contentStartY;
|
|
2195
|
-
if (!canCollapseThrough
|
|
2198
|
+
if (!canCollapseThrough) {
|
|
2196
2199
|
contentEnd += prevMarginBottom;
|
|
2197
2200
|
}
|
|
2201
|
+
contentEnd = Math.max(0, contentEnd);
|
|
2198
2202
|
box.height = borderTop + padTop + contentEnd + padBottom + borderBottom;
|
|
2199
2203
|
if (style.minHeight > 0)
|
|
2200
2204
|
box.height = Math.max(box.height, style.minHeight);
|