diff --git a/sbr-util/src/const_tagged_enum.rs b/sbr-util/src/const_tagged_enum.rs new file mode 100644 index 00000000..06a7884e --- /dev/null +++ b/sbr-util/src/const_tagged_enum.rs @@ -0,0 +1,182 @@ +#[macro_export] +macro_rules! const_tagged_enum { + ( + #[const_tagged_enum( + impl_module = $impl_mod: ident, + derive(Debug) + )] + enum $name: ident $(<$($generic_params: tt),*> $(where [$($generic_bounds: tt)*])?)? { + $($variant_name: ident ($variant_ty: ty) = $variant_discriminant: expr),* $(,)? + } + ) => { + mod $impl_mod { + use super::*; + + #[allow(non_snake_case)] + union ImplUnion $($(<$generic_params),*> $(where $($generic_bounds)*)?)? { + $($variant_name: std::mem::ManuallyDrop<$variant_ty>,)* + } + + #[repr(C)] + pub(super) struct $name <$($($generic_params,)*)? const VARIANT: u32> $($(where $($generic_bounds)*)?)? (ImplUnion $($(<$generic_params),*>)?); + + $crate::const_tagged_enum!(@impl_variants + [$($($generic_params),*)?] [$($([$($generic_bounds)*])?)?] + $name $($variant_name($variant_ty) = $variant_discriminant)* + ); + + #[allow(dead_code)] + #[allow(non_snake_case)] + impl<$($($generic_params,)*)? const VARIANT: u32> $name<$($($generic_params,)*)? VARIANT> where $($(where $($generic_bounds)*)?)? { + $crate::const_tagged_enum!(@impl_dispatch_inner_broadcast_generics + [$($($generic_params),*)?] + $name $($variant_name($variant_ty) = $variant_discriminant),* + ); + } + + #[allow(non_snake_case)] + #[automatically_derived] + impl<$($($generic_params,)*)? const VARIANT: u32> std::fmt::Debug for $name<$($($generic_params,)*)? VARIANT> where $($(where $($generic_bounds)*)?)? { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + Self::dispatch_with(self, f, $(|f, $variant_name| std::fmt::Debug::fmt(&*$variant_name, f)),*) + } + } + + #[automatically_derived] + impl<$($($generic_params,)*)? const VARIANT: u32> Drop for $name<$($($generic_params,)*)? VARIANT> where $($(where $($generic_bounds)*)?)? { + #[inline] + fn drop(&mut self) { + const { assert!($(VARIANT == $variant_discriminant)||*, "Const discriminant is not one of the allowed values"); } + + $(if const { VARIANT == $variant_discriminant } { + return unsafe { std::mem::ManuallyDrop::drop(&mut self.0.$variant_name); }; + })* + + unreachable!(); + } + } + } + + use $impl_mod::$name; + }; + // workaround for macro repetition limitations + // have to temporarily "squash" generic params to a tt so the compiler doesn't complain + // about repetition counts + (@impl_variants + $generic_params: tt $generic_bounds: tt $name: ident + $($variant_name: ident($variant_ty: ty) = $variant_discriminant: expr)* + ) => { + $($crate::const_tagged_enum!(@impl_variant $generic_params $generic_bounds $name $variant_name($variant_ty) = $variant_discriminant);)* + }; + (@impl_variant + [$($generic_params: tt)*] [$([$($generic_bounds: tt)*])?] $name: ident + $variant_name: ident($variant_ty: ty) = $variant_discriminant: expr + ) => { + #[allow(dead_code)] + impl<$($generic_params),*> $name<$($generic_params,)* $variant_discriminant> where $($(where $($generic_bounds)*)?)? { + #[inline] + pub fn new(value: $variant_ty) -> Self { + Self(ImplUnion { + $variant_name: std::mem::ManuallyDrop::new(value) + }) + } + + #[inline] + pub fn into_inner(mut this: Self) -> $variant_ty { + let inner = unsafe { std::mem::ManuallyDrop::take(&mut this.0.$variant_name) }; + std::mem::forget(this); + inner + } + } + + #[automatically_derived] + impl<$($generic_params),*> std::ops::Deref for $name<$($generic_params,)* $variant_discriminant> where $($(where $($generic_bounds)*)?)? { + type Target = $variant_ty; + + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { &self.0.$variant_name } + } + } + + #[automatically_derived] + impl<$($generic_params),*> std::ops::DerefMut for $name<$($generic_params,)* $variant_discriminant> where $($(where $($generic_bounds)*)?)? { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut self.0.$variant_name } + } + } + }; + (@impl_dispatch_inner_broadcast_generics + $generic_params: tt + $name: ident $($variant_name: ident($variant_ty: ty) = $variant_discriminant: expr),* + ) => { + $crate::const_tagged_enum!(@impl_dispatch_inner + $name $($generic_params $variant_name($variant_ty) = $variant_discriminant),* + ); + }; + (@impl_dispatch_inner + $name: ident $([$($generic_params: tt)*] $variant_name: ident($variant_ty: ty) = $variant_discriminant: expr),* + ) => { + #[inline] + pub fn dispatch_with(&self, arg: A, $($variant_name: impl FnOnce(A, &$name<$($generic_params,)* $variant_discriminant> ) -> R),*) -> R { + const { assert!($(VARIANT == $variant_discriminant)||*, "Const discriminant is not one of the allowed values"); } + + $(if const { VARIANT == $variant_discriminant } { + // This transmute literally transmutes the value to the exact same type, just "conster". + let concrete_ref = unsafe { std::mem::transmute::<&Self, &$name<$($generic_params,)* $variant_discriminant>>(&self) }; + return $variant_name(arg, concrete_ref); + })* + + unreachable!(); + } + + #[inline] + pub fn dispatch(&self, $($variant_name: impl FnOnce(&$name<$($generic_params,)* $variant_discriminant> ) -> R),*) -> R { + Self::dispatch_with(self, (), $(|_, v| $variant_name(v)),*) + } + }; +} + +#[cfg(test)] +mod test { + const VALUE_TYPE_STRING: u32 = 0; + const VALUE_TYPE_STRING_REF: u32 = 1; + const VALUE_TYPE_NUMBER: u32 = 2; + + const_tagged_enum! { + #[const_tagged_enum( + impl_module = value_impl, + derive(Debug) + )] + enum Value<'a> { + String(String) = VALUE_TYPE_STRING, + StringRef(&'a str) = VALUE_TYPE_STRING_REF, + Number(u32) = VALUE_TYPE_NUMBER, + } + } + + const _: () = const { + assert!( + std::mem::size_of::>() + == std::mem::size_of::>() + ); + assert!( + std::mem::align_of::>() + == std::mem::align_of::>() + ); + }; + + #[test] + fn value_manipulation() { + let string_ref = Value::::new("hello"); + assert_eq!(*string_ref, "hello"); + + let mut string = Value::::new("hello".into()); + string.push_str(" world"); + assert_eq!(*string, "hello world"); + let inner = Value::::into_inner(string); + assert_eq!(inner, "hello world"); + } +} diff --git a/sbr-util/src/lib.rs b/sbr-util/src/lib.rs index b632fc34..d66cda98 100644 --- a/sbr-util/src/lib.rs +++ b/sbr-util/src/lib.rs @@ -1,6 +1,7 @@ use std::{borrow::Borrow, hash::Hash, mem::MaybeUninit, ops::Deref, ptr::NonNull}; pub mod cache; +pub mod const_tagged_enum; pub mod math; pub mod rc; pub mod rev_if; diff --git a/src/layout/block.rs b/src/layout/block.rs index 627010eb..583b802e 100644 --- a/src/layout/block.rs +++ b/src/layout/block.rs @@ -144,6 +144,15 @@ impl PartialBlockContainer<'_> { } } +impl std::fmt::Debug for PartialBlockContainer<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PartialBlockContainer") + .field("style", &self.style) + .field("intrinsic_width", &self.intrinsic_width) + .finish_non_exhaustive() + } +} + pub fn layout_initial<'a>( lctx: &mut LayoutContext, container: &'a BlockContainer, diff --git a/src/layout/inline.rs b/src/layout/inline.rs index 34c698c0..e93125ca 100644 --- a/src/layout/inline.rs +++ b/src/layout/inline.rs @@ -2,7 +2,10 @@ use std::{ops::Range, rc::Rc}; use icu_segmenter::{options::LineBreakOptions, GraphemeClusterSegmenter}; use thiserror::Error; -use util::math::{I26Dot6, Vec2}; +use util::{ + const_tagged_enum, + math::{I26Dot6, Vec2}, +}; use super::{FixedL, FragmentBox, LayoutConstraints, LayoutContext, Vec2L}; use crate::{ @@ -382,8 +385,8 @@ pub enum InlineLayoutError { } #[derive(Debug)] -struct InitialShapingResult<'a, 'f> { - shaped: Vec>, +struct InitialShapingResult<'a, 'f, const PHASE: u32> { + shaped: Vec>, break_opportunities: Vec, text_leaf_items: Vec>, bidi: unicode_bidi::BidiInfo<'a>, @@ -391,7 +394,7 @@ struct InitialShapingResult<'a, 'f> { grapheme_cluster_boundaries: Vec, } -impl InitialShapingResult<'_, '_> { +impl InitialShapingResult<'_, '_, ITEM_PHASE_INITIAL> { fn empty() -> Self { Self { shaped: Vec::new(), @@ -446,10 +449,13 @@ struct LeafItemRange<'a> { style: &'a ComputedStyle, } +const ITEM_PHASE_INITIAL: u32 = 0; +const ITEM_PHASE_IN_LAYOUT: u32 = 1; + #[derive(Debug)] -struct ShapedItem<'a, 'f> { +struct ShapedItem<'a, 'f, const PHASE: u32> { range: Range, - kind: ShapedItemKind<'a, 'f>, + kind: ShapedItemKind<'a, 'f, PHASE>, /// Padding metrics used during line breaking, note that due to bidi /// reordering this *may not correspond to the final padding* applied /// to these glyphs. In fact, since shaped items don't even correspond @@ -458,6 +464,8 @@ struct ShapedItem<'a, 'f> { padding: ShapedItemPadding, } +type LayoutShapedItem<'a, 'f> = ShapedItem<'a, 'f, ITEM_PHASE_IN_LAYOUT>; + #[derive(Debug, Clone)] struct ShapedItemPadding { current_padding_left: FixedL, @@ -485,10 +493,10 @@ impl ShapedItemPadding { } #[derive(Debug)] -enum ShapedItemKind<'a, 'f> { +enum ShapedItemKind<'a, 'f, const PHASE: u32> { Text(ShapedItemText<'f>), - Ruby(ShapedItemRuby<'a, 'f>), - Block(ShapedItemBlock<'a>), + Ruby(ShapedItemRuby<'a, 'f, PHASE>), + Block(ShapedItemBlock<'a, PHASE>), } #[derive(Debug)] @@ -500,29 +508,32 @@ struct ShapedItemText<'f> { } #[derive(Debug)] -struct ShapedItemRuby<'a, 'f> { +struct ShapedItemRuby<'a, 'f, const PHASE: u32> { style: ComputedStyle, - base_annotation_pairs: Vec<(ShapedRubyBase<'a, 'f>, ShapedRubyAnnotation<'a, 'f>)>, + base_annotation_pairs: Vec<( + ShapedRubyBase<'a, 'f, PHASE>, + ShapedRubyAnnotation<'a, 'f, PHASE>, + )>, span_id: usize, } #[derive(Debug)] -struct ShapedRubyBase<'a, 'f> { +struct ShapedRubyBase<'a, 'f, const PHASE: u32> { style: &'a ComputedStyle, primary_font: &'f Font, - inner: InitialShapingResult<'a, 'f>, + inner: InitialShapingResult<'a, 'f, PHASE>, } #[derive(Debug)] -struct ShapedRubyAnnotation<'a, 'f> { +struct ShapedRubyAnnotation<'a, 'f, const PHASE: u32> { style: &'a ComputedStyle, primary_font: &'f Font, - inner: InitialShapingResult<'a, 'f>, + inner: InitialShapingResult<'a, 'f, PHASE>, } #[derive(Debug)] -struct ShapedItemBlock<'a> { - kind: ShapedItemBlockKind<'a>, +struct ShapedItemBlock<'a, const PHASE: u32> { + kind: ShapedItemBlockKind<'a, PHASE>, span_id: usize, } @@ -532,119 +543,136 @@ struct ShapedItemBlock<'a> { // container. // - If we're performing the second "full" stage of the layout pass it should be // immediately transitioned to the `Fragment` state and remain that way. -// -// TODO: This probably *could* be encoded into the type-system with enough effort. -enum ShapedItemBlockKind<'a> { - Partial(PartialBlockContainer<'a>), - Fragment(ShapedItemBlockFragment), +const_tagged_enum! { + #[const_tagged_enum( + impl_module = value_impl, + derive(Debug) + )] + enum ShapedItemBlockKind<'a> { + Partial(PartialBlockContainer<'a>) = ITEM_PHASE_INITIAL, + Fragment(ShapedItemBlockFragment) = ITEM_PHASE_IN_LAYOUT + } } -impl ShapedItemBlockKind<'_> { - const EMPTY: Self = Self::Fragment(ShapedItemBlockFragment { - alignment_baseline: FixedL::ZERO, - block: BlockContainerFragment::EMPTY, - }); - +impl<'a> ShapedItemBlockKind<'a, ITEM_PHASE_INITIAL> { fn width(&self, available_width: FixedL) -> FixedL { - match self { - // https://www.w3.org/TR/CSS2/visudet.html#inlineblock-width - // TODO: This also needs to take into account minimum intrinsic width - // But this works in *most* cases - ShapedItemBlockKind::Partial(partial) => partial.intrinsic_width().min(available_width), - ShapedItemBlockKind::Fragment(ShapedItemBlockFragment { - block: fragment, .. - }) => fragment.fbox.size_for_layout().x, - } + // https://www.w3.org/TR/CSS2/visudet.html#inlineblock-width + // TODO: This also needs to take into account minimum intrinsic width + // But this works in *most* cases + self.intrinsic_width().min(available_width) } - fn assert_as_fragment(&self) -> &ShapedItemBlockFragment { - let Self::Fragment(fragment) = self else { - unreachable!("`ShapedItemBlock` is a partial block container but a laid-out fragment was expected at this point"); - }; - - fragment - } - - fn assert_take_fragment(&mut self) -> ShapedItemBlockFragment { - let Self::Fragment(fragment) = std::mem::replace(self, Self::EMPTY) else { - unreachable!("`ShapedItemBlock` is a partial block container but a laid-out fragment was expected at this point"); - }; - - fragment - } - - fn make_fragment( - &mut self, + fn into_fragment( + self, lctx: &mut LayoutContext, available_width: FixedL, - ) -> Result<(), InlineLayoutError> { - match std::mem::replace(self, Self::EMPTY) { - Self::Partial(partial) => { - let width = partial.intrinsic_width().min(available_width); - let fragment = partial.layout( - lctx, - &LayoutConstraints { - size: Vec2::new(width, FixedL::MAX), - }, - )?; - *self = Self::Fragment(ShapedItemBlockFragment { - alignment_baseline: fragment.alphabetic_baseline().unwrap_or_else(|| { - // https://www.w3.org/TR/css-inline-3/#baseline-synthesis-box - // We assume alphabetic baseline so this is just the bottom of the - // margin box. - fragment.fbox.margin_box().max.y - }), - block: fragment, - }) - } - Self::Fragment(_) => { - unreachable!("Item is already a fragment in `ShapedItemBlockKind::make_fragment`") - } - }; - - Ok(()) - } -} - -impl std::fmt::Debug for ShapedItemBlockKind<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Partial(_) => f.debug_tuple("Partial").finish_non_exhaustive(), - Self::Fragment(_) => f.debug_tuple("Fragment").finish_non_exhaustive(), - } + ) -> Result, InlineLayoutError> { + let width = self.width(available_width); + let fragment = Self::into_inner(self).layout( + lctx, + &LayoutConstraints { + size: Vec2::new(width, FixedL::MAX), + }, + )?; + + Ok(ShapedItemBlockKind::::new( + ShapedItemBlockFragment { + alignment_baseline: fragment.alphabetic_baseline().unwrap_or_else(|| { + // https://www.w3.org/TR/css-inline-3/#baseline-synthesis-box + // We assume alphabetic baseline so this is just the bottom of the + // margin box. + fragment.fbox.margin_box().max.y + }), + block: fragment, + }, + )) } } +#[derive(Debug)] struct ShapedItemBlockFragment { alignment_baseline: FixedL, block: BlockContainerFragment, } -fn finish_block_layout_in_shaped_items( - items: &mut [ShapedItem], - lctx: &mut LayoutContext, - constraints: &LayoutConstraints, -) -> Result<(), InlineLayoutError> { - for item in items { - match &mut item.kind { - ShapedItemKind::Text(_) => (), - ShapedItemKind::Ruby(ruby) => { - for (base, annotation) in &mut ruby.base_annotation_pairs { - finish_block_layout_in_shaped_items(&mut base.inner.shaped, lctx, constraints)?; - finish_block_layout_in_shaped_items( - &mut annotation.inner.shaped, - lctx, - constraints, - )?; - } - } - ShapedItemKind::Block(ShapedItemBlock { kind, .. }) => { - kind.make_fragment(lctx, constraints.size.x)? - } - } +impl<'a> ShapedItemBlockKind<'a, ITEM_PHASE_IN_LAYOUT> { + fn width(&self) -> FixedL { + self.block.fbox.size_for_layout().x + } +} + +impl<'a, 'f> ShapedItemRuby<'a, 'f, ITEM_PHASE_INITIAL> { + fn transition_to_layout( + self, + lctx: &mut LayoutContext, + constraints: &LayoutConstraints, + ) -> Result, InlineLayoutError> { + Ok(ShapedItemRuby { + style: self.style, + span_id: self.span_id, + base_annotation_pairs: self + .base_annotation_pairs + .into_iter() + .map(|(base, annotation)| { + Ok(( + ShapedRubyBase { + style: base.style, + primary_font: base.primary_font, + inner: base.inner.transition_to_layout(lctx, constraints)?, + }, + ShapedRubyAnnotation { + style: annotation.style, + primary_font: annotation.primary_font, + inner: annotation.inner.transition_to_layout(lctx, constraints)?, + }, + )) + }) + .collect::>()?, + }) } +} - Ok(()) +impl<'a, 'f> InitialShapingResult<'a, 'f, ITEM_PHASE_INITIAL> { + fn transition_to_layout( + self, + lctx: &mut LayoutContext, + constraints: &LayoutConstraints, + ) -> Result, InlineLayoutError> { + Ok(InitialShapingResult { + shaped: { + // NOTE: `ShapedItem`s have exactly the same layout regardless of their phase so + // this should be specialized to an in-place transformation :) + // THAT is what we call a zero-cost abstraction. + // Though not sure whether libstd specializes `Result<>` collection too, I hope so. + self.shaped + .into_iter() + .map(|item| { + Ok(ShapedItem { + range: item.range, + padding: item.padding, + kind: match item.kind { + ShapedItemKind::Text(text) => ShapedItemKind::Text(text), + ShapedItemKind::Ruby(ruby) => ShapedItemKind::Ruby( + ruby.transition_to_layout(lctx, constraints)?, + ), + ShapedItemKind::Block(ShapedItemBlock { kind, span_id }) => { + ShapedItemKind::Block(ShapedItemBlock { + kind: kind.into_fragment(lctx, constraints.size.x)?, + span_id, + }) + } + }, + }) + }) + .collect::>()? + }, + break_opportunities: self.break_opportunities, + text_leaf_items: self.text_leaf_items, + bidi: self.bidi, + font_feature_events: self.font_feature_events, + grapheme_cluster_boundaries: self.grapheme_cluster_boundaries, + }) + } } fn font_matcher_from_style<'f>( @@ -771,7 +799,7 @@ fn shape_run_initial<'a, 'f>( compute_break_opportunities: bool, span_state: &mut Vec>, inner_style: &'a ComputedStyle, -) -> Result, InlineLayoutError> { +) -> Result, InlineLayoutError> { struct QueuedText<'f> { matcher: FontMatcher<'f>, range: Range, @@ -784,7 +812,7 @@ fn shape_run_initial<'a, 'f>( bidi: &unicode_bidi::BidiInfo, font_arena: &'f FontArena, lctx: &mut LayoutContext, - result: &mut Vec>, + result: &mut Vec>, font_features_events: &[FontFeatureEvent], left_padding: &mut FixedL, buffer: &mut ShapingBuffer, @@ -876,7 +904,7 @@ fn shape_run_initial<'a, 'f>( font_arena: &'f FontArena, break_opportunities: Vec, - shaped: Vec>, + shaped: Vec>, span_state: &'s mut Vec>, shaping_buffer: ShapingBuffer, queued_text: Option>, @@ -1088,7 +1116,7 @@ fn shape_run_initial<'a, 'f>( end_item_index: &mut usize, compute_break_opportunities: bool, inner_style: &'a ComputedStyle, - ) -> Result, InlineLayoutError> { + ) -> Result, InlineLayoutError> { let items = &self.content.items; let mut current_item = item_index; let mut current_style = inner_style; @@ -1345,9 +1373,9 @@ fn shape_run_initial<'a, 'f>( self.shaped.push(ShapedItem { range: content_index..content_end, kind: ShapedItemKind::Block(ShapedItemBlock { - kind: ShapedItemBlockKind::Partial(super::block::layout_initial( - self.lctx, block, - )?), + kind: ShapedItemBlockKind::::new( + super::block::layout_initial(self.lctx, block)?, + ), span_id: self.current_span_id, }), padding: ShapedItemPadding { @@ -1458,47 +1486,15 @@ struct BreakingContext<'f, 'l, 'a, 'b> { #[derive(Debug)] #[allow(clippy::large_enum_variant)] // TODO: is this a problem here? enum BreakOutcome<'a, 'f> { - BreakSplit(ShapedItem<'a, 'f>), + BreakSplit(ShapedItem<'a, 'f, ITEM_PHASE_IN_LAYOUT>), BreakAfter, BreakBefore, None, } -impl<'a, 'f> ShapedItem<'a, 'f> { - fn line_break( - &mut self, - current_width: &mut FixedL, - ctx: &mut BreakingContext<'f, '_, '_, '_>, - ) -> Result, InlineLayoutError> { - let can_break_before = *current_width != FixedL::ZERO; - *current_width += self.padding.current_padding_left; - - if *current_width > ctx.constraints.size.x { - return Ok(BreakOutcome::BreakBefore); - } - - match &mut self.kind { - ShapedItemKind::Text(text) => text.line_break( - &mut self.range, - current_width, - can_break_before, - ctx, - &mut self.padding, - ), - ShapedItemKind::Ruby(_) | ShapedItemKind::Block(_) => { - // TODO: Implement proper ruby line breaking - // It should only allow breaking between distinct base-annotation pairs. - self.accumulate_content_width(current_width, ctx.constraints.size.x); - *current_width += self.padding.current_padding_right; - if *current_width > ctx.constraints.size.x { - Ok(BreakOutcome::BreakBefore) - } else { - Ok(BreakOutcome::None) - } - } - } - } - +impl<'a, 'f, const PHASE: u32> ShapedItem<'a, 'f, PHASE> { + // NOTE: `available_width` is unused if `PHASE == IN_LAYOUT` but cleaning that up would + // require duplicatinug this function. fn accumulate_content_width(&self, result: &mut FixedL, available_width: FixedL) { match &self.kind { ShapedItemKind::Text(text) => { @@ -1522,7 +1518,11 @@ impl<'a, 'f> ShapedItem<'a, 'f> { } } ShapedItemKind::Block(ShapedItemBlock { kind, .. }) => { - *result += kind.width(available_width); + *result += ShapedItemBlockKind::dispatch( + kind, + |partial| partial.width(available_width), + |fragment| fragment.width(), + ) } } } @@ -1541,6 +1541,42 @@ impl<'a, 'f> ShapedItem<'a, 'f> { } } +impl<'a, 'f> ShapedItem<'a, 'f, ITEM_PHASE_IN_LAYOUT> { + fn line_break( + &mut self, + current_width: &mut FixedL, + ctx: &mut BreakingContext<'f, '_, '_, '_>, + ) -> Result, InlineLayoutError> { + let can_break_before = *current_width != FixedL::ZERO; + *current_width += self.padding.current_padding_left; + + if *current_width > ctx.constraints.size.x { + return Ok(BreakOutcome::BreakBefore); + } + + match &mut self.kind { + ShapedItemKind::Text(text) => text.line_break( + &mut self.range, + current_width, + can_break_before, + ctx, + &mut self.padding, + ), + ShapedItemKind::Ruby(_) | ShapedItemKind::Block(_) => { + // TODO: Implement proper ruby line breaking + // It should only allow breaking between distinct base-annotation pairs. + self.accumulate_content_width(current_width, ctx.constraints.size.x); + *current_width += self.padding.current_padding_right; + if *current_width > ctx.constraints.size.x { + Ok(BreakOutcome::BreakBefore) + } else { + Ok(BreakOutcome::None) + } + } + } + } +} + impl<'f> ShapedItemText<'f> { fn break_opportunity_to_range(&self, opportunity: usize) -> Range { let text = self.glyphs.text(); @@ -1662,7 +1698,7 @@ impl<'f> ShapedItemText<'f> { // unsafe because `font_arena` must hold all fonts required by `initial_shaping_result` unsafe fn layout_run_full<'a, 'f>( content: &'a InlineContent, - initial_shaping_result: InitialShapingResult<'a, 'f>, + initial_shaping_result: InitialShapingResult<'a, 'f, ITEM_PHASE_INITIAL>, span_state: Vec>, lctx: &mut LayoutContext, constraints: &LayoutConstraints, @@ -1747,9 +1783,9 @@ unsafe fn layout_run_full<'a, 'f>( } fn reorder<'a>( - shaped: &mut [ShapedItem<'a, '_>], + shaped: &mut [LayoutShapedItem<'a, '_>], bidi: &unicode_bidi::BidiInfo, - mut push_item: impl FnMut(&mut ShapedItem<'a, '_>) -> Result<(), InlineLayoutError>, + mut push_item: impl FnMut(&mut LayoutShapedItem<'a, '_>) -> Result<(), InlineLayoutError>, ) -> Result<(), InlineLayoutError> { let line_range = { if let (Some(first), Some(last)) = (shaped.first(), shaped.last()) { @@ -1786,7 +1822,7 @@ unsafe fn layout_run_full<'a, 'f>( for range in visual_runs { let mut push_item_in_range = - |item: &mut ShapedItem<'a, '_>| -> Result<(), InlineLayoutError> { + |item: &mut LayoutShapedItem<'a, '_>| -> Result<(), InlineLayoutError> { if range.start <= item.range.start && range.end >= item.range.end { push_item(item) } else if let ShapedItemKind::Text(text) = &item.kind { @@ -1927,7 +1963,7 @@ unsafe fn layout_run_full<'a, 'f>( } // https://drafts.csswg.org/css-inline/#inline-height - fn process_item(&mut self, item: &ShapedItem, line_height: LineHeight) { + fn process_item(&mut self, item: &LayoutShapedItem, line_height: LineHeight) { match &item.kind { ShapedItemKind::Text(text) => match line_height { LineHeight::Normal => { @@ -1967,8 +2003,7 @@ unsafe fn layout_run_full<'a, 'f>( } } } - ShapedItemKind::Block(ShapedItemBlock { kind, .. }) => { - let fragment = kind.assert_as_fragment(); + ShapedItemKind::Block(ShapedItemBlock { kind: fragment, .. }) => { let ascender = fragment.alignment_baseline; let descender = ascender - fragment.block.fbox.margin_box().height(); self.expand_to(ascender, descender); @@ -2087,7 +2122,7 @@ unsafe fn layout_run_full<'a, 'f>( // the input items" invarant. unsafe fn reorder_and_append( &mut self, - shaped: &mut [ShapedItem<'_, '_>], + shaped: &mut [LayoutShapedItem<'_, '_>], font_arena: util::rc::Rc, bidi: &'a unicode_bidi::BidiInfo<'a>, text_leaf_items: &'a [LeafItemRange<'a>], @@ -2264,13 +2299,16 @@ unsafe fn layout_run_full<'a, 'f>( ref mut kind, span_id, }) => { - let fragment_item = kind.assert_take_fragment(); - let inner_width = fragment_item.block.fbox.size_for_layout().x; + let inner_width = kind.block.fbox.size_for_layout().x; let (fragment, width, y_correction) = self.rebuild_leaf_branch( span_id, inner_width, - self.current_top_y - fragment_item.alignment_baseline, - InlineItemFragment::Block(fragment_item.block).into(), + self.current_top_y - kind.alignment_baseline, + InlineItemFragment::Block(std::mem::replace( + &mut kind.block, + BlockContainerFragment::EMPTY, + )) + .into(), OBJECT_REPLACEMENT_LENGTH, span_state, ); @@ -2288,7 +2326,7 @@ unsafe fn layout_run_full<'a, 'f>( impl<'t, 'f> FragmentBuilder<'t, 'f> { fn split_on_leaves_for_fragmentation( - item: &ShapedItem<'t, 'f>, + item: &LayoutShapedItem<'t, 'f>, leaves: &[LeafItemRange], mut on_leaf: impl FnMut(usize, Range), ) { @@ -2329,7 +2367,7 @@ unsafe fn layout_run_full<'a, 'f>( fn update_line_fragmentation_state_pre( &mut self, - shaped_item: &ShapedItem<'t, 'f>, + shaped_item: &LayoutShapedItem<'t, 'f>, leaves: &[LeafItemRange], ) { Self::split_on_leaves_for_fragmentation(shaped_item, leaves, |span_id, range| { @@ -2359,7 +2397,7 @@ unsafe fn layout_run_full<'a, 'f>( unsafe fn push_line( &mut self, - shaped: &mut [ShapedItem<'t, 'f>], + shaped: &mut [LayoutShapedItem<'t, 'f>], font_arena: util::rc::Rc, ) -> Result<(), InlineLayoutError> { let mut line_width = FixedL::ZERO; @@ -2473,10 +2511,7 @@ unsafe fn layout_run_full<'a, 'f>( bidi, font_feature_events, grapheme_cluster_boundaries, - } = initial_shaping_result; - - // Transition block shaped items into `Fragment` state. - finish_block_layout_in_shaped_items(&mut shaped, lctx, constraints)?; + } = initial_shaping_result.transition_to_layout(lctx, constraints)?; let mut builder = FragmentBuilder { current_y: FixedL::ZERO, @@ -2562,7 +2597,7 @@ pub struct PartialInline<'a> { content: &'a InlineContent, font_arena: util::rc::Rc, span_state: Vec>, - initial_shaping_result: InitialShapingResult<'a, 'static>, + initial_shaping_result: InitialShapingResult<'a, 'static, ITEM_PHASE_INITIAL>, } pub fn shape<'l, 'a, 'b, 'c>(