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 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;AAmHD;;;;;;;;;;;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;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 = IS_BLINK;
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. This is a separate question from the baseline rounding
195
- * 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.
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
- * The CSS half-leading is `(lineHeight - (ascent + descent)) / 2`, and the
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
- * So the rounding is the engine's, not a style choice: each browser's canvas
210
- * lands on the baseline that browser's own DOM would use, which is what keeps a
211
- * 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.
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
- const parentBox = leadedBox(ctx, word.parentStyle, useBulletProbe);
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
- // A shift moves the box, not the line's baseline: positive is downward,
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: shifted content grew the box through
1942
- // `lineAscent`/`lineDescent` above, so nothing on the line reaches
1943
- // 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.
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.tagName === 'li' || node.tagName === 'ul' || node.tagName === 'ol' ||
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
- if (prevMarginBottom > 0) {
2147
- curY += prevMarginBottom;
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.style.marginTop;
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 && allowCollapseThrough;
2189
- if (canCollapseThrough && prevMarginBottom > 0) {
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 = Math.max(style.marginBottom, prevMarginBottom);
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 && prevMarginBottom > 0) {
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);