Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 27 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dioxus-cli-config = { version = "0.7.3" }
dioxus-core-macro = { version = "0.7.3" }

# Taffy + Parley + Fontations
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4ae4219b34e0ad22fd907cb1a7fa9a68c1ea1621", default-features = false, features = [
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "bb30dbf261c46eef5008376c62470e8e7874a148", default-features = false, features = [
"std",
"flexbox",
"grid",
Expand All @@ -105,7 +105,7 @@ taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4ae4219b34e0ad22fd
"calc",
"detailed_layout_info",
] }
parley = { version = "0.11.1", default-features = false, features = ["std"] }
parley = { git = "https://github.com/DioxusLabs/parley", rev = "95a9ff1479533a03775b8e53587495dd41a01b74", default-features = false, features = ["std"] }
skrifa = { version = "0.44", default-features = false, features = [
"std",
] } # Should match parley and vello versions
Expand Down
28 changes: 25 additions & 3 deletions packages/blitz-dom/src/layout/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ use std::sync::Arc;

use markup5ever::{QualName, local_name, ns};
use parley::{
FontContext, InlineBox, InlineBoxKind, LayoutContext, StyleProperty, TreeBuilder,
WhiteSpaceCollapse,
FontContext, InlineBox, InlineBoxKind, InlineBoxVerticalAlign, LayoutContext, StyleProperty,
TreeBuilder, WhiteSpaceCollapse,
};
use style::{
computed_values::position::T as PositionProperty,
data::ElementData as StyloElementData,
shared_lock::StylesheetGuards,
values::{
computed::{Content, ContentItem, Display, Float, TextTransform},
computed::{BaselineShift, Content, ContentItem, Display, Float, TextTransform},
generics::box_::BaselineShiftKeyword,
specified::box_::{DisplayInside, DisplayOutside},
},
};
Expand Down Expand Up @@ -1031,6 +1032,7 @@ pub(crate) fn build_inline_layout_into(

// Create a parley tree builder
let mut builder = layout_ctx.tree_builder(font_ctx, scale, true, &parley_style);
builder.set_compute_strut(true);

// Set whitespace collapsing mode
let collapse_mode = root_node_style
Expand Down Expand Up @@ -1190,6 +1192,10 @@ pub(crate) fn build_inline_layout_into(
// Width and height are set during layout
width: 0.0,
height: 0.0,
baseline: None,
vertical_align: inline_box_vertical_align(
style.map(|s| s.clone_baseline_shift()),
),
});
} else if *tag_name == local_name!("br") {
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
Expand Down Expand Up @@ -1270,6 +1276,10 @@ pub(crate) fn build_inline_layout_into(
// Width and height are set during layout
width: 0.0,
height: 0.0,
baseline: None,
vertical_align: inline_box_vertical_align(
style.map(|s| s.clone_baseline_shift()),
),
});
}
};
Expand Down Expand Up @@ -1298,3 +1308,15 @@ pub(crate) fn build_inline_layout_into(
}
}
}

/// Map the computed `baseline-shift` (the longhand behind `vertical-align: top`/`bottom`)
/// to parley's inline-box vertical alignment.
fn inline_box_vertical_align(baseline_shift: Option<BaselineShift>) -> InlineBoxVerticalAlign {
match baseline_shift {
Some(BaselineShift::Keyword(BaselineShiftKeyword::Top)) => InlineBoxVerticalAlign::Top,
Some(BaselineShift::Keyword(BaselineShiftKeyword::Bottom)) => {
InlineBoxVerticalAlign::Bottom
}
_ => InlineBoxVerticalAlign::Baseline,
}
}
87 changes: 74 additions & 13 deletions packages/blitz-dom/src/layout/inline.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use blitz_traits::node_id::NodeId;
use parley::{AlignmentOptions, IndentOptions};
use style::values::specified::box_::DisplayOutside;
use style::values::{computed::CSSPixelLength, generics::text::GenericTextIndent};
use style::values::{
computed::{CSSPixelLength, Contain},
generics::text::GenericTextIndent,
};
use taffy::{
AvailableSpace, BlockContext, BlockFormattingContext, BoxSizing, CollapsibleMarginSet,
CoreStyle as _, Direction, LayoutInput, LayoutOutput, LayoutPartialTree as _, MaybeMath as _,
MaybeResolve as _, Overflow, Point, Position, RequestedAxis, ResolveOrZero as _, RunMode, Size,
SizingMode,
CoreStyle as _, Direction, Display, LayoutInput, LayoutOutput, LayoutPartialTree as _,
MaybeMath as _, MaybeResolve as _, Overflow, Point, Position, RequestedAxis,
ResolveOrZero as _, RunMode, Size, SizingMode,
};

#[cfg(feature = "floats")]
Expand Down Expand Up @@ -304,11 +307,54 @@ impl BaseDocument {
ibox.width = 0.0;
ibox.height = 0.0;
} else {
let is_scroll_container = style.overflow.x.is_scroll_container()
|| style.overflow.y.is_scroll_container();
let display = style.display;
// Layout containment suppresses the box's baseline (css-contain §3)
let has_layout_containment = self.nodes[NodeId::from_u64(ibox.id)]
.primary_styles()
.is_some_and(|s| s.clone_contain().contains(Contain::LAYOUT));
let output = self.compute_child_layout(taffy::NodeId::from(ibox.id), child_inputs);
// Per CSS, an in-flow inline-block is baseline-aligned to the baseline of its
// last in-flow line box, while inline flex/grid containers use their first
// baseline. A box with no natural baseline uses its bottom margin edge
// (Parley's fallback when `baseline` is `None`, since `ibox.height`
// includes margins). A scroll container also uses its bottom margin edge
// if it is an inline-block, but flex/grid scroll containers still take
// their baseline from their content, clamped to the border box
// (css-align §9.1).
let baseline = match display {
Display::Flex | Display::Grid => output.baselines.first,
_ => output.baselines.last,
};
let baseline = if has_layout_containment {
None
} else if is_scroll_container {
match display {
Display::Flex | Display::Grid => {
baseline.map(|b| b.min(output.size.height).max(0.0))
}
_ => None,
}
} else {
baseline
};
// Round to physical pixels so that content within the box (which is
// positioned relative to the box's top edge and pixel-snapped there)
// stays pixel-aligned after the box is shifted to sit on the
// (pixel-snapped) line baseline.
ibox.baseline = baseline.map(|baseline| ((margin.top + baseline) * scale).round());
ibox.width = (margin.left + margin.right + output.size.width) * scale;
// Vertical margins adjust the space the box reserves in the line, but the
// reserved space cannot be negative.
ibox.height = (margin.top + margin.bottom + output.size.height).max(0.0) * scale;
// Vertical margins adjust the space the box reserves in the line. With an
// explicit baseline the ascent/descent contributions may legitimately be
// negative, but a bottom-aligned box (no baseline) contributes its height
// as ascent, and the space it reserves cannot be negative.
let height = margin.top + margin.bottom + output.size.height;
ibox.height = if ibox.baseline.is_some() {
height * scale
} else {
height.max(0.0) * scale
};
}
}

Expand Down Expand Up @@ -583,7 +629,7 @@ impl BaseDocument {
// dbg!(&layout.size);
// dbg!(&layout.location);

state.append_inline_box_to_line(box_break_data.advance, 0.0);
state.append_inline_box_to_line(box_break_data.advance, None, false);

// if float.is_floated() {
// println!("INLINE FLOATED BOX ({}) {:?}", ibox.id, float);
Expand Down Expand Up @@ -792,11 +838,18 @@ impl BaseDocument {
layout.size = size;
layout.location.x =
(ibox.x / scale) + margin.left + container_pb.left + inset_offset.x;
// A negative `margin-top` shrinks the space the box reserves in the
// line but does not move the box itself, which stays anchored to the
// bottom of the reserved space.
// For a baseline-aligned box, `ibox.y` is the top of the margin box,
// so the border box sits `margin.top` below it. For a bottom-aligned
// box (no baseline), a negative `margin-top` shrinks the space the box
// reserves in the line but does not move the box itself, which stays
// anchored to the bottom of the reserved space.
let margin_top_offset = if ibox.baseline.is_some() {
margin.top
} else {
margin.top.max(0.0)
};
layout.location.y = (ibox.y / scale)
+ margin.top.max(0.0)
+ margin_top_offset
+ container_pb.top
+ inset_offset.y;
layout.padding = padding; //.map(|p| p / scale);
Expand All @@ -817,6 +870,11 @@ impl BaseDocument {
.lines()
.next()
.map(|line| (line.metrics().baseline / scale) + container_pb.top);
let last_baseline = inline_layout
.layout
.lines()
.next_back()
.map(|line| (line.metrics().baseline / scale) + container_pb.top);

// Put layout back
self.nodes[node_id]
Expand All @@ -839,7 +897,10 @@ impl BaseDocument {
bottom: content_extent.height,
}
},
baselines: taffy::Baselines::from_first(first_baseline),
baselines: taffy::Baselines {
first: first_baseline,
last: last_baseline,
},
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
Expand Down
4 changes: 2 additions & 2 deletions packages/blitz-dom/src/node/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,8 +1260,8 @@ impl Node {
if let Some((cluster, _side)) =
Cluster::from_point_exact(layout, x * scale, y * scale)
{
let style_index = cluster.glyphs().next()?.style_index();
let node_id = layout.styles()[style_index].brush.id;
let style_index = cluster.style_index();
let node_id = layout.styles()[usize::from(style_index)].brush.id;
let text_pointer_events_none = self
.with(node_id)
.primary_styles()
Expand Down
14 changes: 10 additions & 4 deletions packages/blitz-paint/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub(crate) fn draw_inline_backgrounds<'a>(
continue;
}

let metrics = glyph_run.run().metrics();
let metrics = glyph_run.run().font_metrics();
let x = glyph_run.offset() as f64;
let w = glyph_run.advance() as f64;
let baseline = glyph_run.baseline() as f64;
Expand All @@ -75,7 +75,7 @@ pub(crate) fn stroke_text<'a>(
let run = glyph_run.run();
let font = run.font();
let font_size = run.font_size();
let metrics = run.metrics();
let metrics = run.font_metrics();
let style = glyph_run.style();
let synthesis = run.synthesis();
let glyph_xform = synthesis
Expand Down Expand Up @@ -109,11 +109,17 @@ pub(crate) fn stroke_text<'a>(
kurbo::Vec2::default()
};

let normalized_coords: Vec<i16> = run
.normalized_coords()
.iter()
.map(|coord| coord.to_bits())
.collect();

scene.draw_glyphs(
font,
&font.font,
font_size,
!FONT_EMBOLDEN_ENABLED, // hint
run.normalized_coords(),
&normalized_coords,
embolden,
Fill::NonZero,
&anyrender::Paint::from(text_color),
Expand Down
4 changes: 1 addition & 3 deletions packages/stylo_taffy/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,7 @@ pub fn item_alignment(input: stylo::AlignFlags, is_horiz_rtl: bool) -> Option<ta
stylo::AlignFlags::RIGHT => Some(taffy::AlignItems::END),
stylo::AlignFlags::CENTER => Some(taffy::AlignItems::CENTER),
stylo::AlignFlags::BASELINE => Some(taffy::AlignItems::BASELINE),
// Taffy does not support last-baseline alignment, so map it to its
// fallback alignment of `self-end` (https://www.w3.org/TR/css-align-3/#baseline-values)
stylo::AlignFlags::LAST_BASELINE => Some(taffy::AlignItems::END),
stylo::AlignFlags::LAST_BASELINE => Some(taffy::AlignItems::LAST_BASELINE),
// Should never be hit. But no real reason to panic here.
_ => None,
}?;
Expand Down
Loading