mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
layout: Shape text only once (#31146)
Shape text during InlineFormattingContext construction rather than doing it twice during fragment tree construction. This is a step on the way toward proper font fallback. This also moves all `TextRun` related code into `text_run.rs` to try to trim down the size of `inline.rs`. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes do not require tests because this should only have performance impacts. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
parent
bb04c97f15
commit
094f7845b1
6 changed files with 289 additions and 268 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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};
|
||||
|
|
213
components/layout_2020/flow/text_run.rs
Normal file
213
components/layout_2020/flow/text_run.rs
Normal file
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue