aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout_2020/flexbox/construct.rs41
-rw-r--r--components/layout_2020/flow/construct.rs32
-rw-r--r--components/layout_2020/flow/inline.rs255
-rw-r--r--components/layout_2020/flow/mod.rs1
-rw-r--r--components/layout_2020/flow/text_run.rs213
-rw-r--r--components/layout_2020/formatting_contexts.rs15
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,