render: shape atomic generated controls
This commit is contained in:
1 parent
9ffc071ce0
commit
237ba35639
6 files changed
+271
-26
No files matched your search
@@ -128,7 +128,6 @@ impl Stylesheet {
|
|||||||
tree: &DomTree,
|
tree: &DomTree,
|
||||||
matcher: &mut Matcher,
|
matcher: &mut Matcher,
|
||||||
nid: NodeId,
|
nid: NodeId,
|
||||||
host: &LayoutStyle,
|
|
||||||
props: &HashMap<String, String>,
|
props: &HashMap<String, String>,
|
||||||
) -> (Option<LayoutStyle>, Option<LayoutStyle>) {
|
) -> (Option<LayoutStyle>, Option<LayoutStyle>) {
|
||||||
let build = |rules: &[PseudoRule], matcher: &mut Matcher| {
|
let build = |rules: &[PseudoRule], matcher: &mut Matcher| {
|
||||||
@@ -142,12 +141,6 @@ impl Stylesheet {
|
|||||||
}
|
}
|
||||||
matched.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
|
matched.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
|
||||||
let mut style = LayoutStyle::default();
|
let mut style = LayoutStyle::default();
|
||||||
style.color = host.color;
|
|
||||||
style.font_size = host.font_size;
|
|
||||||
style.font_weight = host.font_weight.clone();
|
|
||||||
style.font_family = host.font_family.clone();
|
|
||||||
style.line_height = host.line_height;
|
|
||||||
style.text_transform = host.text_transform;
|
|
||||||
let mut content = None;
|
let mut content = None;
|
||||||
for &(_, _, rule) in &matched {
|
for &(_, _, rule) in &matched {
|
||||||
let expanded = substitute_vars(&rule.normal_decls, props, 0);
|
let expanded = substitute_vars(&rule.normal_decls, props, 0);
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ fn cascade_walk(
|
|||||||
this_props = std::rc::Rc::new(m);
|
this_props = std::rc::Rc::new(m);
|
||||||
}
|
}
|
||||||
let (before_pseudo, after_pseudo) =
|
let (before_pseudo, after_pseudo) =
|
||||||
sheet.pseudo_styles(tree, matcher, id, &style, &this_props);
|
sheet.pseudo_styles(tree, matcher, id, &this_props);
|
||||||
style.before_content = before_pseudo
|
style.before_content = before_pseudo
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|pseudo| pseudo.position != Some(taffy::Position::Absolute))
|
.filter(|pseudo| pseudo.position != Some(taffy::Position::Absolute))
|
||||||
@@ -944,6 +944,116 @@ pub(crate) fn layout_dom_with_web_fonts(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pseudo-elements inherit the originating element's COMPUTED
|
||||||
|
// values. They were cascaded beside the host before this
|
||||||
|
// top-down pass, so inheriting its still-unresolved specified
|
||||||
|
// values there made `font-size:.875rem` disappear and left a
|
||||||
|
// positioned generated label at the 16px paint fallback.
|
||||||
|
// Resolve pseudo-authored relative values against the host,
|
||||||
|
// then fill every omitted inherited property from the host's
|
||||||
|
// now-final computed style.
|
||||||
|
let host_color = style.color;
|
||||||
|
let host_font_size = style.font_size.unwrap_or(parent_fs);
|
||||||
|
let host_weight = crate::style::used_font_weight(style);
|
||||||
|
let host_family = style.font_family.clone();
|
||||||
|
let host_line_height = style.line_height;
|
||||||
|
let host_transform = style.text_transform;
|
||||||
|
let host_italic = style.font_style_italic;
|
||||||
|
let host_text_align = style.text_align;
|
||||||
|
let host_invisible = style.effectively_invisible;
|
||||||
|
let settle_pseudo = |pseudo: &mut crate::LayoutStyle| {
|
||||||
|
if let Some(expression) = pseudo.font_size_expression.as_deref() {
|
||||||
|
pseudo.font_size = crate::style::resolve_contextual_length(
|
||||||
|
expression,
|
||||||
|
host_font_size,
|
||||||
|
root_fs,
|
||||||
|
vw,
|
||||||
|
vh,
|
||||||
|
host_font_size,
|
||||||
|
);
|
||||||
|
} else if let Some(raw) = pseudo.font_size_raw {
|
||||||
|
pseudo.font_size = Some(match raw {
|
||||||
|
crate::Dimension::Percent(percent) => host_font_size * percent,
|
||||||
|
crate::Dimension::Em(value) => host_font_size * value,
|
||||||
|
dimension => match dimension.resolve(
|
||||||
|
host_font_size,
|
||||||
|
root_fs,
|
||||||
|
vw,
|
||||||
|
vh,
|
||||||
|
) {
|
||||||
|
crate::Dimension::Px(pixels) => pixels,
|
||||||
|
_ => host_font_size,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if pseudo.font_size.is_none() {
|
||||||
|
pseudo.font_size = Some(host_font_size);
|
||||||
|
}
|
||||||
|
let pseudo_em = pseudo.font_size.unwrap_or(host_font_size);
|
||||||
|
let weight = crate::style::computed_font_weight(
|
||||||
|
pseudo.font_weight.as_deref(),
|
||||||
|
host_weight,
|
||||||
|
);
|
||||||
|
pseudo.font_weight = Some(weight.to_string());
|
||||||
|
if pseudo.font_family.is_none() {
|
||||||
|
pseudo.font_family = host_family.clone();
|
||||||
|
}
|
||||||
|
if let Some(expression) = pseudo.line_height_expression.as_deref() {
|
||||||
|
if let Some(resolved) = crate::style::resolve_contextual_length(
|
||||||
|
expression,
|
||||||
|
pseudo_em,
|
||||||
|
root_fs,
|
||||||
|
vw,
|
||||||
|
vh,
|
||||||
|
pseudo_em,
|
||||||
|
) {
|
||||||
|
pseudo.line_height = Some(
|
||||||
|
if crate::style::line_height_expression_is_length(expression) {
|
||||||
|
crate::LineHeight::Px(resolved)
|
||||||
|
} else {
|
||||||
|
crate::LineHeight::Ratio(resolved)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some(crate::LineHeight::Relative(relative)) =
|
||||||
|
pseudo.line_height
|
||||||
|
{
|
||||||
|
let pixels = match relative {
|
||||||
|
crate::Dimension::Percent(percent) => pseudo_em * percent,
|
||||||
|
dimension => match dimension.resolve(
|
||||||
|
pseudo_em,
|
||||||
|
root_fs,
|
||||||
|
vw,
|
||||||
|
vh,
|
||||||
|
) {
|
||||||
|
crate::Dimension::Px(pixels) => pixels,
|
||||||
|
_ => pseudo_em,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
pseudo.line_height = Some(crate::LineHeight::Px(pixels));
|
||||||
|
} else if pseudo.line_height.is_none() {
|
||||||
|
pseudo.line_height = host_line_height;
|
||||||
|
}
|
||||||
|
if pseudo.color.is_none() {
|
||||||
|
pseudo.color = host_color;
|
||||||
|
}
|
||||||
|
if pseudo.text_transform.is_none() {
|
||||||
|
pseudo.text_transform = host_transform;
|
||||||
|
}
|
||||||
|
if pseudo.font_style_italic.is_none() {
|
||||||
|
pseudo.font_style_italic = host_italic;
|
||||||
|
}
|
||||||
|
if pseudo.text_align.is_none() {
|
||||||
|
pseudo.text_align = host_text_align;
|
||||||
|
}
|
||||||
|
pseudo.effectively_invisible = host_invisible;
|
||||||
|
};
|
||||||
|
if let Some(pseudo) = style.before_pseudo.as_deref_mut() {
|
||||||
|
settle_pseudo(pseudo);
|
||||||
|
}
|
||||||
|
if let Some(pseudo) = style.after_pseudo.as_deref_mut() {
|
||||||
|
settle_pseudo(pseudo);
|
||||||
|
}
|
||||||
|
|
||||||
// Containing-block width handed to this element's children is
|
// Containing-block width handed to this element's children is
|
||||||
// its own content-box width. A definite content-box width is
|
// its own content-box width. A definite content-box width is
|
||||||
// already that value; a border-box or auto width includes
|
// already that value; a border-box or auto width includes
|
||||||
@@ -4961,4 +5071,28 @@ mod tests {
|
|||||||
assert_eq!(width("border-cell"), 100.0);
|
assert_eq!(width("border-cell"), 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn positioned_pseudo_inherits_the_hosts_computed_font_metrics() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html { font-size:16px }
|
||||||
|
button { font-size:.875rem; line-height:1.5 }
|
||||||
|
button::after {
|
||||||
|
content:attr(text); position:absolute; inset:1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<button id="cta" text="Get Started">Get Started</button>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (1280.0, 720.0));
|
||||||
|
let cta = tree.get_element_by_id("cta").unwrap();
|
||||||
|
let host = &laid.styles[&cta];
|
||||||
|
let pseudo = host.after_pseudo.as_ref().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(host.font_size, Some(14.0));
|
||||||
|
assert_eq!(pseudo.font_size, host.font_size);
|
||||||
|
assert_eq!(pseudo.font_family, host.font_family);
|
||||||
|
assert_eq!(pseudo.font_weight, host.font_weight);
|
||||||
|
assert_eq!(pseudo.line_height, host.line_height);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -535,6 +535,48 @@ impl TextEngine {
|
|||||||
self.push_shaped_item(base, spans, collector.clip_fills)
|
self.push_shaped_item(base, spans, collector.clip_fills)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shape generated text that owns a positioned pseudo box.
|
||||||
|
///
|
||||||
|
/// Positioned `::before`/`::after` boxes do not participate in the taffy
|
||||||
|
/// tree, but their text still uses the same authored webfonts, variable
|
||||||
|
/// weight selection, transformations, and glyph rasterizer as an ordinary
|
||||||
|
/// inline formatting context. The caller measures/finalizes/paints the
|
||||||
|
/// returned item immediately against the pseudo's resolved content box.
|
||||||
|
pub(crate) fn push_generated_text(
|
||||||
|
&mut self,
|
||||||
|
text: &str,
|
||||||
|
style: &LayoutStyle,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let mut collector = Collector {
|
||||||
|
last_was_space: true,
|
||||||
|
clip_fills: Vec::new(),
|
||||||
|
};
|
||||||
|
let (family, weight) = resolve_loaded_font(
|
||||||
|
style.font_family.as_deref(),
|
||||||
|
crate::style::used_font_weight(style),
|
||||||
|
style.font_style_italic.unwrap_or(false),
|
||||||
|
&self.loaded_families,
|
||||||
|
);
|
||||||
|
let context = base_span_ctx(style, family, weight, &mut collector);
|
||||||
|
let attrs = SpanAttrs {
|
||||||
|
weight: context.weight,
|
||||||
|
italic: context.italic,
|
||||||
|
underline: context.underline,
|
||||||
|
color: context.color,
|
||||||
|
family: context.family,
|
||||||
|
clip_fill: context.clip_fill,
|
||||||
|
};
|
||||||
|
let mut spans = Vec::new();
|
||||||
|
push_text(
|
||||||
|
text,
|
||||||
|
context.transform,
|
||||||
|
&attrs,
|
||||||
|
&mut spans,
|
||||||
|
&mut collector,
|
||||||
|
);
|
||||||
|
self.push_shaped_item(style, spans, collector.clip_fills)
|
||||||
|
}
|
||||||
|
|
||||||
/// Shared tail of [`try_build`] / [`try_build_run`]: shape the collected
|
/// Shared tail of [`try_build`] / [`try_build_run`]: shape the collected
|
||||||
/// spans into a cosmic-text buffer under `base`'s font metrics and
|
/// spans into a cosmic-text buffer under `base`'s font metrics and
|
||||||
/// alignment, and store it as a new inline item.
|
/// alignment, and store it as a new inline item.
|
||||||
@@ -936,7 +978,9 @@ fn is_pure_text_ifc(tree: &DomTree, id: NodeId, styles: &std::collections::HashM
|
|||||||
// the flex-column stand-ins our UA sheet uses for td/th/center). A real
|
// the flex-column stand-ins our UA sheet uses for td/th/center). A real
|
||||||
// flex/grid row with inline children is rare and better left to taffy.
|
// flex/grid row with inline children is rare and better left to taffy.
|
||||||
let flow = style.display == Display::Block
|
let flow = style.display == Display::Block
|
||||||
|| (style.display == Display::Flex && style.flex_direction == Some(taffy::FlexDirection::Column));
|
|| style.is_inline_block
|
||||||
|
|| (style.display == Display::Flex
|
||||||
|
&& style.flex_direction == Some(taffy::FlexDirection::Column));
|
||||||
if !flow {
|
if !flow {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1342,6 +1386,23 @@ mod tests {
|
|||||||
assert_eq!(semibold.1, 700);
|
assert_eq!(semibold.1, 700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_only_inline_block_keeps_an_internal_shaping_context() {
|
||||||
|
let tree = obscura_dom::parse_html(
|
||||||
|
"<span id='icon'>ligature_name</span>",
|
||||||
|
);
|
||||||
|
let icon = tree.get_element_by_id("icon").unwrap();
|
||||||
|
let mut style = LayoutStyle::default();
|
||||||
|
style.display = Display::Inline;
|
||||||
|
style.is_inline_block = true;
|
||||||
|
let styles = HashMap::from([(icon, style)]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
is_pure_text_ifc(&tree, icon, &styles),
|
||||||
|
"atomic inline participation must not disable shaping inside the box"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clip_fill_only_for_transparent_clip_text() {
|
fn clip_fill_only_for_transparent_clip_text() {
|
||||||
let mut s = LayoutStyle::default();
|
let mut s = LayoutStyle::default();
|
||||||
|
|||||||
@@ -804,6 +804,16 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style {
|
|||||||
s.align_content = style.align_content;
|
s.align_content = style.align_content;
|
||||||
if let Some(jc) = style.justify_content {
|
if let Some(jc) = style.justify_content {
|
||||||
s.justify_content = Some(jc);
|
s.justify_content = Some(jc);
|
||||||
|
} else if style.is_inline_block {
|
||||||
|
// Taffy's inline-box stand-in is a wrapping flex row. `text-align`
|
||||||
|
// aligns each line of an inline-block's internal formatting context;
|
||||||
|
// map that inherited value onto the row axis without affecting how
|
||||||
|
// the atomic box itself participates in its parent's inline flow.
|
||||||
|
s.justify_content = match style.text_align {
|
||||||
|
Some(taffy::AlignItems::CENTER) => Some(taffy::JustifyContent::CENTER),
|
||||||
|
Some(taffy::AlignItems::FLEX_END) => Some(taffy::JustifyContent::FLEX_END),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if let Some(fg) = style.flex_grow {
|
if let Some(fg) = style.flex_grow {
|
||||||
s.flex_grow = fg;
|
s.flex_grow = fg;
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32), base_url: Option<&str>) -
|
|||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
paint_positioned_pseudo(
|
paint_positioned_pseudo(
|
||||||
|
&mut laid.text_engine,
|
||||||
&mut pixmap,
|
&mut pixmap,
|
||||||
pseudo,
|
pseudo,
|
||||||
&rect,
|
&rect,
|
||||||
@@ -1745,6 +1746,7 @@ fn split_background_size_components(value: &str) -> Vec<&str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint_positioned_pseudo(
|
fn paint_positioned_pseudo(
|
||||||
|
text_engine: &mut crate::inline::TextEngine,
|
||||||
pixmap: &mut Pixmap,
|
pixmap: &mut Pixmap,
|
||||||
style: &crate::LayoutStyle,
|
style: &crate::LayoutStyle,
|
||||||
containing_block: &crate::Rect,
|
containing_block: &crate::Rect,
|
||||||
@@ -1816,10 +1818,8 @@ fn paint_positioned_pseudo(
|
|||||||
paint_linear_gradient(pixmap, &path, &rect, *angle, stops);
|
paint_linear_gradient(pixmap, &path, &rect, *angle, stops);
|
||||||
}
|
}
|
||||||
if let Some(content) = style.before_content.as_deref().filter(|content| !content.is_empty()) {
|
if let Some(content) = style.before_content.as_deref().filter(|content| !content.is_empty()) {
|
||||||
let font_size = style.font_size.unwrap_or(16.0);
|
let Some(item) = text_engine.push_generated_text(content, style) else { return };
|
||||||
let bold = crate::style::used_font_weight(style) >= 600;
|
let (text_width, text_height) = text_engine.measure(item, None);
|
||||||
let text_width = measure_text(content, font_size, bold, style.font_family.as_deref());
|
|
||||||
let line_height = crate::inline::used_line_height(style);
|
|
||||||
let x = match style.justify_content {
|
let x = match style.justify_content {
|
||||||
Some(taffy::JustifyContent::CENTER) => rect.x + (rect.width - text_width) / 2.0,
|
Some(taffy::JustifyContent::CENTER) => rect.x + (rect.width - text_width) / 2.0,
|
||||||
Some(taffy::JustifyContent::FLEX_END | taffy::JustifyContent::END) => {
|
Some(taffy::JustifyContent::FLEX_END | taffy::JustifyContent::END) => {
|
||||||
@@ -1828,23 +1828,14 @@ fn paint_positioned_pseudo(
|
|||||||
_ => rect.x + style.padding.left,
|
_ => rect.x + style.padding.left,
|
||||||
};
|
};
|
||||||
let y = match style.align_items {
|
let y = match style.align_items {
|
||||||
Some(taffy::AlignItems::CENTER) => rect.y + (rect.height - line_height) / 2.0,
|
Some(taffy::AlignItems::CENTER) => rect.y + (rect.height - text_height) / 2.0,
|
||||||
Some(taffy::AlignItems::FLEX_END | taffy::AlignItems::END) => {
|
Some(taffy::AlignItems::FLEX_END | taffy::AlignItems::END) => {
|
||||||
rect.y + rect.height - style.padding.bottom - line_height
|
rect.y + rect.height - style.padding.bottom - text_height
|
||||||
}
|
}
|
||||||
_ => rect.y + style.padding.top,
|
_ => rect.y + style.padding.top,
|
||||||
};
|
};
|
||||||
draw_text(
|
text_engine.finalize(item, (x, y), text_width, Some(visible));
|
||||||
pixmap,
|
text_engine.paint_item(item, pixmap, (0.0, 0.0));
|
||||||
content,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
style.color.unwrap_or([0, 0, 0, 255]),
|
|
||||||
font_size,
|
|
||||||
bold,
|
|
||||||
style.font_family.as_deref(),
|
|
||||||
Some(visible),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2948,6 +2939,46 @@ mod tests {
|
|||||||
assert!(dark_pixels > 10, "generated attr() text must be painted");
|
assert!(dark_pixels > 10, "generated attr() text must be painted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn later_positioned_pseudo_opaquely_covers_the_earlier_one() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<html><head><style>
|
||||||
|
body { margin:0 }
|
||||||
|
#cta {
|
||||||
|
position:relative; width:120px; height:40px; padding:0;
|
||||||
|
color:transparent; background:black;
|
||||||
|
}
|
||||||
|
#cta::before {
|
||||||
|
content:"before";
|
||||||
|
position:absolute; inset:1px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
color:red; background:red;
|
||||||
|
}
|
||||||
|
#cta::after {
|
||||||
|
content:"after";
|
||||||
|
position:absolute; inset:1px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
color:blue; background:white;
|
||||||
|
}
|
||||||
|
</style></head><body><button id="cta">host</button></body></html>"#,
|
||||||
|
);
|
||||||
|
let pixmap = paint_dom(&tree, (140.0, 60.0), None).expect("pixmap");
|
||||||
|
let inner = pixmap.pixel(5, 5).expect("inner pixel");
|
||||||
|
assert_eq!(
|
||||||
|
(inner.red(), inner.green(), inner.blue()),
|
||||||
|
(255, 255, 255),
|
||||||
|
"::after's opaque background must cover ::before"
|
||||||
|
);
|
||||||
|
let red_pixels = (1..119)
|
||||||
|
.flat_map(|x| (1..39).map(move |y| (x, y)))
|
||||||
|
.filter(|&(x, y)| {
|
||||||
|
let pixel = pixmap.pixel(x, y).unwrap();
|
||||||
|
pixel.red() > 180 && pixel.green() < 80 && pixel.blue() < 80
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
assert_eq!(red_pixels, 0, "::before must not bleed through ::after");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn later_element_paints_over_earlier() {
|
fn later_element_paints_over_earlier() {
|
||||||
// A blue div nested inside a red one: both cover the origin, and blue
|
// A blue div nested inside a red one: both cover the origin, and blue
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
} else if tag == "a" {
|
} else if tag == "a" {
|
||||||
style.color = Some([0, 0, 238, 255]); // blue
|
style.color = Some([0, 0, 238, 255]); // blue
|
||||||
style.underline = Some(true); // UA default: links are underlined
|
style.underline = Some(true); // UA default: links are underlined
|
||||||
|
} else if tag == "button" {
|
||||||
|
// HTML buttons are inline-block controls whose anonymous inner
|
||||||
|
// content is centered by the browser UA sheet. Keep the atomic outer
|
||||||
|
// participation separate from the internal text alignment: authored
|
||||||
|
// display/text-align declarations still cascade over both values.
|
||||||
|
style.display = Display::Inline;
|
||||||
|
style.is_inline_block = true;
|
||||||
|
style.text_align = Some(taffy::AlignItems::CENTER);
|
||||||
} else if tag == "input" {
|
} else if tag == "input" {
|
||||||
// Native text controls are atomic inline-level boxes with their own
|
// Native text controls are atomic inline-level boxes with their own
|
||||||
// platform font and intrinsic border-box dimensions; they do not
|
// platform font and intrinsic border-box dimensions; they do not
|
||||||
@@ -3528,6 +3536,14 @@ mod tests {
|
|||||||
assert_eq!(collapsed.border_collapse, Some(true));
|
assert_eq!(collapsed.border_collapse, Some(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn button_ua_style_is_a_centered_atomic_inline_box() {
|
||||||
|
let button = ua_style("button");
|
||||||
|
assert_eq!(button.display, crate::Display::Inline);
|
||||||
|
assert!(button.is_inline_block);
|
||||||
|
assert_eq!(button.text_align, Some(taffy::AlignItems::CENTER));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn border_none_clears_native_and_per_side_widths() {
|
fn border_none_clears_native_and_per_side_widths() {
|
||||||
let input = compute_style("input", Some("border:none"));
|
let input = compute_style("input", Some("border:none"));
|
||||||
|
|||||||
Reference in new issue
Block a user