use std::mem;
use std::ops::Range;
use app_units::Au;
use base::text::is_bidi_control;
use fonts::{
FontContext, FontRef, GlyphRun, ShapingFlags, ShapingOptions, LAST_RESORT_GLYPH_ADVANCE,
};
use fonts_traits::ByteIndex;
use log::warn;
use range::Range as ServoRange;
use serde::Serialize;
use servo_arc::Arc;
use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::computed_values::word_break::T as WordBreak;
use style::properties::ComputedValues;
use style::str::char_is_whitespace;
use style::values::computed::OverflowWrap;
use unicode_bidi::{BidiInfo, Level};
use unicode_script::Script;
use xi_unicode::linebreak_property;
use super::line_breaker::LineBreaker;
use super::{FontKeyAndMetrics, InlineFormattingContextLayout};
use crate::fragment_tree::BaseFragmentInfo;
pub(crate) const XI_LINE_BREAKING_CLASS_CM: u8 = 9;
pub(crate) const XI_LINE_BREAKING_CLASS_GL: u8 = 12;
pub(crate) const XI_LINE_BREAKING_CLASS_ZW: u8 = 28;
pub(crate) const XI_LINE_BREAKING_CLASS_WJ: u8 = 30;
pub(crate) const XI_LINE_BREAKING_CLASS_ZWJ: u8 = 42;
#[derive(Debug, Serialize)]
pub(crate) struct TextRun {
pub base_fragment_info: BaseFragmentInfo,
#[serde(skip_serializing)]
pub parent_style: Arc<ComputedValues>,
pub text_range: Range<usize>,
pub shaped_text: Vec<TextRunSegment>,
}
#[derive(PartialEq)]
enum SegmentStartSoftWrapPolicy {
Force,
FollowLinebreaker,
}
#[derive(Debug, Serialize)]
pub(crate) struct TextRunSegment {
pub font_index: usize,
#[serde(skip_serializing)]
pub script: Script,
pub bidi_level: Level,
pub range: Range<usize>,
pub break_at_start: bool,
pub runs: Vec<GlyphRun>,
}
impl TextRunSegment {
fn new(font_index: usize, script: Script, bidi_level: Level, start_offset: usize) -> Self {
Self {
font_index,
script,
bidi_level,
range: start_offset..start_offset,
runs: Vec::new(),
break_at_start: false,
}
}
fn update_if_compatible(
&mut self,
new_font: &FontRef,
script: Script,
bidi_level: Level,
fonts: &[FontKeyAndMetrics],
font_context: &FontContext,
) -> bool {
fn is_specific(script: Script) -> bool {
script != Script::Common && script != Script::Inherited
}
if bidi_level != self.bidi_level {
return false;
}
let current_font_key_and_metrics = &fonts[self.font_index];
if new_font.key(font_context) != current_font_key_and_metrics.key ||
new_font.descriptor.pt_size != current_font_key_and_metrics.pt_size
{
return false;
}
if !is_specific(self.script) && is_specific(script) {
self.script = script;
}
script == self.script || !is_specific(script)
}
fn layout_into_line_items(
&self,
text_run: &TextRun,
mut soft_wrap_policy: SegmentStartSoftWrapPolicy,
ifc: &mut InlineFormattingContextLayout,
) {
if self.break_at_start && soft_wrap_policy == SegmentStartSoftWrapPolicy::FollowLinebreaker
{
soft_wrap_policy = SegmentStartSoftWrapPolicy::Force;
}
for (run_index, run) in self.runs.iter().enumerate() {
ifc.possibly_flush_deferred_forced_line_break();
if run.is_single_preserved_newline() {
ifc.defer_forced_line_break();
continue;
}
if run_index != 0 || soft_wrap_policy == SegmentStartSoftWrapPolicy::Force {
ifc.process_soft_wrap_opportunity();
}
ifc.push_glyph_store_to_unbreakable_segment(
run.glyph_store.clone(),
text_run,
self.font_index,
self.bidi_level,
);
}
}
fn shape_and_push_range(
&mut self,
range: &Range<usize>,
formatting_context_text: &str,
segment_font: &FontRef,
options: &ShapingOptions,
) {
self.runs.push(GlyphRun {
glyph_store: segment_font.shape_text(&formatting_context_text[range.clone()], options),
range: ServoRange::new(
ByteIndex(range.start as isize),
ByteIndex(range.len() as isize),
),
});
}
fn shape_text(
&mut self,
parent_style: &ComputedValues,
formatting_context_text: &str,
linebreaker: &mut LineBreaker,
shaping_options: &ShapingOptions,
font: FontRef,
) {
let range = self.range.clone();
let linebreaks = linebreaker.advance_to_linebreaks_in_range(self.range.clone());
let linebreak_iter = linebreaks.iter().chain(std::iter::once(&range.end));
self.runs.clear();
self.runs.reserve(linebreaks.len());
self.break_at_start = false;
let text_style = parent_style.get_inherited_text().clone();
let can_break_anywhere = text_style.word_break == WordBreak::BreakAll ||
text_style.overflow_wrap == OverflowWrap::Anywhere ||
text_style.overflow_wrap == OverflowWrap::BreakWord;
let mut last_slice = self.range.start..self.range.start;
for break_index in linebreak_iter {
if *break_index == self.range.start {
self.break_at_start = true;
continue;
}
let mut options = *shaping_options;
let mut slice = last_slice.end..*break_index;
let word = &formatting_context_text[slice.clone()];
let mut whitespace = slice.end..slice.end;
let mut rev_char_indices = word.char_indices().rev().peekable();
let mut ends_with_whitespace = false;
let ends_with_newline = rev_char_indices
.peek()
.is_some_and(|&(_, character)| character == '\n');
if let Some((first_white_space_index, first_white_space_character)) = rev_char_indices
.take_while(|&(_, character)| char_is_whitespace(character))
.last()
{
ends_with_whitespace = true;
whitespace.start = slice.start + first_white_space_index;
if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces &&
first_white_space_character != '\n' &&
!can_break_anywhere
{
whitespace.start += first_white_space_character.len_utf8();
options
.flags
.insert(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG);
}
slice.end = whitespace.start;
}
if !ends_with_whitespace &&
*break_index != self.range.end &&
text_style.word_break == WordBreak::KeepAll &&
!can_break_anywhere
{
continue;
}
last_slice = slice.start..*break_index;
if !slice.is_empty() {
self.shape_and_push_range(&slice, formatting_context_text, &font, &options);
}
if whitespace.is_empty() {
continue;
}
options.flags.insert(
ShapingFlags::IS_WHITESPACE_SHAPING_FLAG |
ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG,
);
if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces {
let start_index = whitespace.start;
for (index, character) in formatting_context_text[whitespace].char_indices() {
let index = start_index + index;
self.shape_and_push_range(
&(index..index + character.len_utf8()),
formatting_context_text,
&font,
&options,
);
}
continue;
}
if ends_with_newline && whitespace.len() > 1 {
self.shape_and_push_range(
&(whitespace.start..whitespace.end - 1),
formatting_context_text,
&font,
&options,
);
self.shape_and_push_range(
&(whitespace.end - 1..whitespace.end),
formatting_context_text,
&font,
&options,
);
} else {
self.shape_and_push_range(&whitespace, formatting_context_text, &font, &options);
}
}
}
}
impl TextRun {
pub(crate) fn new(
base_fragment_info: BaseFragmentInfo,
parent_style: Arc<ComputedValues>,
text_range: Range<usize>,
) -> Self {
Self {
base_fragment_info,
parent_style,
text_range,
shaped_text: Vec::new(),
}
}
pub(super) fn segment_and_shape(
&mut self,
formatting_context_text: &str,
font_context: &FontContext,
linebreaker: &mut LineBreaker,
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
) {
let inherited_text_style = self.parent_style.get_inherited_text().clone();
let letter_spacing = inherited_text_style
.letter_spacing
.0
.resolve(self.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 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)
}
let specified_word_spacing = &inherited_text_style.word_spacing;
let style_word_spacing: Option<Au> = specified_word_spacing.to_length().map(|l| l.into());
let segments = self
.segment_text_by_font(formatting_context_text, font_context, font_cache, bidi_info)
.into_iter()
.map(|(mut segment, font)| {
let word_spacing = style_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);
specified_word_spacing.to_used_value(Au::from_f64_px(space_width))
});
let mut flags = flags;
if segment.bidi_level.is_rtl() {
flags.insert(ShapingFlags::RTL_FLAG);
}
let shaping_options = ShapingOptions {
letter_spacing,
word_spacing,
script: segment.script,
flags,
};
segment.shape_text(
&self.parent_style,
formatting_context_text,
linebreaker,
&shaping_options,
font,
);
segment
})
.collect();
let _ = std::mem::replace(&mut self.shaped_text, segments);
}
fn segment_text_by_font(
&mut self,
formatting_context_text: &str,
font_context: &FontContext,
font_cache: &mut Vec<FontKeyAndMetrics>,
bidi_info: &BidiInfo,
) -> Vec<(TextRunSegment, FontRef)> {
let font_group = font_context.font_group(self.parent_style.clone_font());
let mut current: Option<(TextRunSegment, FontRef)> = None;
let mut results = Vec::new();
let text_run_text = &formatting_context_text[self.text_range.clone()];
let char_iterator = TwoCharsAtATimeIterator::new(text_run_text.chars());
let mut next_byte_index = self.text_range.start;
for (character, next_character) in char_iterator {
let current_byte_index = next_byte_index;
next_byte_index += character.len_utf8();
if char_does_not_change_font(character) {
continue;
}
let Some(font) =
font_group
.write()
.find_by_codepoint(font_context, character, next_character)
else {
continue;
};
let script = Script::from(character);
let bidi_level = bidi_info.levels[current_byte_index];
if let Some(current) = current.as_mut() {
if current.0.update_if_compatible(
&font,
script,
bidi_level,
font_cache,
font_context,
) {
continue;
}
}
let font_index = add_or_get_font(&font, font_cache, font_context);
let start_byte_index = match current {
Some(_) => current_byte_index,
None => self.text_range.start,
};
let new = (
TextRunSegment::new(font_index, script, bidi_level, start_byte_index),
font,
);
if let Some(mut finished) = current.replace(new) {
finished.0.range.end = current_byte_index;
results.push(finished);
}
}
if current.is_none() {
current = font_group.write().first(font_context).map(|font| {
let font_index = add_or_get_font(&font, font_cache, font_context);
(
TextRunSegment::new(
font_index,
Script::Common,
Level::ltr(),
self.text_range.start,
),
font,
)
})
}
if let Some(mut last_segment) = current.take() {
last_segment.0.range.end = self.text_range.end;
results.push(last_segment);
}
results
}
pub(super) fn layout_into_line_items(&self, ifc: &mut InlineFormattingContextLayout) {
if self.text_range.is_empty() {
return;
}
let have_deferred_soft_wrap_opportunity =
mem::replace(&mut ifc.have_deferred_soft_wrap_opportunity, false);
let mut soft_wrap_policy = match have_deferred_soft_wrap_opportunity {
true => SegmentStartSoftWrapPolicy::Force,
false => SegmentStartSoftWrapPolicy::FollowLinebreaker,
};
for segment in self.shaped_text.iter() {
segment.layout_into_line_items(self, soft_wrap_policy, ifc);
soft_wrap_policy = SegmentStartSoftWrapPolicy::FollowLinebreaker;
}
}
}
fn char_does_not_change_font(character: char) -> bool {
if character.is_control() {
return true;
}
if character == '\u{00A0}' {
return true;
}
if is_bidi_control(character) {
return false;
}
let class = linebreak_property(character);
class == XI_LINE_BREAKING_CLASS_CM ||
class == XI_LINE_BREAKING_CLASS_GL ||
class == XI_LINE_BREAKING_CLASS_ZW ||
class == XI_LINE_BREAKING_CLASS_WJ ||
class == XI_LINE_BREAKING_CLASS_ZWJ
}
pub(super) fn add_or_get_font(
font: &FontRef,
ifc_fonts: &mut Vec<FontKeyAndMetrics>,
font_context: &FontContext,
) -> usize {
let font_instance_key = font.key(font_context);
for (index, ifc_font_info) in ifc_fonts.iter().enumerate() {
if ifc_font_info.key == font_instance_key &&
ifc_font_info.pt_size == font.descriptor.pt_size
{
return index;
}
}
ifc_fonts.push(FontKeyAndMetrics {
metrics: font.metrics.clone(),
key: font_instance_key,
pt_size: font.descriptor.pt_size,
});
ifc_fonts.len() - 1
}
pub(super) fn get_font_for_first_font_for_style(
style: &ComputedValues,
font_context: &FontContext,
) -> Option<FontRef> {
let font = font_context
.font_group(style.clone_font())
.write()
.first(font_context);
if font.is_none() {
warn!("Could not find font for style: {:?}", style.clone_font());
}
font
}
pub(crate) struct TwoCharsAtATimeIterator<InputIterator> {
iterator: InputIterator,
next_character: Option<char>,
}
impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
fn new(iterator: InputIterator) -> Self {
Self {
iterator,
next_character: None,
}
}
}
impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
where
InputIterator: Iterator<Item = char>,
{
type Item = (char, Option<char>);
fn next(&mut self) -> Option<Self::Item> {
if self.next_character.is_none() {
self.next_character = self.iterator.next();
}
let character = self.next_character?;
self.next_character = self.iterator.next();
Some((character, self.next_character))
}
}