Improve modern layout and viewport rendering

This commit is contained in:
SGavrl committed 2026-07-30 20:24:57 +00:00
1 parent aff0172987
commit 10b8492116
22 files changed
+5978 -450

No files matched your search

@@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,14 @@
obscura-vf-test.woff2.b64
Source: Inter variable Roman Latin webfont served by https://vuejs.org/
Upstream: https://github.com/rsms/inter
Upstream version recorded in the font: 4.001;git-9221beed3
License: SIL Open Font License 1.1 (see LICENSE-OFL-1.1.txt)
The test fixture was subset with fontTools 4.29.1 to U+0020, U+0041,
U+004D, U+0061, and U+006D while retaining the opsz and wght variation axes.
All family, full, PostScript, unique, and named-instance records were renamed
from the OFL reserved name to "Obscura VF Test" / "ObscuraVFTest" variants.
The original copyright attribution remains in the font's copyright record.
The WOFF2 is stored as base64 text so repository edits remain reviewable.
@@ -0,0 +1,65 @@
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
+459
View File
@@ -930,6 +930,164 @@ fn non_stretched_auto_grid_item_shrink_wraps_inside_grid_area() {
);
}
#[test]
fn percentage_max_width_clamps_grid_item_intrinsic_size() {
let tree = parse_html(
r#"
<style>
body { margin: 0 }
#grid { display: grid; width: 1200px }
#item { max-width: 100% }
#gallery { display: flex; gap: 8px }
.column { flex: none; height: 20px }
</style>
<div id="grid">
<div id="item">
<div id="gallery">
<div class="column" style="width:303px"></div>
<div class="column" style="width:200px"></div>
<div class="column" style="width:500px"></div>
<div class="column" style="width:200px"></div>
<div class="column" style="width:293px"></div>
</div>
</div>
</div>
"#,
);
let layout = layout_dom(&tree, (1280.0, 200.0));
let grid = layout.rects[&tree.get_element_by_id("grid").unwrap()];
let item = layout.rects[&tree.get_element_by_id("item").unwrap()];
let gallery = layout.rects[&tree.get_element_by_id("gallery").unwrap()];
assert!(
(grid.width - 1200.0).abs() < 0.01
&& (item.width - 1200.0).abs() < 0.01
&& (gallery.width - 1200.0).abs() < 0.01,
"max-width:100% must clamp the grid item while its flex contents overflow: \
grid={grid:?} item={item:?} gallery={gallery:?}"
);
}
#[test]
fn named_area_shorthand_survives_end_longhand_override() {
let tree = parse_html(
r#"
<style>
body { margin: 0 }
#grid {
display: grid;
width: 1200px;
grid-template-columns:
[extended-full-start] 16px
[full-start] 200px
[content-start] 768px
[content-end] 200px
[full-end] 16px
[extended-full-end];
}
#grid > * { grid-column: content }
#hero { grid-column-end: extended-full-end }
</style>
<div id="grid"><div id="hero"></div></div>
"#,
);
let layout = layout_dom(&tree, (1280.0, 200.0));
let hero = layout.rects[&tree.get_element_by_id("hero").unwrap()];
assert!(
(hero.x - 216.0).abs() < 0.01 && (hero.width - 984.0).abs() < 0.01,
"the end longhand must retain the shorthand's named content start: {hero:?}"
);
}
#[test]
fn auto_width_flex_button_keeps_native_intrinsic_sizing() {
let tree = parse_html(
r#"
<style>
body { margin: 0 }
#box { width: 600px }
button {
display: flex;
margin: 0 auto;
padding: 19px 24px;
border: 2px solid;
gap: 6px;
font-size: 24px;
}
button::before { content: ""; width: 1em; height: 1em }
</style>
<div id="box"><button id="search">Search</button></div>
"#,
);
let layout = layout_dom(&tree, (800.0, 200.0));
let button = layout.rects[&tree.get_element_by_id("search").unwrap()];
assert!(
button.width > 120.0
&& button.width < 220.0
&& (button.x - (600.0 - button.width) / 2.0).abs() < 0.01,
"the auto-width flex button should shrink-wrap and center: {button:?}"
);
}
#[test]
fn flex_auto_margin_absorbs_space_before_justify_content() {
let tree = parse_html(
r#"
<style>
body { margin: 0 }
#bar {
display: flex;
width: 1280px;
gap: 16px;
justify-content: flex-end;
}
#crumb { width: 42px; height: 20px; margin: 0 auto 0 0 }
#theme { width: 91px; height: 20px }
#language { width: 131px; height: 20px }
</style>
<div id="bar">
<div id="crumb"></div>
<div id="theme"></div>
<div id="language"></div>
</div>
"#,
);
let layout = layout_dom(&tree, (1280.0, 200.0));
let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()];
let crumb = rect("crumb");
let theme = rect("theme");
let language = rect("language");
assert!(
(crumb.x - 0.0).abs() < 0.01
&& (theme.x - 1042.0).abs() < 0.01
&& (language.x - 1149.0).abs() < 0.01,
"main-axis auto margin must consume free space before justify-content: \
crumb={crumb:?} theme={theme:?} language={language:?}"
);
}
#[test]
fn nested_is_descendant_utility_blockifies_matching_lines() {
let tree = parse_html(
r#"
<style>
pre { display: flex }
code code { position: relative }
:is(.\*\*\:\[\.line\]\:block *).line { display: block }
</style>
<div class="**:[.line]:block">
<pre><code><code><span id="one" class="line">one</span><span id="two" class="line">two</span></code></code></pre>
</div>
"#,
);
let layout = layout_dom(&tree, (500.0, 200.0));
let one = tree.get_element_by_id("one").unwrap();
let two = tree.get_element_by_id("two").unwrap();
assert_eq!(layout.styles[&one].display, obscura_render::Display::Block);
assert_eq!(layout.styles[&two].display, obscura_render::Display::Block);
assert!(layout.rects[&two].y > layout.rects[&one].y);
assert_eq!(layout.rects[&one].width, layout.rects[&two].width);
}
#[test]
fn opposing_floats_share_header_band_through_inline_wrapper() {
let tree = parse_html(include_str!("../../../render-repros/opposing-header-floats.html"));
@@ -1168,3 +1326,304 @@ fn contextual_right_inset_uses_root_font_tokens() {
"right inset should place 100px box at 900 - 56 - 100: {rect:?}"
);
}
#[test]
fn false_legacy_supports_probe_does_not_override_modern_line_height() {
// Tailwind v4 uses this old-browser probe around a leading-variable reset.
// Chromium rejects the condition. Flattening every @supports body made
// the reset win, invalidated the var(), and inflated three 96px lines from
// 288px to an inherited 432px.
let tree = parse_html(
r#"
<style>
@supports (((-webkit-hyphens:none)) and
(not (margin-trim:inline))) or
((-moz-orient:inline) and
(not (color:rgb(from red r g b)))) {
* { --leading:initial }
}
:root { --hero-line-height:1 }
body { margin:0; line-height:1.5 }
#hero {
margin:0;
width:600px;
font-size:96px;
line-height:var(--leading,var(--hero-line-height))
}
</style>
<h1 id="hero">one<br>two<br>three</h1>
"#,
);
let layout = layout_dom(&tree, (1280.0, 720.0));
let hero = layout.rects[&tree.get_element_by_id("hero").unwrap()];
assert!(
(hero.height - 288.0).abs() < 0.01,
"three 96px modern lines should be 288px tall: {hero:?}"
);
}
#[test]
fn custom_property_css_wide_keywords_preserve_var_fallback_semantics() {
let tree = parse_html(
r#"
<style>
:root {
--tone:#112233;
--light:initial;
--toggle:var(--light) #18191b;
--page:var(--toggle,#ffffff)
}
#invalid { --tone:initial; color:var(--tone,#445566) }
#inherited { --tone:unset; color:var(--tone,#445566) }
#nested { background-color:var(--page) }
</style>
<div id="invalid">fallback</div>
<div id="inherited">inherit</div>
<div id="nested">nested fallback</div>
"#,
);
let layout = layout_dom(&tree, (800.0, 600.0));
let invalid = tree.get_element_by_id("invalid").unwrap();
let inherited = tree.get_element_by_id("inherited").unwrap();
let nested = tree.get_element_by_id("nested").unwrap();
assert_eq!(
layout.styles[&invalid].color,
Some([0x44, 0x55, 0x66, 0xff]),
"`initial` is guaranteed-invalid and var() must use its fallback"
);
assert_eq!(
layout.styles[&inherited].color,
Some([0x11, 0x22, 0x33, 0xff]),
"`unset` inherits a custom property's parent value"
);
assert_eq!(
layout.styles[&nested].background_color,
Some([0xff, 0xff, 0xff, 0xff]),
"an invalid nested custom property must activate the outer fallback"
);
}
#[test]
fn stable_root_scrollbar_gutter_reduces_the_initial_containing_block() {
let tree = parse_html(
r#"
<style>
html { overflow-y:auto; scrollbar-gutter:stable }
body { margin:0 }
#fill { width:100%; height:40px }
#center { width:401px; height:40px; margin-inline:auto }
</style>
<div id="fill"></div>
<div id="center"></div>
"#,
);
let layout = layout_dom(&tree, (900.0, 600.0));
let fill = layout.rects[&tree.get_element_by_id("fill").unwrap()];
let center = layout.rects[&tree.get_element_by_id("center").unwrap()];
assert!(
(fill.width - 885.0).abs() < 0.01,
"stable classic gutter should reserve 15px: {fill:?}"
);
assert!(
(center.x - 242.0).abs() < 0.01,
"401px box should center in the 885px ICB: {center:?}"
);
}
#[test]
fn auto_grid_track_freezes_at_growth_limit_during_distribution() {
// The first auto track reaches its max-content growth limit before the
// second. The remaining free space belongs only to the second track.
// Giving the frozen track that remainder again makes the grid overflow
// even though the two automatic minimums fit the definite container.
let tree = parse_html(
r#"
<style>
* { box-sizing:border-box }
html, body { margin:0 }
#grid {
display:grid;
width:1016px;
grid-template-columns:auto auto;
grid-template-rows:320px;
column-gap:16px;
}
#copy {
grid-column:1;
font:400 48px/57.6px sans-serif;
}
#art {
grid-column:2;
overflow:hidden;
}
#art > div { width:560px; height:560px }
</style>
<div id="grid">
<h1 id="copy">Resources for Developers,<br> by Developers</h1>
<div id="art"><div></div></div>
</div>
"#,
);
let layout = layout_dom(&tree, (1280.0, 720.0));
let rect = |id| layout.rects[&tree.get_element_by_id(id).unwrap()];
let grid = rect("grid");
let copy = rect("copy");
let art = rect("art");
assert!(
((copy.x + copy.width + 16.0) - art.x).abs() < 0.01,
"the authored column gap must separate the final tracks: {copy:?} {art:?}"
);
assert!(
((art.x + art.width) - (grid.x + grid.width)).abs() < 0.01,
"free-space redistribution must not grow a track after it freezes: {grid:?} {copy:?} {art:?}"
);
}
#[test]
fn empty_block_before_participates_in_normal_flow_geometry() {
let tree = parse_html(
r#"
<style>
html, body { margin:0 }
body { font-size:20px; line-height:20px }
#host { width:200px; background:#eee }
#host::before {
content:"";
display:block;
width:80px;
height:40px;
margin-bottom:10px;
background:#06c;
}
#next { width:20px; height:10px; background:#0a0 }
</style>
<div id="host">TEXT</div><div id="next"></div>
"#,
);
let layout = layout_dom(&tree, (800.0, 600.0));
let host = layout.rects[&tree.get_element_by_id("host").unwrap()];
let next = layout.rects[&tree.get_element_by_id("next").unwrap()];
assert!(
(host.height - 70.0).abs() < 0.01,
"40px generated block + 10px margin + 20px line must size host: {host:?}"
);
assert!(
(next.y - 70.0).abs() < 0.01,
"following flow content must start after generated geometry: {next:?}"
);
}
#[test]
fn empty_inline_block_pseudo_is_an_atomic_flex_item() {
let tree = parse_html(
r#"
<style>
html, body { margin:0 }
#host { display:flex; width:100px; height:20px }
#host::before {
content:"";
display:inline-block;
width:.5em;
height:.5em;
margin-right:5px;
border:2px solid #06c;
}
#label { width:20px; height:10px }
</style>
<div id="host"><span id="label"></span></div>
"#,
);
let layout = layout_dom(&tree, (800.0, 600.0));
let label = layout.rects[&tree.get_element_by_id("label").unwrap()];
assert!(
(label.x - 17.0).abs() < 0.01,
"8px pseudo content width + 2px borders + 5px margin must precede label: {label:?}"
);
}
#[test]
fn light_dark_uses_the_final_inherited_color_scheme_across_the_cascade() {
let tree = parse_html(
r#"
<style>
:root {
--surface:light-dark(#f5f5f5,#18191b);
}
#dark {
background-color:var(--surface);
color:light-dark(#111111,#eeeeee);
border:1px solid light-dark(#dddddd,#333333);
}
#dark {
color-scheme:dark;
}
#dark::before {
content:"";
display:block;
width:10px;
height:10px;
background:light-dark(#ff0000,#0000ff);
}
#child {
background-image:linear-gradient(
light-dark(#ffffff,#101010),
light-dark(#eeeeee,#202020)
);
}
#icon rect {
fill:light-dark(#c3c7cb,#51565d);
stroke:light-dark(#ffffff,#000000);
}
#mixed {
background:light-dark(#abcdef,#123456);
color-scheme:light dark;
}
</style>
<section id="dark">
<div id="child"></div>
<svg id="icon"><rect width="10" height="10"/></svg>
</section>
<div id="mixed"></div>
"#,
);
let layout = layout_dom(&tree, (800.0, 600.0));
let dark = tree.get_element_by_id("dark").unwrap();
let child = tree.get_element_by_id("child").unwrap();
let mixed = tree.get_element_by_id("mixed").unwrap();
let rect = tree.query_selector("#icon rect").unwrap().unwrap();
let dark_style = &layout.styles[&dark];
assert!(dark_style.color_scheme_dark);
assert_eq!(dark_style.background_color, Some([0x18, 0x19, 0x1b, 0xff]));
assert_eq!(dark_style.color, Some([0xee, 0xee, 0xee, 0xff]));
assert_eq!(dark_style.border_color, Some([0x33, 0x33, 0x33, 0xff]));
assert_eq!(
dark_style
.before_pseudo
.as_deref()
.and_then(|pseudo| pseudo.background_color),
Some([0x00, 0x00, 0xff, 0xff]),
"pseudo must use its host's inherited dark scheme"
);
let child_style = &layout.styles[&child];
assert!(child_style.color_scheme_dark);
let (_, stops) = child_style
.background_gradient
.as_ref()
.expect("dark gradient");
assert_eq!(stops[0].0, [0x10, 0x10, 0x10, 0xff]);
assert_eq!(stops[1].0, [0x20, 0x20, 0x20, 0xff]);
let rect_style = &layout.styles[&rect];
assert!(rect_style.color_scheme_dark);
assert_eq!(rect_style.svg_fill.as_deref(), Some("#51565dff"));
assert_eq!(rect_style.svg_stroke.as_deref(), Some("#000000ff"));
let mixed_style = &layout.styles[&mixed];
assert!(
!mixed_style.color_scheme_dark,
"a scheme list admitting light uses the current light preference"
);
assert_eq!(mixed_style.background_color, Some([0xab, 0xcd, 0xef, 0xff]));
}