diff --git a/Cargo.lock b/Cargo.lock index 0a14235a699..1251c1e7de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8067,6 +8067,7 @@ dependencies = [ "encoding_rs", "euclid", "http 1.4.0", + "icu_locid", "indexmap", "ipc-channel", "keyboard-types", diff --git a/components/layout/flow/inline/text_run.rs b/components/layout/flow/inline/text_run.rs index aa02fc12a0a..86cdb16b55e 100644 --- a/components/layout/flow/inline/text_run.rs +++ b/components/layout/flow/inline/text_run.rs @@ -54,13 +54,50 @@ enum SegmentStartSoftWrapPolicy { FollowLinebreaker, } -/// A data structure which contains font and language information about a run of text or -/// glyphs processed during inline layout. +/// A data structure which contains information used when shaping a [`TextRunSegment`]. #[derive(Clone, Debug, MallocSizeOf)] pub(crate) struct FontAndScriptInfo { + /// The font used when shaping a [`TextRunSegment`]. pub font: FontRef, + /// The script used when shaping a [`TextRunSegment`]. pub script: Script, + /// The BiDi [`Level`] used when shaping a [`TextRunSegment`]. pub bidi_level: Level, + /// The [`Language`] used when shaping a [`TextRunSegment`]. + pub language: Language, + /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property. + /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null. + /// + /// Letter spacing is not applied to all characters. Use [Self::letter_spacing_for_character] to + /// determine the amount of spacing to apply. + pub letter_spacing: Option, + /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property. + pub word_spacing: Option, + /// The [`TextRendering`] value from the original style. + pub text_rendering: TextRendering, +} + +impl From<&FontAndScriptInfo> for ShapingOptions { + fn from(info: &FontAndScriptInfo) -> Self { + let mut flags = ShapingFlags::empty(); + if info.bidi_level.is_rtl() { + flags.insert(ShapingFlags::RTL_FLAG); + } + if info.letter_spacing.is_some() { + flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); + }; + if info.text_rendering == TextRendering::Optimizespeed { + flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); + flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) + } + Self { + letter_spacing: info.letter_spacing, + word_spacing: info.word_spacing, + script: info.script, + language: info.language, + flags, + } + } } #[derive(Debug, MallocSizeOf)] @@ -103,18 +140,26 @@ impl TextRunSegment { /// Update this segment if the Font and Script are compatible. The update will only /// ever make the Script specific. Returns true if the new Font and Script are /// compatible with this segment or false otherwise. - fn update_if_compatible(&mut self, info: &FontAndScriptInfo) -> bool { - if self.info.bidi_level != info.bidi_level || !Arc::ptr_eq(&self.info.font, &info.font) { + fn update_if_compatible( + &mut self, + new_font: &FontRef, + new_script: Script, + new_bidi_level: Level, + ) -> bool { + if self.info.bidi_level != new_bidi_level || !Arc::ptr_eq(&self.info.font, new_font) { return false; } fn is_specific(script: Script) -> bool { script != Script::Common && script != Script::Inherited } - if !is_specific(self.info.script) && is_specific(info.script) { - self.info = Arc::new(info.clone()); + if !is_specific(self.info.script) && is_specific(new_script) { + self.info = Arc::new(FontAndScriptInfo { + script: new_script, + ..(*self.info).clone() + }); } - info.script == self.info.script || !is_specific(info.script) + new_script == self.info.script || !is_specific(new_script) } fn layout_into_line_items( @@ -186,8 +231,9 @@ impl TextRunSegment { parent_style: &ComputedValues, formatting_context_text: &str, linebreaker: &mut LineBreaker, - shaping_options: &ShapingOptions, ) { + let options: ShapingOptions = (&*self.info).into(); + // Gather the linebreaks that apply to this segment from the inline formatting context's collection // of line breaks. Also add a simulated break at the end of the segment in order to ensure the final // piece of text is processed. @@ -206,13 +252,12 @@ impl TextRunSegment { let mut last_slice = self.range.start..self.range.start; for break_index in linebreak_iter { + let mut options = options; if *break_index == self.range.start { self.break_at_start = true; continue; } - let mut options = *shaping_options; - // Extend the slice to the next UAX#14 line break opportunity. let mut slice = last_slice.end..*break_index; let word = &formatting_context_text[slice.clone()]; @@ -376,87 +421,15 @@ impl TextRun { bidi_info: &BidiInfo, ) { let parent_style = self.inline_styles.style.borrow().clone(); - let inherited_text_style = parent_style.get_inherited_text().clone(); - let letter_spacing = inherited_text_style - .letter_spacing - .0 - .resolve(parent_style.clone_font().font_size.computed_size()); - let letter_spacing = if letter_spacing.px() != 0. { - Some(app_units::Au::from(letter_spacing)) - } else { - None - }; - let language = parent_style - .get_font() - ._x_lang - .0 - .parse() - .unwrap_or(Language::UND); - - let mut flags = ShapingFlags::empty(); - if inherited_text_style.text_rendering == TextRendering::Optimizespeed { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) + let mut segments = self.segment_text_by_font( + layout_context, + formatting_context_text, + bidi_info, + &parent_style, + ); + for segment in segments.iter_mut() { + segment.shape_text(&parent_style, formatting_context_text, linebreaker); } - let word_spacing = inherited_text_style.word_spacing.to_length().map(Au::from); - - let segments = self - .segment_text_by_font( - layout_context, - formatting_context_text, - bidi_info, - &parent_style, - ) - .into_iter() - .map(|mut segment| { - let word_spacing = word_spacing.unwrap_or_else(|| { - let space_width = segment - .info - .font - .glyph_index(' ') - .map(|glyph_id| segment.info.font.glyph_h_advance(glyph_id)) - .unwrap_or(LAST_RESORT_GLYPH_ADVANCE); - inherited_text_style - .word_spacing - .to_used_value(Au::from_f64_px(space_width)) - }); - - let mut flags = flags; - if segment.info.bidi_level.is_rtl() { - flags.insert(ShapingFlags::RTL_FLAG); - } - - // From https://www.w3.org/TR/css-text-3/#cursive-script: - // Cursive scripts do not admit gaps between their letters for either - // justification or letter-spacing. - let letter_spacing = if is_cursive_script(segment.info.script) { - None - } else { - letter_spacing - }; - if letter_spacing.is_some() { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - }; - - let shaping_options = ShapingOptions { - letter_spacing, - word_spacing: Some(word_spacing), - script: segment.info.script, - language, - flags, - }; - - segment.shape_text( - &parent_style, - formatting_context_text, - linebreaker, - &shaping_options, - ); - - segment - }) - .collect(); - let _ = std::mem::replace(&mut self.shaped_text, segments); } @@ -476,15 +449,42 @@ impl TextRun { let mut current: Option = None; let mut results = Vec::new(); - let lang = parent_style.get_font()._x_lang.clone(); + let x_lang = parent_style.get_font()._x_lang.clone(); + let language = x_lang.0.parse().unwrap_or(Language::UND); let text_run_text = &formatting_context_text[self.text_range.clone()]; let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars()); + let parent_style = self.inline_styles.style.borrow().clone(); + let inherited_text_style = parent_style.get_inherited_text().clone(); + let letter_spacing = inherited_text_style + .letter_spacing + .0 + .resolve(parent_style.clone_font().font_size.computed_size()); + let letter_spacing = if letter_spacing.px() != 0. { + Some(app_units::Au::from(letter_spacing)) + } else { + None + }; + let text_rendering = inherited_text_style.text_rendering; + let word_spacing = inherited_text_style.word_spacing.to_length().map(Au::from); + // The next current character index within the entire inline formatting context's text. let mut next_character_index = self.character_range.start; // The next bytes index of the charcter within the entire inline formatting context's text. let mut next_byte_index = self.text_range.start; + let resolve_word_spacing_for_font = |font: &FontRef| { + word_spacing.unwrap_or_else(|| { + let space_width = font + .glyph_index(' ') + .map(|glyph_id| font.glyph_h_advance(glyph_id)) + .unwrap_or(LAST_RESORT_GLYPH_ADVANCE); + inherited_text_style + .word_spacing + .to_used_value(Au::from_f64_px(space_width)) + }) + }; + for (character, next_character) in char_iterator { let current_character_index = next_character_index; next_character_index += 1; @@ -500,24 +500,41 @@ impl TextRun { &layout_context.font_context, character, next_character, - lang.clone(), + x_lang.clone(), ) else { continue; }; - let info = FontAndScriptInfo { - font, - script: Script::from(character), - bidi_level: bidi_info.levels[current_byte_index], - }; + let script = Script::from(character); + let bidi_level = bidi_info.levels[current_byte_index]; // If the existing segment is compatible with the character, keep going. if let Some(current) = current.as_mut() { - if current.update_if_compatible(&info) { + if current.update_if_compatible(&font, script, bidi_level) { continue; } } + // From https://www.w3.org/TR/css-text-3/#cursive-script: + // Cursive scripts do not admit gaps between their letters for either + // justification or letter-spacing. + let letter_spacing = if is_cursive_script(script) { + None + } else { + letter_spacing + }; + + let word_spacing = Some(resolve_word_spacing_for_font(&font)); + let info = FontAndScriptInfo { + font, + script, + bidi_level, + language, + word_spacing, + letter_spacing, + text_rendering, + }; + // Add the new segment and finish the existing one, if we had one. If the first // characters in the run were control characters we may be creating the first // segment in the middle of the run (ie the start should be the start of this @@ -539,11 +556,16 @@ impl TextRun { // of those cases, just use the first font. if current.is_none() { current = font_group.first(&layout_context.font_context).map(|font| { + let word_spacing = Some(resolve_word_spacing_for_font(&font)); TextRunSegment::new( Arc::new(FontAndScriptInfo { font, script: Script::Common, + language, bidi_level: Level::ltr(), + letter_spacing, + word_spacing, + text_rendering, }), self.text_range.start, self.character_range.start, diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 9f68cb7222a..e06c0ae53c6 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -22,6 +22,7 @@ data-url = { workspace = true } encoding_rs = '0.8' euclid = { workspace = true } http = { workspace = true } +icu_locid = { workspace = true } indexmap = { workspace = true } ipc-channel = { workspace = true } keyboard-types = { workspace = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index efa36789e18..11434dcb7b3 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -1152,6 +1152,7 @@ malloc_size_of_is_0!(content_security_policy::sandboxing_directive::SandboxingFl malloc_size_of_is_0!(encoding_rs::Decoder); malloc_size_of_is_0!(http::StatusCode); malloc_size_of_is_0!(http::Method); +malloc_size_of_is_0!(icu_locid::subtags::Language); malloc_size_of_is_0!(keyboard_types::Code); malloc_size_of_is_0!(keyboard_types::Modifiers); malloc_size_of_is_0!(mime::Mime); @@ -1309,6 +1310,7 @@ malloc_size_of_is_stylo_malloc_size_of!(style::attr::AttrValue); malloc_size_of_is_stylo_malloc_size_of!(style::color::AbsoluteColor); malloc_size_of_is_stylo_malloc_size_of!(style::computed_values::font_variant_caps::T); malloc_size_of_is_stylo_malloc_size_of!(style::computed_values::text_decoration_style::T); +malloc_size_of_is_stylo_malloc_size_of!(style::computed_values::text_rendering::T); malloc_size_of_is_stylo_malloc_size_of!(style::dom::OpaqueNode); malloc_size_of_is_stylo_malloc_size_of!(style::invalidation::element::restyle_hints::RestyleHint); malloc_size_of_is_stylo_malloc_size_of!(style::logical_geometry::WritingMode);