diff options
-rw-r--r-- | components/layout_2020/flexbox/construct.rs | 41 | ||||
-rw-r--r-- | components/layout_2020/flow/construct.rs | 32 | ||||
-rw-r--r-- | components/layout_2020/flow/inline.rs | 255 | ||||
-rw-r--r-- | components/layout_2020/flow/mod.rs | 1 | ||||
-rw-r--r-- | components/layout_2020/flow/text_run.rs | 213 | ||||
-rw-r--r-- | components/layout_2020/formatting_contexts.rs | 15 |
6 files changed, 289 insertions, 268 deletions
diff --git a/components/layout_2020/flexbox/construct.rs b/components/layout_2020/flexbox/construct.rs index 3f4f7aab734..22215651395 100644 --- a/components/layout_2020/flexbox/construct.rs +++ b/components/layout_2020/flexbox/construct.rs @@ -12,7 +12,11 @@ use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom::{BoxSlot, LayoutBox, NodeExt}; use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler}; -use crate::formatting_contexts::IndependentFormattingContext; +use crate::flow::BlockFormattingContext; +use crate::formatting_contexts::{ + IndependentFormattingContext, NonReplacedFormattingContext, + NonReplacedFormattingContextContents, +}; use crate::positioned::AbsolutelyPositionedBox; use crate::style_ext::DisplayGeneratingBox; @@ -143,20 +147,29 @@ where let mut children = std::mem::take(&mut self.jobs) .into_par_iter() .map(|job| match job { - FlexLevelJob::TextRuns(runs) => ArcRefCell::new(FlexLevelBox::FlexItem( - IndependentFormattingContext::construct_for_text_runs( - &self - .info - .new_replacing_style(anonymous_style.clone().unwrap()), - runs.into_iter().map(|run| crate::flow::inline::TextRun { - base_fragment_info: (&run.info).into(), - text: run.text.into(), - parent_style: run.info.style, - has_uncollapsible_content: false, - }), + FlexLevelJob::TextRuns(runs) => ArcRefCell::new(FlexLevelBox::FlexItem({ + let runs = runs.into_iter().map(|run| crate::flow::text_run::TextRun { + base_fragment_info: (&run.info).into(), + text: run.text.into(), + parent_style: run.info.style, + has_uncollapsible_content: false, + shaped_text: None, + }); + let bfc = BlockFormattingContext::construct_for_text_runs( + runs, + self.context, self.text_decoration_line, - ), - )), + ); + let info = &self + .info + .new_replacing_style(anonymous_style.clone().unwrap()); + IndependentFormattingContext::NonReplaced(NonReplacedFormattingContext { + base_fragment_info: info.into(), + style: info.style.clone(), + content_sizes: None, + contents: NonReplacedFormattingContextContents::Flow(bfc), + }) + })), FlexLevelJob::Element { info, display, diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index 4c7b712fade..3155ff6e878 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -19,7 +19,8 @@ use crate::context::LayoutContext; use crate::dom::{BoxSlot, LayoutBox, NodeExt}; use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler}; use crate::flow::float::FloatBox; -use crate::flow::inline::{InlineBox, InlineFormattingContext, InlineLevelBox, TextRun}; +use crate::flow::inline::{InlineBox, InlineFormattingContext, InlineLevelBox}; +use crate::flow::text_run::TextRun; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::formatting_contexts::IndependentFormattingContext; use crate::positioned::AbsolutelyPositionedBox; @@ -56,6 +57,7 @@ impl BlockFormattingContext { pub fn construct_for_text_runs<'dom>( runs: impl Iterator<Item = TextRun>, + layout_context: &LayoutContext, text_decoration_line: TextDecorationLine, ) -> Self { // FIXME: do white space collapsing @@ -70,10 +72,8 @@ impl BlockFormattingContext { contains_floats: false, ends_with_whitespace: false, }; - let contents = BlockContainer::InlineFormattingContext(ifc); - Self { - contents, + contents: BlockContainer::construct_inline_formatting_context(layout_context, ifc), contains_floats: false, } } @@ -216,6 +216,16 @@ impl BlockContainer { contents.traverse(context, info, &mut builder); builder.finish() } + + pub(super) fn construct_inline_formatting_context( + layout_context: &LayoutContext, + mut ifc: InlineFormattingContext, + ) -> Self { + // TODO(mrobinson): Perhaps it would be better to iteratively break and shape the contents + // of the IFC, and not wait until it is completely built. + ifc.break_and_shape_text(layout_context); + BlockContainer::InlineFormattingContext(ifc) + } } impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node> @@ -251,7 +261,8 @@ where if !self.ongoing_inline_formatting_context.is_empty() { if self.block_level_boxes.is_empty() { - return BlockContainer::InlineFormattingContext( + return BlockContainer::construct_inline_formatting_context( + self.context, self.ongoing_inline_formatting_context, ); } @@ -427,6 +438,7 @@ where parent_style: Arc::clone(&info.style), text: output, has_uncollapsible_content, + shaped_text: None, }))); } } @@ -811,15 +823,15 @@ where /* ends_with_whitespace */ false, ); std::mem::swap(&mut self.ongoing_inline_formatting_context, &mut ifc); - let kind = BlockLevelCreator::SameFormattingContextBlock( - IntermediateBlockContainer::InlineFormattingContext(ifc), - ); + let info = self.info.new_replacing_style(anonymous_style.clone()); self.block_level_boxes.push(BlockLevelJob { info, // FIXME(nox): We should be storing this somewhere. box_slot: BoxSlot::dummy(), - kind, + kind: BlockLevelCreator::SameFormattingContextBlock( + IntermediateBlockContainer::InlineFormattingContext(ifc), + ), }); } @@ -919,7 +931,7 @@ impl IntermediateBlockContainer { is_list_item, ), IntermediateBlockContainer::InlineFormattingContext(ifc) => { - BlockContainer::InlineFormattingContext(ifc) + BlockContainer::construct_inline_formatting_context(context, ifc) }, } } diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index 03861743091..0fb6d905bc0 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -8,7 +8,6 @@ use std::mem; use app_units::Au; use gfx::font::FontMetrics; use gfx::text::glyph::GlyphStore; -use gfx::text::text_run::GlyphRun; use log::warn; use serde::Serialize; use servo_arc::Arc; @@ -23,13 +22,13 @@ use style::values::specified::text::{TextAlignKeyword, TextDecorationLine}; use style::values::specified::{TextAlignLast, TextJustify}; use style::Zero; use webrender_api::FontInstanceKey; -use xi_unicode::{linebreak_property, LineBreakLeafIter}; use super::float::PlacementAmongFloats; use super::line::{ layout_line_items, AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, InlineBoxLineItem, LineItem, LineItemLayoutState, LineMetrics, TextRunLineItem, }; +use super::text_run::TextRun; use super::CollapsibleWithParentStartMargin; use crate::cell::ArcRefCell; use crate::context::LayoutContext; @@ -46,12 +45,6 @@ use crate::sizing::ContentSizes; use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin}; use crate::ContainingBlock; -// These constants are the xi-unicode line breaking classes that are defined in -// `table.rs`. Unfortunately, they are only identified by number. -const XI_LINE_BREAKING_CLASS_GL: u8 = 12; -const XI_LINE_BREAKING_CLASS_WJ: u8 = 30; -const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 40; - // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; @@ -93,16 +86,6 @@ pub(crate) struct InlineBox { pub children: Vec<ArcRefCell<InlineLevelBox>>, } -/// <https://www.w3.org/TR/css-display-3/#css-text-run> -#[derive(Debug, Serialize)] -pub(crate) struct TextRun { - pub base_fragment_info: BaseFragmentInfo, - #[serde(skip_serializing)] - pub parent_style: Arc<ComputedValues>, - pub text: String, - pub has_uncollapsible_content: bool, -} - /// Information about the current line under construction for a particular /// [`InlineFormattingContextState`]. This tracks position and size information while /// [`LineItem`]s are collected and is used as input when those [`LineItem`]s are @@ -517,7 +500,7 @@ struct InlineBoxContainerState { is_last_fragment: bool, } -struct InlineFormattingContextState<'a, 'b> { +pub(super) struct InlineFormattingContextState<'a, 'b> { positioning_context: &'a mut PositioningContext, containing_block: &'b ContainingBlock<'b>, sequential_layout_state: Option<&'a mut SequentialLayoutState>, @@ -550,9 +533,6 @@ struct InlineFormattingContextState<'a, 'b> { /// Information about the unbreakable line segment currently being laid out into [`LineItem`]s. current_line_segment: UnbreakableSegmentUnderConstruction, - /// The line breaking state for this inline formatting context. - linebreaker: Option<LineBreakLeafIter>, - /// After a forced line break (for instance from a `<br>` element) we wait to actually /// break the line until seeing more content. This allows ongoing inline boxes to finish, /// since in the case where they have no more content they should not be on the next @@ -576,13 +556,13 @@ struct InlineFormattingContextState<'a, 'b> { /// Whether or not a soft wrap opportunity is queued. Soft wrap opportunities are /// queued after replaced content and they are processed when the next text content /// is encountered. - have_deferred_soft_wrap_opportunity: bool, + pub have_deferred_soft_wrap_opportunity: bool, /// Whether or not a soft wrap opportunity should be prevented before the next atomic /// element encountered in the inline formatting context. See /// `char_prevents_soft_wrap_opportunity_when_before_or_after_atomic` for more /// details. - prevent_soft_wrap_opportunity_before_next_atomic: bool, + pub prevent_soft_wrap_opportunity_before_next_atomic: bool, /// Whether or not this InlineFormattingContext has processed any in flow content at all. had_inflow_content: bool, @@ -1100,7 +1080,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { inline_would_overflow } - fn defer_forced_line_break(&mut self) { + pub(super) fn defer_forced_line_break(&mut self) { // If this hard line break happens in the middle of an unbreakable segment, there are two // scenarios: // 1. The current portion of the unbreakable segment fits on the current line in which @@ -1124,7 +1104,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { self.had_inflow_content = true; } - fn possibly_flush_deferred_forced_line_break(&mut self) { + pub(super) fn possibly_flush_deferred_forced_line_break(&mut self) { if !self.linebreak_before_new_content { return; } @@ -1139,7 +1119,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { .push_line_item(line_item, self.inline_box_state_stack.len()); } - fn push_glyph_store_to_unbreakable_segment( + pub(super) fn push_glyph_store_to_unbreakable_segment( &mut self, glyph_store: std::sync::Arc<GlyphStore>, base_fragment_info: BaseFragmentInfo, @@ -1242,7 +1222,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> { /// Process a soft wrap opportunity. This will either commit the current unbreakble /// segment to the current line, if it fits within the containing block and float /// placement boundaries, or do a line break and then commit the segment. - fn process_soft_wrap_opportunity(&mut self) { + pub(super) fn process_soft_wrap_opportunity(&mut self) { if self.current_line_segment.line_items.is_empty() { return; } @@ -1466,7 +1446,6 @@ impl InlineFormattingContext { self.text_decoration_line, inline_container_needs_strut(style, layout_context, None), ), - linebreaker: None, inline_box_state_stack: Vec::new(), current_line_segment: UnbreakableSegmentUnderConstruction::new(), linebreak_before_new_content: false, @@ -1494,9 +1473,7 @@ impl InlineFormattingContext { InlineLevelBox::InlineBox(ref inline_box) => { ifc.start_inline_box(inline_box); }, - InlineLevelBox::TextRun(ref run) => { - run.layout_into_line_items(layout_context, &mut ifc) - }, + InlineLevelBox::TextRun(ref run) => run.layout_into_line_items(&mut ifc), InlineLevelBox::Atomic(ref mut atomic_formatting_context) => { atomic_formatting_context.layout_into_line_items(layout_context, &mut ifc); }, @@ -1555,6 +1532,18 @@ impl InlineFormattingContext { inline_level_boxes_are_empty(&self.inline_level_boxes) } + + /// Break and shape text of this InlineFormattingContext's TextRun's, which requires doing + /// all font matching and FontMetrics collection. + pub(crate) fn break_and_shape_text(&mut self, layout_context: &LayoutContext) { + let mut linebreaker = None; + self.foreach(|iter_item| match iter_item { + InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(ref mut text_run)) => { + text_run.break_and_shape(layout_context, &mut linebreaker); + }, + _ => {}, + }); + } } impl InlineContainerState { @@ -1958,170 +1947,6 @@ impl IndependentFormattingContext { } } -struct BreakAndShapeResult { - font_metrics: FontMetrics, - font_key: FontInstanceKey, - runs: Vec<GlyphRun>, - break_at_start: bool, -} - -impl TextRun { - fn break_and_shape( - &self, - layout_context: &LayoutContext, - linebreaker: &mut Option<LineBreakLeafIter>, - ) -> Result<BreakAndShapeResult, &'static str> { - use gfx::font::ShapingFlags; - use style::computed_values::text_rendering::T as TextRendering; - use style::computed_values::word_break::T as WordBreak; - - let font_style = self.parent_style.clone_font(); - let inherited_text_style = self.parent_style.get_inherited_text(); - let letter_spacing = if inherited_text_style.letter_spacing.0.px() != 0. { - Some(app_units::Au::from(inherited_text_style.letter_spacing.0)) - } else { - None - }; - - let mut flags = ShapingFlags::empty(); - if letter_spacing.is_some() { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - } - if inherited_text_style.text_rendering == TextRendering::Optimizespeed { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) - } - if inherited_text_style.word_break == WordBreak::KeepAll { - flags.insert(ShapingFlags::KEEP_ALL_FLAG); - } - - crate::context::with_thread_local_font_context(layout_context, |font_context| { - let font_group = font_context.font_group(font_style); - let font = match font_group.borrow_mut().first(font_context) { - Some(font) => font, - None => return Err("Could not find find for TextRun."), - }; - let mut font = font.borrow_mut(); - - let word_spacing = &inherited_text_style.word_spacing; - let word_spacing = word_spacing - .to_length() - .map(|l| l.into()) - .unwrap_or_else(|| { - let space_width = font - .glyph_index(' ') - .map(|glyph_id| font.glyph_h_advance(glyph_id)) - .unwrap_or(gfx::font::LAST_RESORT_GLYPH_ADVANCE); - word_spacing.to_used_value(Au::from_f64_px(space_width)) - }); - - let shaping_options = gfx::font::ShapingOptions { - letter_spacing, - word_spacing, - script: unicode_script::Script::Common, - flags, - }; - - let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape( - &mut font, - &self.text, - &shaping_options, - linebreaker, - ); - - Ok(BreakAndShapeResult { - font_metrics: font.metrics.clone(), - font_key: font.font_key, - runs, - break_at_start, - }) - }) - } - - fn glyph_run_is_whitespace_ending_with_preserved_newline(&self, run: &GlyphRun) -> bool { - if !run.glyph_store.is_whitespace() { - return false; - } - if !self - .parent_style - .get_inherited_text() - .white_space - .preserve_newlines() - { - return false; - } - - let last_byte = self.text.as_bytes().get(run.range.end().to_usize() - 1); - last_byte == Some(&b'\n') - } - - fn layout_into_line_items( - &self, - layout_context: &LayoutContext, - ifc: &mut InlineFormattingContextState, - ) { - let result = self.break_and_shape(layout_context, &mut ifc.linebreaker); - let BreakAndShapeResult { - font_metrics, - font_key, - runs, - break_at_start, - } = match result { - Ok(result) => result, - Err(string) => { - warn!("Could not render TextRun: {string}"); - return; - }, - }; - - // We either have a soft wrap opportunity if specified by the breaker or if we are - // following replaced content. - let have_deferred_soft_wrap_opportunity = - mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false); - let mut break_at_start = break_at_start || have_deferred_soft_wrap_opportunity; - - if have_deferred_soft_wrap_opportunity { - if let Some(first_character) = self.text.chars().nth(0) { - break_at_start = break_at_start && - !char_prevents_soft_wrap_opportunity_when_before_or_after_atomic( - first_character, - ) - } - } - - if let Some(last_character) = self.text.chars().last() { - ifc.prevent_soft_wrap_opportunity_before_next_atomic = - char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(last_character); - } - - for (run_index, run) in runs.into_iter().enumerate() { - ifc.possibly_flush_deferred_forced_line_break(); - - // If this whitespace forces a line break, queue up a hard line break the next time we - // see any content. We don't line break immediately, because we'd like to finish processing - // any ongoing inline boxes before ending the line. - if self.glyph_run_is_whitespace_ending_with_preserved_newline(&run) { - ifc.defer_forced_line_break(); - continue; - } - - // Break before each unbrekable run in this TextRun, except the first unless the - // linebreaker was set to break before the first run. - if run_index != 0 || break_at_start { - ifc.process_soft_wrap_opportunity(); - } - - ifc.push_glyph_store_to_unbreakable_segment( - run.glyph_store, - self.base_fragment_info, - &self.parent_style, - &font_metrics, - font_key, - ); - } - } -} - impl FloatBox { fn layout_into_line_items( &mut self, @@ -2177,26 +2002,6 @@ fn font_metrics_from_style(layout_context: &LayoutContext, style: &ComputedValue }) } -/// comes before or after an atomic inline element. -/// -/// From <https://www.w3.org/TR/css-text-3/#line-break-details>: -/// -/// > For Web-compatibility there is a soft wrap opportunity before and after each -/// > replaced element or other atomic inline, even when adjacent to a character that -/// > would normally suppress them, including U+00A0 NO-BREAK SPACE. However, with -/// > the exception of U+00A0 NO-BREAK SPACE, there must be no soft wrap opportunity -/// > between atomic inlines and adjacent characters belonging to the Unicode GL, WJ, -/// > or ZWJ line breaking classes. -fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: char) -> bool { - if character == '\u{00A0}' { - return false; - } - let class = linebreak_property(character); - class == XI_LINE_BREAKING_CLASS_GL || - class == XI_LINE_BREAKING_CLASS_WJ || - class == XI_LINE_BREAKING_CLASS_ZWJ -} - fn is_baseline_relative(vertical_align: GenericVerticalAlign<LengthPercentage>) -> bool { match vertical_align { GenericVerticalAlign::Keyword(VerticalAlignKeyword::Top) | @@ -2259,8 +2064,6 @@ struct ContentSizesComputation<'a> { pending_whitespace: Length, /// Whether or not this IFC has seen any non-whitespace content. had_non_whitespace_content_yet: bool, - /// The global linebreaking state. - linebreaker: Option<LineBreakLeafIter>, /// Stack of ending padding, margin, and border to add to the length /// when an inline box finishes. ending_inline_pbm_stack: Vec<Length>, @@ -2305,20 +2108,15 @@ impl<'a> ContentSizesComputation<'a> { self.add_length(length); }, InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(text_run)) => { - let result = text_run.break_and_shape(self.layout_context, &mut self.linebreaker); - let BreakAndShapeResult { - runs, - break_at_start, - .. - } = match result { - Ok(result) => result, - Err(_) => return, + let result = match text_run.shaped_text { + Some(ref result) => result, + None => return, }; - if break_at_start { + if result.break_at_start { self.line_break_opportunity() } - for run in runs.iter() { + for run in result.runs.iter() { let advance = Length::from(run.glyph_store.total_advance()); if !run.glyph_store.is_whitespace() { @@ -2398,7 +2196,6 @@ impl<'a> ContentSizesComputation<'a> { current_line: ContentSizes::zero(), pending_whitespace: Length::zero(), had_non_whitespace_content_yet: false, - linebreaker: None, ending_inline_pbm_stack: Vec::new(), } .traverse(inline_formatting_context) diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index eb16f496e8f..d0096742174 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -39,6 +39,7 @@ pub mod float; pub mod inline; mod line; mod root; +pub mod text_run; pub(crate) use construct::BlockContainerBuilder; pub use root::{BoxTree, CanvasBackground}; diff --git a/components/layout_2020/flow/text_run.rs b/components/layout_2020/flow/text_run.rs new file mode 100644 index 00000000000..77bf83bf58d --- /dev/null +++ b/components/layout_2020/flow/text_run.rs @@ -0,0 +1,213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::mem; + +use app_units::Au; +use gfx::font::FontMetrics; +use gfx::text::text_run::GlyphRun; +use serde::Serialize; +use servo_arc::Arc; +use style::properties::ComputedValues; +use webrender_api::FontInstanceKey; +use xi_unicode::{linebreak_property, LineBreakLeafIter}; + +use super::inline::InlineFormattingContextState; +use crate::context::LayoutContext; +use crate::fragment_tree::BaseFragmentInfo; + +// These constants are the xi-unicode line breaking classes that are defined in +// `table.rs`. Unfortunately, they are only identified by number. +const XI_LINE_BREAKING_CLASS_GL: u8 = 12; +const XI_LINE_BREAKING_CLASS_WJ: u8 = 30; +const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 40; + +/// https://www.w3.org/TR/css-display-3/#css-text-run +#[derive(Debug, Serialize)] +pub(crate) struct TextRun { + pub base_fragment_info: BaseFragmentInfo, + #[serde(skip_serializing)] + pub parent_style: Arc<ComputedValues>, + pub text: String, + pub has_uncollapsible_content: bool, + pub shaped_text: Option<BreakAndShapeResult>, +} + +#[derive(Debug, Serialize)] +pub(crate) struct BreakAndShapeResult { + pub font_metrics: FontMetrics, + pub font_key: FontInstanceKey, + pub runs: Vec<GlyphRun>, + pub break_at_start: bool, +} + +impl TextRun { + pub(super) fn break_and_shape( + &mut self, + layout_context: &LayoutContext, + linebreaker: &mut Option<LineBreakLeafIter>, + ) { + use gfx::font::ShapingFlags; + use style::computed_values::text_rendering::T as TextRendering; + use style::computed_values::word_break::T as WordBreak; + + let font_style = self.parent_style.clone_font(); + let inherited_text_style = self.parent_style.get_inherited_text(); + let letter_spacing = if inherited_text_style.letter_spacing.0.px() != 0. { + Some(app_units::Au::from(inherited_text_style.letter_spacing.0)) + } else { + None + }; + + let mut flags = ShapingFlags::empty(); + if letter_spacing.is_some() { + flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); + } + if inherited_text_style.text_rendering == TextRendering::Optimizespeed { + flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); + flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) + } + if inherited_text_style.word_break == WordBreak::KeepAll { + flags.insert(ShapingFlags::KEEP_ALL_FLAG); + } + + self.shaped_text = + crate::context::with_thread_local_font_context(layout_context, |font_context| { + let font_group = font_context.font_group(font_style); + let font = match font_group.borrow_mut().first(font_context) { + Some(font) => font, + None => return Err("Could not find find for TextRun."), + }; + let mut font = font.borrow_mut(); + + let word_spacing = &inherited_text_style.word_spacing; + let word_spacing = + word_spacing + .to_length() + .map(|l| l.into()) + .unwrap_or_else(|| { + let space_width = font + .glyph_index(' ') + .map(|glyph_id| font.glyph_h_advance(glyph_id)) + .unwrap_or(gfx::font::LAST_RESORT_GLYPH_ADVANCE); + word_spacing.to_used_value(Au::from_f64_px(space_width)) + }); + + let shaping_options = gfx::font::ShapingOptions { + letter_spacing, + word_spacing, + script: unicode_script::Script::Common, + flags, + }; + + let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape( + &mut font, + &self.text, + &shaping_options, + linebreaker, + ); + + Ok(BreakAndShapeResult { + font_metrics: font.metrics.clone(), + font_key: font.font_key, + runs, + break_at_start, + }) + }) + .ok(); + } + + pub(super) fn glyph_run_is_whitespace_ending_with_preserved_newline( + &self, + run: &GlyphRun, + ) -> bool { + if !run.glyph_store.is_whitespace() { + return false; + } + if !self + .parent_style + .get_inherited_text() + .white_space + .preserve_newlines() + { + return false; + } + + let last_byte = self.text.as_bytes().get(run.range.end().to_usize() - 1); + last_byte == Some(&b'\n') + } + + pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextState) { + let broken = match self.shaped_text.as_ref() { + Some(broken) => broken, + None => return, + }; + + // We either have a soft wrap opportunity if specified by the breaker or if we are + // following replaced content. + let have_deferred_soft_wrap_opportunity = + mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false); + let mut break_at_start = broken.break_at_start || have_deferred_soft_wrap_opportunity; + + if have_deferred_soft_wrap_opportunity { + if let Some(first_character) = self.text.chars().nth(0) { + break_at_start = break_at_start && + !char_prevents_soft_wrap_opportunity_when_before_or_after_atomic( + first_character, + ) + } + } + + if let Some(last_character) = self.text.chars().last() { + ifc.prevent_soft_wrap_opportunity_before_next_atomic = + char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(last_character); + } + + for (run_index, run) in broken.runs.iter().enumerate() { + ifc.possibly_flush_deferred_forced_line_break(); + + // If this whitespace forces a line break, queue up a hard line break the next time we + // see any content. We don't line break immediately, because we'd like to finish processing + // any ongoing inline boxes before ending the line. + if self.glyph_run_is_whitespace_ending_with_preserved_newline(run) { + ifc.defer_forced_line_break(); + continue; + } + + // Break before each unbrekable run in this TextRun, except the first unless the + // linebreaker was set to break before the first run. + if run_index != 0 || break_at_start { + ifc.process_soft_wrap_opportunity(); + } + + ifc.push_glyph_store_to_unbreakable_segment( + run.glyph_store.clone(), + self.base_fragment_info, + &self.parent_style, + &broken.font_metrics, + broken.font_key, + ); + } + } +} + +/// comes before or after an atomic inline element. +/// +/// From https://www.w3.org/TR/css-text-3/#line-break-details: +/// +/// > For Web-compatibility there is a soft wrap opportunity before and after each +/// > replaced element or other atomic inline, even when adjacent to a character that +/// > would normally suppress them, including U+00A0 NO-BREAK SPACE. However, with +/// > the exception of U+00A0 NO-BREAK SPACE, there must be no soft wrap opportunity +/// > between atomic inlines and adjacent characters belonging to the Unicode GL, WJ, +/// > or ZWJ line breaking classes. +fn char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character: char) -> bool { + if character == '\u{00A0}' { + return false; + } + let class = linebreak_property(character); + class == XI_LINE_BREAKING_CLASS_GL || + class == XI_LINE_BREAKING_CLASS_WJ || + class == XI_LINE_BREAKING_CLASS_ZWJ +} diff --git a/components/layout_2020/formatting_contexts.rs b/components/layout_2020/formatting_contexts.rs index 2d33564f9d2..4eca72d4878 100644 --- a/components/layout_2020/formatting_contexts.rs +++ b/components/layout_2020/formatting_contexts.rs @@ -126,21 +126,6 @@ impl IndependentFormattingContext { } } - pub fn construct_for_text_runs<'dom>( - node_and_style_info: &NodeAndStyleInfo<impl NodeExt<'dom>>, - runs: impl Iterator<Item = crate::flow::inline::TextRun>, - propagated_text_decoration_line: TextDecorationLine, - ) -> Self { - let bfc = - BlockFormattingContext::construct_for_text_runs(runs, propagated_text_decoration_line); - Self::NonReplaced(NonReplacedFormattingContext { - base_fragment_info: node_and_style_info.into(), - style: Arc::clone(&node_and_style_info.style), - content_sizes: None, - contents: NonReplacedFormattingContextContents::Flow(bfc), - }) - } - pub fn style(&self) -> &Arc<ComputedValues> { match self { Self::NonReplaced(inner) => &inner.style, |