Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f7aa93c
Resolve abspos baseline fallback against the containing grid
skyturkish Sep 25, 2026
60131af
Saturate position offsets instead of truncating them to int16
skyturkish Sep 25, 2026
d388dc8
Use the baseline fallback when flex baselines run along the cross axis
skyturkish Sep 25, 2026
bf48567
Apply align-self to abspos static positions inside a line box
skyturkish Sep 25, 2026
84a472c
Skip node-dependent background placement for detached boxes
skyturkish Sep 25, 2026
6530c13
Clip local backgrounds of scroll containers to the padding box
skyturkish Sep 25, 2026
0d0ad99
Paint each rounded-border row once as outer minus inner span
skyturkish Sep 25, 2026
d6cadee
Support background-clip: border-area for background colors
skyturkish Sep 25, 2026
408a8eb
Support text-align-last and ignore edge spaces when aligning lines
skyturkish Sep 25, 2026
10da0f6
Paint outer box-shadows
skyturkish Sep 25, 2026
5b300e3
Paint opaque constant linear gradients as solid fills
skyturkish Sep 25, 2026
b699670
Support line-clamp, max-lines and continue: collapse
skyturkish Sep 25, 2026
010bff4
Support vertical-align keywords in inline formatting
skyturkish Sep 25, 2026
06bbf0f
Support position: sticky
skyturkish Sep 25, 2026
15ac8d9
Keep floats from splitting unbreakable inline content
skyturkish Sep 25, 2026
682afab
Paint the block ellipsis at a line-clamp clamp point
skyturkish Sep 25, 2026
f5f1cf5
Paint transformed borders and clip overflow to rounded shapes
skyturkish Sep 25, 2026
3d8c3ce
Keep opaque glyphs out of the background-clip: text mask
skyturkish Sep 25, 2026
8541b68
Lay out multi-column containers
skyturkish Sep 25, 2026
5e63ea5
Support border-style and its longhands
skyturkish Sep 25, 2026
2fdbf2b
Support logical properties for horizontal-tb ltr
skyturkish Sep 25, 2026
ecb4e71
Align lines of several inline items and resolve start by direction
skyturkish Sep 25, 2026
3f3da0a
Contain abspos children of fragmented inlines in their fragments
skyturkish Sep 26, 2026
ff40b0c
Support text-emphasis
skyturkish Sep 26, 2026
5727d0e
Support background-blend-mode and clip text backgrounds once
skyturkish Sep 26, 2026
0705e8b
Support border-image with gradient sources
skyturkish Sep 26, 2026
2f14954
Split decorated inline boxes around their blocks
skyturkish Sep 26, 2026
63227c3
Snap fractional flex and percentage edges like browsers
skyturkish Sep 26, 2026
96a297e
Establish block formatting contexts for flow-root and align-content
skyturkish Sep 26, 2026
f429e0a
Follow CSS operator precedence in calc()
skyturkish Sep 26, 2026
df58839
Clamp lines by height with box ends, margins and BFC boundaries
skyturkish Sep 26, 2026
e426d16
Lay out inline-flex and inline-grid as atomic inline-level boxes
skyturkish Sep 26, 2026
eb8fbaf
Align absolute boxes on their static position in block containers
skyturkish Sep 26, 2026
53f1265
Lay out display: inline and inline-block
skyturkish Sep 26, 2026
f3f7642
Align absolute boxes between insets and on their static rectangle
skyturkish Sep 26, 2026
0ede692
Apply center/end align-content to fixed-height block containers
skyturkish Sep 26, 2026
bcd8a2b
Place inline-level absolute boxes on a hypothetical line in block flow
skyturkish Sep 26, 2026
671571e
Size block flex items to their content in a flex row
skyturkish Sep 26, 2026
81a61d5
Place a float after a one-line run at the top of that line
skyturkish Sep 26, 2026
a5b809f
Trim item margins against flex and grid container edges
skyturkish Sep 26, 2026
e3c38de
Resolve transform: inherit to the parent's transform
skyturkish Sep 26, 2026
9e18010
Support the + and ~ sibling combinators in selectors
skyturkish Sep 26, 2026
8c87c3c
Size auto aspect ratios by content box and clamp flex minimums by them
skyturkish Sep 26, 2026
faebbd5
Honor auto margins for blocks laid out in a float context
skyturkish Sep 26, 2026
9357cd7
Draw bare flex text on its baseline unless the container centres it
skyturkish Sep 26, 2026
941f5fe
Store box widths and heights as 32-bit so huge boxes lay out and paint
skyturkish Sep 26, 2026
3849be7
Fix stale paint, clip and style paths flagged in review
skyturkish Sep 29, 2026
31ca9f5
Fix the major issues flagged in review
skyturkish Oct 5, 2026
f86c5c0
Reconcile the rebase with main
skyturkish Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions packages/core/scripts/build-gea-vite-geatsc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1536,22 +1536,27 @@ function parseStaticSelectorPlanSpec(selector) {
if (!text) return null
const parts = []
let nextDirect = false
// Relation to the previous part: 1 next sibling (+), 2 any earlier sibling (~).
let nextSibling = 0
const combinator = (char) => char === '>' || char === '+' || char === '~'
let i = 0
while (i < text.length) {
while (i < text.length && text.charCodeAt(i) <= 32) i++
if (i >= text.length) break
if (text[i] === '>') {
nextDirect = true
if (combinator(text[i])) {
nextDirect = text[i] === '>'
nextSibling = text[i] === '+' ? 1 : text[i] === '~' ? 2 : 0
i++
continue
}
const start = i
while (i < text.length && text.charCodeAt(i) > 32 && text[i] !== '>') i++
while (i < text.length && text.charCodeAt(i) > 32 && !combinator(text[i])) i++
if (i <= start) continue
const simple = parseStaticSimpleSelectorSpec(text.slice(start, i))
if (!simple) return null
parts.push({ simple, directParent: nextDirect })
parts.push({ simple, directParent: nextDirect, sibling: nextSibling })
nextDirect = false
nextSibling = 0
}
return parts.length > 0 ? { text, parts } : null
}
Expand All @@ -1564,7 +1569,7 @@ function staticSimpleSelectorSpecCode(simple) {
}

function staticSelectorPartSpecCode(part) {
return `{${staticSimpleSelectorSpecCode(part.simple)}, ${part.directParent ? 'true' : 'false'}}`
return `{${staticSimpleSelectorSpecCode(part.simple)}, ${part.directParent ? 'true' : 'false'}, ${part.sibling}}`
}

function staticSelectorPlanRegistration(selector) {
Expand Down Expand Up @@ -2046,7 +2051,8 @@ function cssStaticPropertyValue(property, rawValue) {
const length = simpleStaticLength(value)
switch (property) {
case 'display': {
const values = new Map([['block', 0], ['none', 1], ['grid', 2], ['inline-grid', 2], ['flex', 3], ['inline-flex', 3]])
// Keyword flags above the box kind, as in the engine's displayValue().
const values = new Map([['block', 0], ['none', 1], ['grid', 2], ['inline-grid', 66], ['flex', 3], ['inline-flex', 67], ['flow-root', 32], ['inline', 64], ['inline-block', 96]])
return values.has(value) ? direct('Display', values.get(value)) : null
}
case 'box-sizing':
Expand Down Expand Up @@ -2141,9 +2147,12 @@ function cssStaticPropertyValue(property, rawValue) {
if (lower === 'scale-down') return direct('ImageFit', 4)
return null
case 'text-align':
if (value === 'left' || value === 'start') return direct('TextAlign', 0)
// 0 start, 1 center, 2 right, 3 left, 4 end (see textAlignValue in style.cpp).
if (value === 'start') return direct('TextAlign', 0)
if (value === 'center') return direct('TextAlign', 1)
if (value === 'right' || value === 'end') return direct('TextAlign', 2)
if (value === 'right') return direct('TextAlign', 2)
if (value === 'left') return direct('TextAlign', 3)
if (value === 'end') return direct('TextAlign', 4)
return null
case 'text-decoration':
case 'text-decoration-line':
Expand Down
5 changes: 3 additions & 2 deletions packages/core/test/test_css_block_and_flexbasis_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4370,11 +4370,12 @@ void testBaselineAlignment()
StyleSheet::instance().registerRule("baseline-child", "align-self", "last baseline");
StyleSheet::instance().registerRule("baseline-child", "justify-self", "baseline last");
tree.mount(root, 300, 200);
// The fallback end belongs to the grid, not the child's own
// direction or writing mode (WPT grid-abspos-staticpos-*-002).
const bool reversed = std::strcmp(mode, "ltr") != 0;
const bool endAtLeft = reversed && !ownWritingMode;
const int areaLeft = positioned && !small ? (reversed ? 21 : 5) : 3;
const int areaWidth = small ? 2 : positioned ? 20 : 40;
const int x = areaLeft + (endAtLeft ? 0 : areaWidth - 8);
const int x = areaLeft + (reversed ? 0 : areaWidth - 8);
const int y = small ? -1 : positioned ? 19 : 37;
expectEqual(tree.node(child).layout.x - tree.node(grid).layout.x, x, "absolute baseline fallback uses logical horizontal end without overflow clamp");
expectEqual(tree.node(child).layout.y - tree.node(grid).layout.y, y, "absolute baseline fallback uses logical vertical end without overflow clamp");
Expand Down
94 changes: 45 additions & 49 deletions packages/engine/canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4122,57 +4122,53 @@ void Canvas::strokeRoundedRect(int x, int y, int w, int h, int tl, int tr, int b
markDirty(dirtyX0, dirtyY0, dirtyX1, dirtyY1);
return;
}
fillRect(x + tl, y, w - tl - tr, lw, color);
fillRect(x + bl, y + h - lw, w - bl - br, lw, color);
fillRect(x, y + tl, lw, h - tl - bl, color);
fillRect(x + w - lw, y + tr, lw, h - tr - br, color);
for (int q = 0; q < 4; q++) {
int r, cx, cy;
switch (q) {
case 0: r = tr; cx = x + w - 1 - r; cy = y + r; break;
case 1: r = tl; cx = x + r; cy = y + r; break;
case 2: r = bl; cx = x + r; cy = y + h - 1 - r; break;
case 3: r = br; cx = x + w - 1 - r; cy = y + h - 1 - r; break;
default: continue;
}
if (r <= 0) continue;
if (cx + r < clip->x0 || cx - r > clip->x1 ||
cy + r < clip->y0 || cy - r > clip->y1)
// Each row is painted once as the outer shape's span minus the inner
// (padding-edge) shape's span, using fillRoundedRect's scanline so the ring
// is the exact complement of a fill of the inner box. Separate edge and
// corner pieces used to overlap, blending translucent borders twice where
// they met and bulging 1px into the padding area at the curves.
const int ix = x + lw, iy = y + lw, iw = w - 2 * lw, ih = h - 2 * lw;
const int innerMax = std::min(iw / 2, ih / 2);
auto innerRadius = [&](int r) { return std::min(std::max(0, r - lw), std::max(0, innerMax)); };
const int itl = innerRadius(tl), itr = innerRadius(tr), ibr = innerRadius(br), ibl = innerRadius(bl);
auto rowSpan = [](int bx, int by, int bw, int bh, int rtl, int rtr, int rbr, int rbl, int sy, int &left, int &right) {
if (bw <= 0 || bh <= 0 || sy < by || sy >= by + bh) return false;
left = bx + bw;
right = bx - 1;
auto corner = [&](int r, int cy, bool leftSide) {
const int dy = sy > cy ? sy - cy : cy - sy;
const int dx = CanvasMath::integerSqrt(r * r - dy * dy);
if (leftSide) left = std::min(left, bx + r - dx);
else right = std::max(right, bx + bw - 1 - r + dx);
};
const bool inTopLeft = rtl > 0 && sy <= by + rtl, inBottomLeft = rbl > 0 && sy >= by + bh - 1 - rbl;
const bool inTopRight = rtr > 0 && sy <= by + rtr, inBottomRight = rbr > 0 && sy >= by + bh - 1 - rbr;
if (inTopLeft) corner(rtl, by + rtl, true);
if (inBottomLeft) corner(rbl, by + bh - 1 - rbl, true);
if (!inTopLeft && !inBottomLeft) left = bx;
if (inTopRight) corner(rtr, by + rtr, false);
if (inBottomRight) corner(rbr, by + bh - 1 - rbr, false);
if (!inTopRight && !inBottomRight) right = bx + bw - 1;
return left <= right;
};
auto paint = [&](int sy, int sx0, int sx1) {
if (sx0 < clip->x0) sx0 = clip->x0;
if (sx1 > clip->x1) sx1 = clip->x1;
if (sx0 < 0) sx0 = 0;
if (sx1 >= width_) sx1 = width_ - 1;
if (sx0 <= sx1) fillSpanGlobalAlpha(rowToPhysical(sy), sx0, sx1 - sx0 + 1, color);
};
int row0 = std::max(std::max(y, clip->y0), 0);
int row1 = std::min(std::min(y + h - 1, clip->y1), height_ - 1);
for (int sy = row0; sy <= row1; ++sy) {
int oL, oR, iL, iR;
if (!rowSpan(x, y, w, h, tl, tr, br, bl, sy, oL, oR)) continue;
if (!rowSpan(ix, iy, iw, ih, itl, itr, ibr, ibl, sy, iL, iR)) {
paint(sy, oL, oR);
continue;
int ri = r - lw;
if (ri < 0) ri = 0;
int dy0 = 0;
int dy1 = r;
if (q == 0 || q == 1) {
if (cy - dy0 > clip->y1) dy0 = cy - clip->y1;
if (cy - dy1 < clip->y0) dy1 = cy - clip->y0;
} else {
if (cy + dy0 < clip->y0) dy0 = clip->y0 - cy;
if (cy + dy1 > clip->y1) dy1 = clip->y1 - cy;
}
if (dy0 < 0) dy0 = 0;
if (dy1 > r) dy1 = r;
if (dy0 > dy1) continue;
for (int dy = dy0; dy <= dy1; dy++) {
int dx_outer = CanvasMath::integerSqrt(r * r - dy * dy);
int dx_inner = (dy <= ri) ? CanvasMath::integerSqrt(ri * ri - dy * dy) : 0;
int sx0, sx1, sy;
switch (q) {
case 0: sx0 = cx + dx_inner; sx1 = cx + dx_outer; sy = cy - dy; break;
case 1: sx0 = cx - dx_outer; sx1 = cx - dx_inner; sy = cy - dy; break;
case 2: sx0 = cx - dx_outer; sx1 = cx - dx_inner; sy = cy + dy; break;
case 3: sx0 = cx + dx_inner; sx1 = cx + dx_outer; sy = cy + dy; break;
default: continue;
}
if (sy < clip->y0 || sy > clip->y1) continue;
if (sx0 < clip->x0) sx0 = clip->x0;
if (sx1 > clip->x1) sx1 = clip->x1;
if (sx0 < 0) sx0 = 0;
if (sx1 >= width_) sx1 = width_ - 1;
if (sx0 > sx1 || sy < 0 || sy >= height_) continue;

fillSpanGlobalAlpha(rowToPhysical(sy), sx0, sx1 - sx0 + 1, color);
}
paint(sy, oL, std::min(oR, iL - 1));
paint(sy, std::max(oL, iR + 1), oR);
}
markDirtyClipped(x, y, x + w - 1, y + h - 1);
}
Expand Down
9 changes: 9 additions & 0 deletions packages/engine/ui/css_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,15 @@
#define GEA_CSS_BACKGROUND_LAYERS 1
#endif

// CSS Overflow 4 line clamping (line-clamp, max-lines, continue, block-ellipsis)
// and CSS Multi-column (column-count, column-width, column-fill, column-span).
#ifndef GEA_CSS_LINE_CLAMP
#define GEA_CSS_LINE_CLAMP 1
#endif
#ifndef GEA_CSS_MULTICOL
#define GEA_CSS_MULTICOL 1
#endif

#ifndef GEA_CSS_LINE_HEIGHT_EXPRESSIONS
#define GEA_CSS_LINE_HEIGHT_EXPRESSIONS 1
#endif
Expand Down
6 changes: 6 additions & 0 deletions packages/engine/ui/display_invalidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ bool DisplayInvalidation::rebuildsNodeDisplayCommands(Property prop)
case Property::BackgroundColor:
case Property::BackgroundAlpha:
case Property::BackgroundClip:
case Property::BackgroundBlendMode:
case Property::BorderImageSource:
case Property::BorderImageSlice:
case Property::BorderImageWidth:
case Property::BorderImageOutset:
case Property::BorderImageRepeat:
case Property::BackgroundImage:
case Property::Containment:
case Property::ActiveBackgroundColor:
Expand Down
4 changes: 2 additions & 2 deletions packages/engine/ui/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ class InputController {
int hitTestNodeId(int id, int x, int y, bool clipped = false, bool groupRoot = true, bool includePositioned = true)
{
Node &node = Tree::instance().nodes()[id];
if (node.computedStyle().display == 1 || isCollapsedFlexSubtree(node)) return -1;
if (node.computedStyle().display == 1 || (node.layout.line_clamp_hidden & 1) || isCollapsedFlexSubtree(node)) return -1;
// CSS `pointer-events: none` — this node and its subtree are never the
// target of a pointer event, so the hit-test falls through to whatever is
// painted behind it. Without this, a decorative overlay image positioned
Expand Down Expand Up @@ -482,7 +482,7 @@ class InputController {
int findScrollNodeId(int id, int x, int y, bool clipped = false, bool groupRoot = true, bool includePositioned = true)
{
Node &node = Tree::instance().nodes()[id];
if (node.computedStyle().display == 1 || isCollapsedFlexSubtree(node)) return -1;
if (node.computedStyle().display == 1 || (node.layout.line_clamp_hidden & 1) || isCollapsedFlexSubtree(node)) return -1;

const bool inside = pointInsideNodeHitArea(node, x, y);
if (LayoutEngine::isViewportFixed(node)) clipped = false;
Expand Down
8 changes: 7 additions & 1 deletion packages/engine/ui/input_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void InputRenderer::record(int id)
cmd->text.maxWidth = contentW;
cmd->text.color = color;
cmd->text.scale = textScale;
cmd->text.align = n.computedStyle().text_align;
cmd->text.align = static_cast<int8_t>(LayoutEngine::physicalTextAlign(n));
cmd->text.textTransform = n.computedStyle().text_transform;
cmd->text.lineHeight = n.computedStyle().line_height;
cmd->text.containerWidth = contentW;
Expand All @@ -171,6 +171,12 @@ void InputRenderer::record(int id)
cmd->text.textOverflow = n.computedStyle().text_overflow;
cmd->text.maxHeight = 0;
cmd->text.firstLineIndent = 0;
cmd->text.alignLast = -1;
cmd->text.lineLimit = 0;
cmd->text.blockEllipsis = 0;
cmd->text.ellipsisWidth = 0;
cmd->text.emphasis = 0;
cmd->text.emphasisColor = 0;
}
}

Expand Down
Loading
Loading