Skip to content

Commit

Permalink
Move text updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mrobinson committed Oct 19, 2023
1 parent 9ffb423 commit a9ee044
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 84 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions components/layout_2020/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ script_traits = { path = "../script_traits" }
serde = { workspace = true }
serde_json = { workspace = true }
servo_arc = { path = "../servo_arc" }
servo_atoms = { path = "../atoms" }
servo_config = { path = "../config" }
servo_url = { path = "../url" }
style = { path = "../style", features = ["servo"] }
Expand Down
76 changes: 41 additions & 35 deletions components/layout_2020/flow/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,45 +733,51 @@ impl InlineFormattingContext {
add!(last_fragment, inline_end);
},
InlineLevelBox::TextRun(text_run) => {
let BreakAndShapeResult {
runs,
break_at_start,
..
} = text_run
let results = text_run
.break_and_shape(self.layout_context, &mut self.linebreaker);
if break_at_start {
self.line_break_opportunity()
}
for run in &runs {
let advance = Length::from(run.glyph_store.total_advance());

if !run.glyph_store.is_whitespace() {
self.had_non_whitespace_content_yet = true;
self.current_line.min_content += advance;
self.current_line.max_content +=
self.pending_whitespace + advance;
self.pending_whitespace = Length::zero();
} else {
// If this run is a forced line break, we *must* break the line
// and start measuring from the inline origin once more.
if text_run
.glyph_run_is_whitespace_ending_with_preserved_newline(run)
{
for result in results.into_iter() {
let BreakAndShapeResult {
runs,
break_at_start,
..
} = result;

if break_at_start {
self.line_break_opportunity()
}
for run in &runs {
let advance = Length::from(run.glyph_store.total_advance());

if !run.glyph_store.is_whitespace() {
self.had_non_whitespace_content_yet = true;
self.forced_line_break();
self.current_line = ContentSizes::zero();
continue;
}
self.current_line.min_content += advance;
self.current_line.max_content +=
self.pending_whitespace + advance;
self.pending_whitespace = Length::zero();
} else {
// If this run is a forced line break, we *must* break the line
// and start measuring from the inline origin once more.
if text_run
.glyph_run_is_whitespace_ending_with_preserved_newline(
run, 0,
)
{
self.had_non_whitespace_content_yet = true;
self.forced_line_break();
self.current_line = ContentSizes::zero();
continue;
}

// Discard any leading whitespace in the IFC. This will always be trimmed.
if !self.had_non_whitespace_content_yet {
continue;
}
// Discard any leading whitespace in the IFC. This will always be trimmed.
if !self.had_non_whitespace_content_yet {
continue;
}

// Wait to take into account other whitespace until we see more content.
// Whitespace at the end of the IFC will always be trimmed.
self.line_break_opportunity();
self.pending_whitespace += advance;
// Wait to take into account other whitespace until we see more content.
// Whitespace at the end of the IFC will always be trimmed.
self.line_break_opportunity();
self.pending_whitespace += advance;
}
}
}
},
Expand Down
175 changes: 126 additions & 49 deletions components/layout_2020/flow/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

//! Text processing helpers for inline layout.

use std::ops::Range;

use app_units::Au;
use gfx::font::{FontRef, ShapingFlags};
use gfx::text::glyph::GlyphStore;
use gfx::text::text_run::GlyphRun;
use serde::Serialize;
use servo_arc::Arc;
use servo_atoms::Atom;
use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::word_break::T as WordBreak;
use style::properties::ComputedValues;
use style::values::computed::{Length, LineHeight};
use style::Zero;
Expand All @@ -35,18 +41,60 @@ pub(super) struct BreakAndShapeResult {
pub font_key: FontInstanceKey,
pub runs: Vec<GlyphRun>,
pub break_at_start: bool,
pub offset_in_text: usize,
}

impl TextRun {
pub(super) fn break_and_shape(
fn break_and_shape_text_with_font(
&self,
layout_context: &LayoutContext,
linebreaker: &mut Option<LineBreakLeafIter>,
range: Range<usize>,
font: FontRef,
letter_spacing: Option<Au>,
shaping_flags: ShapingFlags,
) -> BreakAndShapeResult {
use gfx::font::ShapingFlags;
use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::word_break::T as WordBreak;
let mut font = font.borrow_mut();
let word_spacing = &self.parent_style.get_inherited_text().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: shaping_flags,
};

let offset_in_text = range.start;
let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape(
&mut font,
&self.text[range],
&shaping_options,
linebreaker,
);

BreakAndShapeResult {
font_metrics: (&font.metrics).into(),
font_key: font.font_key,
runs,
break_at_start,
offset_in_text,
}
}

pub(super) fn break_and_shape(
&self,
layout_context: &LayoutContext,
linebreaker: &mut Option<LineBreakLeafIter>,
) -> Vec<BreakAndShapeResult> {
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. {
Expand All @@ -55,64 +103,75 @@ impl TextRun {
None
};

let mut flags = ShapingFlags::empty();
let mut shaping_flags = ShapingFlags::empty();
if letter_spacing.is_some() {
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
shaping_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)
shaping_flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
shaping_flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
}
if inherited_text_style.word_break == WordBreak::KeepAll {
flags.insert(ShapingFlags::KEEP_ALL_FLAG);
shaping_flags.insert(ShapingFlags::KEEP_ALL_FLAG);
}
let mut results = Vec::new();

crate::context::with_thread_local_font_context(layout_context, |font_context| {
let font_group = font_context.font_group(font_style);
let font = font_group
.borrow_mut()
.first(font_context)
.expect("could not find font");
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 mut current_font = None;
let mut first_byte_index_of_group = 0;

let (runs, break_at_start) = gfx::text::text_run::TextRun::break_and_shape(
&mut font,
&self.text,
&shaping_options,
linebreaker,
);
for (byte_index, character) in self.text.char_indices() {
if character.is_control() {
continue;
}

BreakAndShapeResult {
font_metrics: (&font.metrics).into(),
font_key: font.font_key,
runs,
break_at_start,
let font = font_group
.borrow_mut()
.find_by_codepoint(font_context, character);

fn identifier(font: &Option<FontRef>) -> Option<Atom> {
font.as_ref().map(|f| f.borrow().identifier())
}

if identifier(&font) != identifier(&current_font) {
if byte_index != first_byte_index_of_group {
if let Some(current_font) = current_font {
results.push(self.break_and_shape_text_with_font(
linebreaker,
first_byte_index_of_group..byte_index,
current_font,
letter_spacing,
shaping_flags,
))
}
}

current_font = font;
first_byte_index_of_group = byte_index;
}
}
})

let length = self.text.len();
debug_assert!(length - 1 > first_byte_index_of_group);
if let Some(current_font) = current_font {
results.push(self.break_and_shape_text_with_font(
linebreaker,
first_byte_index_of_group..length,
current_font,
letter_spacing,
shaping_flags,
))
}
});

results
}

pub(super) fn glyph_run_is_whitespace_ending_with_preserved_newline(
&self,
run: &GlyphRun,
offset: usize,
) -> bool {
if !run.glyph_store.is_whitespace() {
return false;
Expand All @@ -126,21 +185,39 @@ impl TextRun {
return false;
}

let last_byte = self.text.as_bytes().get(run.range.end().to_usize() - 1);
let last_byte = self
.text
.as_bytes()
.get(run.range.end().to_usize() - 1 + offset);
last_byte == Some(&b'\n')
}

pub(super) fn layout_into_line_items(
&self,
layout_context: &LayoutContext,
ifc: &mut InlineFormattingContextState,
) {
for result in self
.break_and_shape(layout_context, &mut ifc.linebreaker)
.into_iter()
{
self.layout_break_and_shape_result_into_line_items(layout_context, ifc, result);
}
}

pub(super) fn layout_break_and_shape_result_into_line_items(
&self,
layout_context: &LayoutContext,
ifc: &mut InlineFormattingContextState,
break_and_shape_result: BreakAndShapeResult,
) {
let BreakAndShapeResult {
font_metrics,
font_key,
runs,
break_at_start,
} = self.break_and_shape(layout_context, &mut ifc.linebreaker);
offset_in_text,
} = break_and_shape_result;

let white_space = self.parent_style.get_inherited_text().white_space;
let add_glyphs_to_current_line = |ifc: &mut InlineFormattingContextState,
Expand Down Expand Up @@ -184,7 +261,7 @@ impl TextRun {
}

// If this whitespace forces a line break, finish the line and reset everything.
if self.glyph_run_is_whitespace_ending_with_preserved_newline(run) {
if self.glyph_run_is_whitespace_ending_with_preserved_newline(run, offset_in_text) {
add_glyphs_to_current_line(ifc, glyphs.drain(..).collect(), text_run_inline_size);

// We need to ensure that the appropriate space for a linebox is created even if there
Expand Down

0 comments on commit a9ee044

Please sign in to comment.