render: clamp intrinsic replaced sizing
This commit is contained in:
1 parent
436b69f50f
commit
07d2c6691f
2 files changed
+94
-25
No files matched your search
@@ -2064,7 +2064,34 @@ fn build(
|
|||||||
// measure callback derives the other axis through the intrinsic ratio.
|
// measure callback derives the other axis through the intrinsic ratio.
|
||||||
if _name.local.as_ref() == "img" {
|
if _name.local.as_ref() == "img" {
|
||||||
if let Some((width, height)) = style.intrinsic_size {
|
if let Some((width, height)) = style.intrinsic_size {
|
||||||
let context = engine.register_replaced(width, height);
|
// When an auto axis has a definite min/max constraint, the
|
||||||
|
// measured replaced leaf must own intrinsic-ratio transfer so it
|
||||||
|
// can clamp the derived size. Leaving the same ratio on taffy's
|
||||||
|
// style lets taffy synthesize that axis before measurement and
|
||||||
|
// bypass the constraint (width:100%; height:auto;
|
||||||
|
// max-height:200px became the uncapped intrinsic height).
|
||||||
|
//
|
||||||
|
// Keep taffy's ratio in the unconstrained case: percentage-sized
|
||||||
|
// images in auto wrappers can be measured at intrinsic size before
|
||||||
|
// their percentage width becomes definite, and taffy then needs
|
||||||
|
// the ratio to transfer that final width to height.
|
||||||
|
let measured_axis_constraint =
|
||||||
|
(!matches!(style.height, crate::Dimension::Auto)
|
||||||
|
&& matches!(style.width, crate::Dimension::Auto)
|
||||||
|
&& matches!(
|
||||||
|
(style.min_width, style.max_width),
|
||||||
|
(crate::Dimension::Px(_), _) | (_, crate::Dimension::Px(_))
|
||||||
|
))
|
||||||
|
|| (!matches!(style.width, crate::Dimension::Auto)
|
||||||
|
&& matches!(style.height, crate::Dimension::Auto)
|
||||||
|
&& matches!(
|
||||||
|
(style.min_height, style.max_height),
|
||||||
|
(crate::Dimension::Px(_), _) | (_, crate::Dimension::Px(_))
|
||||||
|
));
|
||||||
|
if measured_axis_constraint {
|
||||||
|
taffy_style.aspect_ratio = None;
|
||||||
|
}
|
||||||
|
let context = engine.register_replaced(width, height, style);
|
||||||
let leaf = taffy_tree.new_leaf_with_context(taffy_style, context).ok()?;
|
let leaf = taffy_tree.new_leaf_with_context(taffy_style, context).ok()?;
|
||||||
id_map.insert(leaf, id);
|
id_map.insert(leaf, id);
|
||||||
return Some(leaf);
|
return Some(leaf);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use cosmic_text::{Align, Attrs, Buffer, Color, Family, FontSystem, Metrics, Shap
|
|||||||
|
|
||||||
use obscura_dom::tree::{DomTree, NodeId};
|
use obscura_dom::tree::{DomTree, NodeId};
|
||||||
|
|
||||||
use crate::{Display, LayoutStyle, Rect, TextTransform};
|
use crate::{Dimension, Display, LayoutStyle, Rect, TextTransform};
|
||||||
|
|
||||||
// Bundled faces. Chrome on this class of host renders `sans-serif` and the
|
// Bundled faces. Chrome on this class of host renders `sans-serif` and the
|
||||||
// ubiquitous Arial/Helvetica/system-ui stacks as Liberation Sans, `serif` as
|
// ubiquitous Arial/Helvetica/system-ui stacks as Liberation Sans, `serif` as
|
||||||
@@ -105,11 +105,63 @@ pub struct TextEngine {
|
|||||||
font_system: FontSystem,
|
font_system: FontSystem,
|
||||||
swash: SwashCache,
|
swash: SwashCache,
|
||||||
items: Vec<InlineItem>,
|
items: Vec<InlineItem>,
|
||||||
replaced: Vec<(f32, f32)>,
|
replaced: Vec<ReplacedItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const REPLACED_CONTEXT_BIT: usize = 1usize << (usize::BITS - 1);
|
const REPLACED_CONTEXT_BIT: usize = 1usize << (usize::BITS - 1);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ReplacedItem {
|
||||||
|
intrinsic_width: f32,
|
||||||
|
intrinsic_height: f32,
|
||||||
|
min_width: Option<f32>,
|
||||||
|
min_height: Option<f32>,
|
||||||
|
max_width: Option<f32>,
|
||||||
|
max_height: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReplacedItem {
|
||||||
|
fn from_style(width: f32, height: f32, style: &LayoutStyle) -> Self {
|
||||||
|
let px = |dimension| match dimension {
|
||||||
|
Dimension::Px(value) => Some(value.max(0.0)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
ReplacedItem {
|
||||||
|
intrinsic_width: width,
|
||||||
|
intrinsic_height: height,
|
||||||
|
min_width: px(style.min_width),
|
||||||
|
min_height: px(style.min_height),
|
||||||
|
max_width: px(style.max_width),
|
||||||
|
max_height: px(style.max_height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp(value: f32, min: Option<f32>, max: Option<f32>) -> f32 {
|
||||||
|
// CSS sizing gives the minimum precedence when min > max.
|
||||||
|
let value = max.map_or(value, |max| value.min(max));
|
||||||
|
min.map_or(value, |min| value.max(min))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self, known: taffy::Size<Option<f32>>) -> taffy::Size<f32> {
|
||||||
|
let (width, height) = match (known.width, known.height) {
|
||||||
|
(Some(width), Some(height)) => (width, height),
|
||||||
|
(Some(width), None) => (
|
||||||
|
width,
|
||||||
|
width * self.intrinsic_height / self.intrinsic_width,
|
||||||
|
),
|
||||||
|
(None, Some(height)) => (
|
||||||
|
height * self.intrinsic_width / self.intrinsic_height,
|
||||||
|
height,
|
||||||
|
),
|
||||||
|
(None, None) => (self.intrinsic_width, self.intrinsic_height),
|
||||||
|
};
|
||||||
|
taffy::Size {
|
||||||
|
width: Self::clamp(width, self.min_width, self.max_width),
|
||||||
|
height: Self::clamp(height, self.min_height, self.max_height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for TextEngine {
|
impl Default for TextEngine {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
@@ -267,10 +319,9 @@ impl TextEngine {
|
|||||||
/// taffy's measure function during layout.
|
/// taffy's measure function during layout.
|
||||||
pub fn measure(&mut self, idx: usize, width: Option<f32>) -> (f32, f32) {
|
pub fn measure(&mut self, idx: usize, width: Option<f32>) -> (f32, f32) {
|
||||||
if idx & REPLACED_CONTEXT_BIT != 0 {
|
if idx & REPLACED_CONTEXT_BIT != 0 {
|
||||||
let (iw, ih) = self.replaced[idx & !REPLACED_CONTEXT_BIT];
|
let size = self.replaced[idx & !REPLACED_CONTEXT_BIT]
|
||||||
return width
|
.size(taffy::Size { width, height: None });
|
||||||
.map(|width| (width, width * ih / iw))
|
return (size.width, size.height);
|
||||||
.unwrap_or((iw, ih));
|
|
||||||
}
|
}
|
||||||
let TextEngine { font_system, items, .. } = self;
|
let TextEngine { font_system, items, .. } = self;
|
||||||
let item = &mut items[idx];
|
let item = &mut items[idx];
|
||||||
@@ -282,9 +333,15 @@ impl TextEngine {
|
|||||||
/// Register a replaced element's intrinsic size as a taffy measure
|
/// Register a replaced element's intrinsic size as a taffy measure
|
||||||
/// context. Percentage-sized image leaves still need their intrinsic
|
/// context. Percentage-sized image leaves still need their intrinsic
|
||||||
/// max-content contribution while an auto-sized ancestor is measured.
|
/// max-content contribution while an auto-sized ancestor is measured.
|
||||||
pub fn register_replaced(&mut self, width: f32, height: f32) -> usize {
|
pub fn register_replaced(
|
||||||
|
&mut self,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
style: &LayoutStyle,
|
||||||
|
) -> usize {
|
||||||
let index = self.replaced.len();
|
let index = self.replaced.len();
|
||||||
self.replaced.push((width, height));
|
self.replaced
|
||||||
|
.push(ReplacedItem::from_style(width, height, style));
|
||||||
REPLACED_CONTEXT_BIT | index
|
REPLACED_CONTEXT_BIT | index
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,22 +355,7 @@ impl TextEngine {
|
|||||||
available: taffy::Size<taffy::AvailableSpace>,
|
available: taffy::Size<taffy::AvailableSpace>,
|
||||||
) -> taffy::Size<f32> {
|
) -> taffy::Size<f32> {
|
||||||
if idx & REPLACED_CONTEXT_BIT != 0 {
|
if idx & REPLACED_CONTEXT_BIT != 0 {
|
||||||
let (iw, ih) = self.replaced[idx & !REPLACED_CONTEXT_BIT];
|
return self.replaced[idx & !REPLACED_CONTEXT_BIT].size(known);
|
||||||
return match (known.width, known.height) {
|
|
||||||
(Some(width), Some(height)) => taffy::Size { width, height },
|
|
||||||
(Some(width), None) => taffy::Size {
|
|
||||||
width,
|
|
||||||
height: width * ih / iw,
|
|
||||||
},
|
|
||||||
(None, Some(height)) => taffy::Size {
|
|
||||||
width: height * iw / ih,
|
|
||||||
height,
|
|
||||||
},
|
|
||||||
(None, None) => taffy::Size {
|
|
||||||
width: iw,
|
|
||||||
height: ih,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
let width = known.width.or(match available.width {
|
let width = known.width.or(match available.width {
|
||||||
taffy::AvailableSpace::Definite(width) => Some(width),
|
taffy::AvailableSpace::Definite(width) => Some(width),
|
||||||
|
|||||||
Reference in new issue
Block a user