1use arrayvec::ArrayVec;
14use icu_properties::props::{EnumeratedProperty, GeneralCategory, GeneralCategoryGroup};
15use icu_segmenter::WordSegmenter;
16use icu_segmenter::options::WordBreakInvariantOptions;
17use malloc_size_of_derive::MallocSizeOf;
18use servo_base::text::Utf32CodeUnits;
19use style::computed_values::_webkit_text_security::T as WebKitTextSecurity;
20use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
21use style::properties::ComputedValues;
22use style::values::specified::text::{TextTransform, TextTransformCase};
23
24use crate::flow::inline::construct::InlineFormattingContextBuilder;
25
26const MAX_CASE_MAPPING_LENGTH: usize = 3;
32
33#[derive(Clone)]
40pub struct CharacterTransformIteration {
41 consumed: Utf32CodeUnits,
43 characters: ArrayVec<char, MAX_CASE_MAPPING_LENGTH>,
45}
46
47impl CharacterTransformIteration {
48 fn case_mapped(iterator: impl ExactSizeIterator<Item = char>) -> Self {
49 debug_assert!(iterator.len() <= MAX_CASE_MAPPING_LENGTH);
50 Self {
51 consumed: Utf32CodeUnits(1),
52 characters: iterator.collect(),
53 }
54 }
55
56 fn one_to_one(character: char) -> Self {
57 Self {
58 consumed: Utf32CodeUnits(1),
59 characters: std::iter::once(character).collect(),
60 }
61 }
62
63 fn collapse(amount_collapsed: usize, character: Option<char>) -> Self {
64 Self {
65 consumed: Utf32CodeUnits(amount_collapsed),
66 characters: character.into_iter().collect(),
67 }
68 }
69
70 fn is_one_to_one(&self) -> bool {
71 self.characters.len() == 1 && self.consumed.0 == 1
72 }
73
74 pub fn characters(&self) -> &[char] {
75 &self.characters
76 }
77}
78
79pub struct WhitespaceCollapse<InputIterator> {
80 input_iterator: InputIterator,
81 white_space_collapse: WhiteSpaceCollapse,
82
83 trimming_leading_white_space: bool,
88
89 following_newline: bool,
92
93 character_pending_to_return: Option<char>,
97}
98
99impl<InputIterator: Iterator<Item = char>> WhitespaceCollapse<InputIterator> {
100 pub fn new(
101 input_iterator: InputIterator,
102 white_space_collapse: WhiteSpaceCollapse,
103 should_trim_leading_white_space: bool,
104 ) -> Self {
105 Self {
106 input_iterator,
107 white_space_collapse,
108 following_newline: false,
109 trimming_leading_white_space: should_trim_leading_white_space,
110 character_pending_to_return: None,
111 }
112 }
113
114 fn iteration_for_collapsed_whitespace(
118 &self,
119 collapsed_whitespace: usize,
120 ) -> CharacterTransformIteration {
121 if !self.following_newline && !self.trimming_leading_white_space {
122 CharacterTransformIteration::collapse(collapsed_whitespace, Some(' '))
123 } else {
124 CharacterTransformIteration::collapse(collapsed_whitespace, None)
125 }
126 }
127
128 fn iteration_for_collected_white_space(
129 &self,
130 collected_whitespace: usize,
131 ) -> Option<CharacterTransformIteration> {
132 (collected_whitespace != 0)
133 .then(|| self.iteration_for_collapsed_whitespace(collected_whitespace))
134 }
135}
136
137impl<InputIterator: Iterator<Item = char>> Iterator for WhitespaceCollapse<InputIterator> {
138 type Item = CharacterTransformIteration;
139
140 fn next(&mut self) -> Option<Self::Item> {
141 if self.white_space_collapse == WhiteSpaceCollapse::Preserve ||
147 self.white_space_collapse == WhiteSpaceCollapse::BreakSpaces
148 {
149 return match self.input_iterator.next() {
154 Some('\r') => Some(CharacterTransformIteration::one_to_one(' ')),
155 next => next.map(CharacterTransformIteration::one_to_one),
156 };
157 }
158
159 if let Some(character) = self.character_pending_to_return.take() {
160 self.trimming_leading_white_space = false;
162 self.following_newline = false;
163 return Some(CharacterTransformIteration::one_to_one(character));
164 }
165
166 let mut collected_whitespace = 0;
171
172 while let Some(character) = self.input_iterator.next() {
173 if InlineFormattingContextBuilder::is_document_white_space(character) &&
177 character != '\n'
178 {
179 collected_whitespace += 1;
180 continue;
181 }
182
183 if character == '\n' {
187 let iteration = if self.white_space_collapse != WhiteSpaceCollapse::Collapse {
198 CharacterTransformIteration::collapse(collected_whitespace + 1, Some('\n'))
199 } else {
200 self.iteration_for_collapsed_whitespace(collected_whitespace + 1)
201 };
202
203 self.following_newline = true;
204 return Some(iteration);
205 }
206
207 if let Some(iteration) = self.iteration_for_collected_white_space(collected_whitespace)
218 {
219 self.character_pending_to_return = Some(character);
220 return Some(iteration);
221 }
222
223 self.trimming_leading_white_space = false;
225 self.following_newline = false;
226 return Some(CharacterTransformIteration::one_to_one(character));
227 }
228
229 self.iteration_for_collected_white_space(collected_whitespace)
230 }
231}
232
233pub(crate) struct TextTransformationIterator<'a> {
234 case_map_iterator: Box<dyn Iterator<Item = CharacterTransformIteration> + 'a>,
235 full_width: bool,
236 full_size_kana: bool,
237}
238
239impl<'a> TextTransformationIterator<'a> {
240 pub(crate) fn new(
241 mut text: &'a str,
242 style: &ComputedValues,
243 trim_leading_white_space: bool,
244 on_word_boundary: bool,
245 ) -> Self {
246 let text_security = style.clone__webkit_text_security();
247
248 let text_transform = style.clone_text_transform();
250
251 if text_transform.intersects(TextTransform::MATH_AUTO) {
252 let mut char_iter = text.chars();
258 if let Some(first_char) = char_iter.next() &&
259 let None = char_iter.next() &&
260 let Some(&mapping) = super::mathml_italics::ITALICS_MAPPINGS.get(&first_char)
261 {
262 text = mapping
263 }
264 }
265
266 let chars = text
267 .chars()
268 .map(move |character| map_character_for_webkit_text_security(text_security, character));
269 let white_space_collapse = style.clone_white_space_collapse();
270 let iterator =
271 WhitespaceCollapse::new(chars, white_space_collapse, trim_leading_white_space);
272
273 let case_map_iterator = match text_transform.case() {
289 TextTransformCase::None => {
290 Box::new(iterator) as Box<dyn Iterator<Item = CharacterTransformIteration>>
291 },
292 TextTransformCase::Lowercase => {
293 Box::new(simple_case_transform_iterator(iterator, |character| {
294 CharacterTransformIteration::case_mapped(character.to_lowercase())
295 }))
296 },
297 TextTransformCase::Uppercase => {
298 Box::new(simple_case_transform_iterator(iterator, |character| {
299 CharacterTransformIteration::case_mapped(character.to_uppercase())
300 }))
301 },
302 TextTransformCase::Capitalize => Box::new(capitalization_iterator(
303 iterator,
304 text.len(),
305 on_word_boundary,
306 )),
307 };
308
309 Self {
310 case_map_iterator,
311 full_width: text_transform.intersects(TextTransform::FULL_WIDTH),
312 full_size_kana: text_transform.intersects(TextTransform::FULL_SIZE_KANA),
313 }
314 }
315}
316
317impl Iterator for TextTransformationIterator<'_> {
318 type Item = CharacterTransformIteration;
319
320 fn next(&mut self) -> Option<Self::Item> {
321 let mut iteration = self.case_map_iterator.next()?;
330 map_characters_with_phf(
331 self.full_width,
332 &mut iteration.characters,
333 &super::full_width::FULL_WIDTH_MAPPINGS,
334 );
335 map_characters_with_phf(
336 self.full_size_kana,
337 &mut iteration.characters,
338 &super::small_kana::SMALL_KANA_MAPPINGS,
339 );
340 Some(iteration)
341 }
342}
343
344fn simple_case_transform_iterator(
345 input_iterator: impl Iterator<Item = CharacterTransformIteration>,
346 mapping: impl Fn(char) -> CharacterTransformIteration,
347) -> impl Iterator<Item = CharacterTransformIteration> {
348 input_iterator.map(move |iteration| {
349 if iteration.is_one_to_one() {
350 mapping(iteration.characters[0])
351 } else {
352 iteration
353 }
354 })
355}
356
357fn is_typographic_letter_unit(character: char) -> bool {
363 let category = GeneralCategory::for_char(character);
364 GeneralCategoryGroup::Letter.contains(category) ||
365 GeneralCategoryGroup::Number.contains(category)
366}
367
368pub(crate) fn capitalization_iterator(
373 input_iterator: impl Iterator<Item = CharacterTransformIteration>,
374 size_hint: usize,
375 allow_word_at_start: bool,
376) -> impl Iterator<Item = CharacterTransformIteration> {
377 let mut iterations: Vec<_> = input_iterator.collect();
378 let mut string = String::with_capacity(size_hint);
379 for iteration in &iterations {
380 string.extend(iteration.characters());
381 }
382
383 let word_segmenter = WordSegmenter::new_auto(WordBreakInvariantOptions::default());
384 let mut bounds = word_segmenter.segment_str(&string).peekable();
385 let mut current_byte_index = 0;
386 let mut pending_word_start = false;
387 for iteration in iterations.iter_mut() {
388 let bytes_to_advance: usize = iteration
389 .characters()
390 .iter()
391 .map(|character| character.len_utf8())
392 .sum();
393 if bytes_to_advance == 0 {
394 continue;
395 }
396
397 if bounds.peek() == Some(¤t_byte_index) {
398 pending_word_start = current_byte_index != 0 || allow_word_at_start;
399 bounds.next();
400 }
401
402 if iteration.is_one_to_one() &&
406 pending_word_start &&
407 is_typographic_letter_unit(iteration.characters[0])
408 {
409 if iteration.characters[0].is_lowercase() {
410 *iteration = CharacterTransformIteration::case_mapped(
414 iteration.characters[0].to_uppercase(),
415 );
416 }
417
418 pending_word_start = false;
419 }
420
421 current_byte_index += bytes_to_advance;
422 }
423
424 iterations.into_iter()
425}
426
427fn map_character_for_webkit_text_security(mode: WebKitTextSecurity, character: char) -> char {
433 if let WebKitTextSecurity::None = mode {
434 return character;
435 }
436
437 match character {
439 '\u{200B}' => '\u{200B}',
443 '\n' => '\n',
445 _ => match mode {
446 WebKitTextSecurity::None => character, WebKitTextSecurity::Circle => '○',
448 WebKitTextSecurity::Disc => '●',
449 WebKitTextSecurity::Square => '■',
450 },
451 }
452}
453
454fn map_characters_with_phf(enabled: bool, characters: &mut [char], map: &phf::Map<char, char>) {
455 if enabled {
456 for character in characters {
459 if let Some(mapping) = map.get(character) {
460 *character = *mapping
461 }
462 }
463 }
464}
465
466#[derive(MallocSizeOf, Clone, Copy)]
467struct OffsetMapKnownPosition {
468 original_offset: Utf32CodeUnits,
469 final_offset: Utf32CodeUnits,
470}
471
472#[derive(Default, MallocSizeOf)]
473pub struct OffsetMap {
474 known_positions: Vec<OffsetMapKnownPosition>,
476 last_range_maps_one_to_one: bool,
478}
479
480impl std::fmt::Debug for OffsetMap {
481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
482 f.debug_struct("OffsetMap")
483 .field("total_original_size", &self.total_original_size())
484 .field("total_final_size", &self.total_final_size())
485 .finish()
486 }
487}
488
489static IMPLICIT_KNOWN_POSITION_AT_START: OffsetMapKnownPosition = OffsetMapKnownPosition {
490 original_offset: Utf32CodeUnits(0),
491 final_offset: Utf32CodeUnits(0),
492};
493
494impl OffsetMap {
495 fn last_known_position(&self) -> &OffsetMapKnownPosition {
496 self.known_positions
497 .last()
498 .unwrap_or(&IMPLICIT_KNOWN_POSITION_AT_START)
499 }
500
501 pub fn total_original_size(&self) -> Utf32CodeUnits {
502 self.last_known_position().original_offset
503 }
504
505 pub fn total_final_size(&self) -> Utf32CodeUnits {
506 self.last_known_position().final_offset
507 }
508
509 pub fn push_range(
510 &mut self,
511 additional_original_length: Utf32CodeUnits,
512 additional_final_length: Utf32CodeUnits,
513 ) {
514 let this_range_maps_one_to_one = additional_original_length == additional_final_length;
515 if this_range_maps_one_to_one &&
516 self.last_range_maps_one_to_one &&
517 let Some(last) = self.known_positions.last_mut()
518 {
519 last.original_offset += additional_original_length;
520 last.final_offset += additional_final_length;
521 } else {
522 let last = self.last_known_position();
523 self.known_positions.push(OffsetMapKnownPosition {
524 original_offset: last.original_offset + additional_original_length,
525 final_offset: last.final_offset + additional_final_length,
526 });
527 }
528 self.last_range_maps_one_to_one = this_range_maps_one_to_one;
529 }
530
531 pub(crate) fn push_iteration(&mut self, iteration: &CharacterTransformIteration) {
532 self.push_range(
533 iteration.consumed,
534 Utf32CodeUnits(iteration.characters.len()),
535 );
536 }
537
538 pub fn map(&self, target_original_offset: Utf32CodeUnits) -> Utf32CodeUnits {
539 self.map_common(
540 target_original_offset,
541 |position| position.original_offset,
542 |position| position.final_offset,
543 )
544 }
545
546 pub fn reverse_map(&self, target_final_offset: Utf32CodeUnits) -> Utf32CodeUnits {
547 self.map_common(
548 target_final_offset,
549 |position| position.final_offset,
550 |position| position.original_offset,
551 )
552 }
553
554 fn map_common(
555 &self,
556 target_offset: Utf32CodeUnits,
557 get_input_offset: impl Copy + Fn(&OffsetMapKnownPosition) -> Utf32CodeUnits,
558 get_output_offset: impl Fn(&OffsetMapKnownPosition) -> Utf32CodeUnits,
559 ) -> Utf32CodeUnits {
560 if target_offset.0 == 0 {
561 return Utf32CodeUnits(0);
563 }
564 match self
565 .known_positions
566 .binary_search_by_key(&target_offset, get_input_offset)
567 {
568 Ok(index) => {
569 get_output_offset(&self.known_positions[index])
571 },
572 Err(index) => {
573 if let Some(position_after) = self.known_positions.get(index) {
575 let position_before = if index > 0 {
576 &self.known_positions[index - 1]
577 } else {
578 &IMPLICIT_KNOWN_POSITION_AT_START
579 };
580 debug_assert!(target_offset > get_input_offset(position_before));
581 debug_assert!(target_offset < get_input_offset(position_after));
582 let offset_within_range = target_offset - get_input_offset(position_before);
583 let candidate = get_output_offset(position_before) + offset_within_range;
584 let upper_bound = get_output_offset(position_after);
586 upper_bound.min(candidate)
587 } else {
588 get_output_offset(self.last_known_position())
590 }
591 },
592 }
593 }
594}
595
596#[test]
597fn test_offsetmap_basic_expansion() {
598 let original_string = "aßΰb";
599 let final_string = "ASS\u{3a5}\u{308}\u{301}B";
600 assert_eq!(original_string.to_uppercase(), final_string);
601
602 let mut offset_map = OffsetMap::default();
603 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
604 'a'.to_uppercase(),
605 ));
606 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
607 'ß'.to_uppercase(),
608 ));
609 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
610 'ΰ'.to_uppercase(),
611 ));
612 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
613 'b'.to_uppercase(),
614 ));
615
616 assert_eq!(offset_map.map(Utf32CodeUnits(0)).0, 0);
617 assert_eq!(offset_map.map(Utf32CodeUnits(1)).0, 1);
618 assert_eq!(offset_map.map(Utf32CodeUnits(2)).0, 3);
619 assert_eq!(offset_map.map(Utf32CodeUnits(3)).0, 6);
620 assert_eq!(offset_map.map(Utf32CodeUnits(4)).0, 7);
621
622 assert_eq!(offset_map.map(Utf32CodeUnits(5)).0, 7);
625 assert_eq!(offset_map.map(Utf32CodeUnits(100)).0, 7);
626
627 let map_substring = |offset: usize, length: usize| {
628 let start = offset_map
629 .map(Utf32CodeUnits(offset))
630 .to_utf8_code_units_in(final_string);
631 let end = offset_map
632 .map(Utf32CodeUnits(offset + length))
633 .to_utf8_code_units_in(final_string);
634 &final_string[start.0..end.0]
635 };
636 assert_eq!(map_substring(0, 1), "A");
637 assert_eq!(map_substring(0, 2), "ASS");
638 assert_eq!(map_substring(0, 3), "ASS\u{3a5}\u{308}\u{301}");
639 assert_eq!(map_substring(0, 4), "ASS\u{3a5}\u{308}\u{301}B");
640 assert_eq!(map_substring(1, 1), "SS");
641}
642
643#[test]
644fn test_offsetmap_basic_collapse() {
645 let _original_string = " aaa b \nc";
646 let final_string = "aaa b\nc";
647
648 let mut offset_map = OffsetMap::default();
649 offset_map.push_iteration(&CharacterTransformIteration::collapse(2, None));
650 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
651 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
652 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
653 assert_eq!(
654 offset_map.known_positions.len(),
655 2,
656 "Consecutive one-to-one mappings are merged"
657 );
658
659 offset_map.push_iteration(&CharacterTransformIteration::collapse(2, Some(' ')));
660 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('b'));
661 offset_map.push_iteration(&CharacterTransformIteration::collapse(2, Some('\n')));
662 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('c'));
663
664 assert_eq!(offset_map.map(Utf32CodeUnits(0)).0, 0);
665 assert_eq!(offset_map.map(Utf32CodeUnits(1)).0, 0);
666 assert_eq!(offset_map.map(Utf32CodeUnits(2)).0, 0);
667 assert_eq!(offset_map.map(Utf32CodeUnits(3)).0, 1);
668 assert_eq!(offset_map.map(Utf32CodeUnits(4)).0, 2);
669 assert_eq!(offset_map.map(Utf32CodeUnits(5)).0, 3);
670 assert_eq!(offset_map.map(Utf32CodeUnits(6)).0, 4);
672 assert_eq!(offset_map.map(Utf32CodeUnits(7)).0, 4);
673 assert_eq!(offset_map.map(Utf32CodeUnits(8)).0, 5);
674 assert_eq!(offset_map.map(Utf32CodeUnits(9)).0, 6);
676 assert_eq!(offset_map.map(Utf32CodeUnits(10)).0, 6);
677 assert_eq!(offset_map.map(Utf32CodeUnits(11)).0, 7);
678
679 assert_eq!(offset_map.map(Utf32CodeUnits(12)).0, 7);
682 assert_eq!(offset_map.map(Utf32CodeUnits(100)).0, 7);
683
684 let map_substring = |offset: usize, length: usize| {
685 let start = offset_map.map(Utf32CodeUnits(offset)).0;
686 let end = offset_map.map(Utf32CodeUnits(offset + length)).0;
687 &final_string[start..end]
688 };
689 assert_eq!(map_substring(0, 1), "");
690 assert_eq!(map_substring(0, 3), "a");
691 assert_eq!(map_substring(0, 5), "aaa");
692 assert_eq!(map_substring(0, 6), "aaa ");
693 assert_eq!(map_substring(0, 7), "aaa ");
694 assert_eq!(map_substring(0, 8), "aaa b");
695 assert_eq!(map_substring(0, 11), "aaa b\nc");
696}