1use arrayvec::ArrayVec;
14use icu_casemap::CaseMapper;
15use icu_casemap::options::TitlecaseOptions;
16use icu_locale_core::LanguageIdentifier;
17use icu_properties::props::{EnumeratedProperty, GeneralCategory, GeneralCategoryGroup};
18use icu_segmenter::WordSegmenter;
19use icu_segmenter::options::WordBreakInvariantOptions;
20use malloc_size_of_derive::MallocSizeOf;
21#[cfg(test)]
22use servo_base::text::AssumeUnder4GB;
23use servo_base::text::Utf32CodeUnits;
24use style::computed_values::_webkit_text_security::T as WebKitTextSecurity;
25use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
26use style::properties::ComputedValues;
27use style::values::specified::text::{TextTransform, TextTransformCase};
28use writeable::Writeable;
29
30use crate::flow::inline::construct::InlineFormattingContextBuilder;
31
32const MAX_CASE_MAPPING_LENGTH: usize = 3;
38
39#[derive(Clone)]
46pub struct CharacterTransformIteration {
47 consumed: Utf32CodeUnits,
49 characters: ArrayVec<char, MAX_CASE_MAPPING_LENGTH>,
51}
52
53impl CharacterTransformIteration {
54 fn case_mapped(iterator: impl ExactSizeIterator<Item = char>) -> Self {
55 debug_assert!(iterator.len() <= MAX_CASE_MAPPING_LENGTH);
56 Self {
57 consumed: Utf32CodeUnits(1),
58 characters: iterator.collect(),
59 }
60 }
61
62 fn one_to_one(character: char) -> Self {
63 Self {
64 consumed: Utf32CodeUnits(1),
65 characters: std::iter::once(character).collect(),
66 }
67 }
68
69 fn collapse(amount_collapsed: Utf32CodeUnits, character: Option<char>) -> Self {
70 Self {
71 consumed: amount_collapsed,
72 characters: character.into_iter().collect(),
73 }
74 }
75
76 fn is_one_to_one(&self) -> bool {
77 self.characters.len() == 1 && self.consumed.0 == 1
78 }
79
80 pub fn characters(&self) -> &[char] {
81 &self.characters
82 }
83}
84
85pub struct WhitespaceCollapse<InputIterator> {
86 input_iterator: InputIterator,
87 white_space_collapse: WhiteSpaceCollapse,
88
89 trimming_leading_white_space: bool,
94
95 following_newline: bool,
98
99 character_pending_to_return: Option<char>,
103}
104
105impl<InputIterator: Iterator<Item = char>> WhitespaceCollapse<InputIterator> {
106 pub fn new(
107 input_iterator: InputIterator,
108 white_space_collapse: WhiteSpaceCollapse,
109 should_trim_leading_white_space: bool,
110 ) -> Self {
111 Self {
112 input_iterator,
113 white_space_collapse,
114 following_newline: false,
115 trimming_leading_white_space: should_trim_leading_white_space,
116 character_pending_to_return: None,
117 }
118 }
119
120 fn iteration_for_collapsed_whitespace(
124 &self,
125 collapsed_whitespace: Utf32CodeUnits,
126 ) -> CharacterTransformIteration {
127 if !self.following_newline && !self.trimming_leading_white_space {
128 CharacterTransformIteration::collapse(collapsed_whitespace, Some(' '))
129 } else {
130 CharacterTransformIteration::collapse(collapsed_whitespace, None)
131 }
132 }
133
134 fn iteration_for_collected_white_space(
135 &self,
136 collected_whitespace: Utf32CodeUnits,
137 ) -> Option<CharacterTransformIteration> {
138 (collected_whitespace.0 != 0)
139 .then(|| self.iteration_for_collapsed_whitespace(collected_whitespace))
140 }
141}
142
143impl<InputIterator: Iterator<Item = char>> Iterator for WhitespaceCollapse<InputIterator> {
144 type Item = CharacterTransformIteration;
145
146 fn next(&mut self) -> Option<Self::Item> {
147 if self.white_space_collapse == WhiteSpaceCollapse::Preserve ||
153 self.white_space_collapse == WhiteSpaceCollapse::BreakSpaces
154 {
155 return match self.input_iterator.next() {
160 Some('\r') => Some(CharacterTransformIteration::one_to_one(' ')),
161 next => next.map(CharacterTransformIteration::one_to_one),
162 };
163 }
164
165 if let Some(character) = self.character_pending_to_return.take() {
166 self.trimming_leading_white_space = false;
168 self.following_newline = false;
169 return Some(CharacterTransformIteration::one_to_one(character));
170 }
171
172 let mut collected_whitespace = Utf32CodeUnits(0);
177
178 while let Some(character) = self.input_iterator.next() {
179 if InlineFormattingContextBuilder::is_document_white_space(character) &&
183 character != '\n'
184 {
185 collected_whitespace += Utf32CodeUnits(1);
186 continue;
187 }
188
189 if character == '\n' {
193 let iteration = if self.white_space_collapse != WhiteSpaceCollapse::Collapse {
204 CharacterTransformIteration::collapse(
205 collected_whitespace + Utf32CodeUnits(1),
206 Some('\n'),
207 )
208 } else {
209 self.iteration_for_collapsed_whitespace(
210 collected_whitespace + Utf32CodeUnits(1),
211 )
212 };
213
214 self.following_newline = true;
215 return Some(iteration);
216 }
217
218 if let Some(iteration) = self.iteration_for_collected_white_space(collected_whitespace)
229 {
230 self.character_pending_to_return = Some(character);
231 return Some(iteration);
232 }
233
234 self.trimming_leading_white_space = false;
236 self.following_newline = false;
237 return Some(CharacterTransformIteration::one_to_one(character));
238 }
239
240 self.iteration_for_collected_white_space(collected_whitespace)
241 }
242}
243
244pub(crate) struct TextTransformationIterator<'a> {
245 case_map_iterator: Box<dyn Iterator<Item = CharacterTransformIteration> + 'a>,
246 full_width: bool,
247 full_size_kana: bool,
248}
249
250impl<'a> TextTransformationIterator<'a> {
251 pub(crate) fn new(
252 mut text: &'a str,
253 style: &'a ComputedValues,
254 trim_leading_white_space: bool,
255 on_word_boundary: bool,
256 ) -> Self {
257 let text_security = style.get__webkit_text_security();
258
259 let text_transform = style.get_text_transform();
261
262 if text_transform.intersects(TextTransform::MATH_AUTO) {
263 let mut char_iter = text.chars();
269 if let Some(first_char) = char_iter.next() &&
270 let None = char_iter.next() &&
271 let Some(&mapping) = super::mathml_italics::ITALICS_MAPPINGS.get(&first_char)
272 {
273 text = mapping
274 }
275 }
276
277 let chars = text
278 .chars()
279 .map(move |character| map_character_for_webkit_text_security(text_security, character));
280 let white_space_collapse = style.slow_clone_white_space_collapse();
281 let iterator =
282 WhitespaceCollapse::new(chars, white_space_collapse, trim_leading_white_space);
283
284 let case_map_iterator = match text_transform.case() {
300 TextTransformCase::None | TextTransformCase::MathAuto => {
301 Box::new(iterator) as Box<dyn Iterator<Item = CharacterTransformIteration>>
302 },
303 TextTransformCase::Lowercase => {
304 Box::new(simple_case_transform_iterator(iterator, |character| {
305 CharacterTransformIteration::case_mapped(character.to_lowercase())
306 }))
307 },
308 TextTransformCase::Uppercase => {
309 Box::new(simple_case_transform_iterator(iterator, |character| {
310 CharacterTransformIteration::case_mapped(character.to_uppercase())
311 }))
312 },
313 TextTransformCase::Capitalize => Box::new(capitalization_iterator(
314 iterator,
315 text.len(),
316 on_word_boundary,
317 style
318 .get__x_lang()
319 .0
320 .parse()
321 .unwrap_or(LanguageIdentifier::UNKNOWN),
322 )),
323 };
324
325 Self {
326 case_map_iterator,
327 full_width: text_transform.intersects(TextTransform::FULL_WIDTH),
328 full_size_kana: text_transform.intersects(TextTransform::FULL_SIZE_KANA),
329 }
330 }
331}
332
333impl Iterator for TextTransformationIterator<'_> {
334 type Item = CharacterTransformIteration;
335
336 fn next(&mut self) -> Option<Self::Item> {
337 let mut iteration = self.case_map_iterator.next()?;
346 map_characters_with_phf(
347 self.full_width,
348 &mut iteration.characters,
349 &super::full_width::FULL_WIDTH_MAPPINGS,
350 );
351 map_characters_with_phf(
352 self.full_size_kana,
353 &mut iteration.characters,
354 &super::small_kana::SMALL_KANA_MAPPINGS,
355 );
356 Some(iteration)
357 }
358}
359
360fn simple_case_transform_iterator(
361 input_iterator: impl Iterator<Item = CharacterTransformIteration>,
362 mapping: impl Fn(char) -> CharacterTransformIteration,
363) -> impl Iterator<Item = CharacterTransformIteration> {
364 input_iterator.map(move |iteration| {
365 if iteration.is_one_to_one() {
366 mapping(iteration.characters[0])
367 } else {
368 iteration
369 }
370 })
371}
372
373fn is_typographic_letter_unit(character: char) -> bool {
379 let category = GeneralCategory::for_char(character);
380 GeneralCategoryGroup::Letter.contains(category) ||
381 GeneralCategoryGroup::Number.contains(category)
382}
383
384#[derive(Default)]
385struct CaseMappingSink(ArrayVec<char, MAX_CASE_MAPPING_LENGTH>);
386
387impl std::fmt::Write for CaseMappingSink {
388 fn write_str(&mut self, string: &str) -> std::fmt::Result {
389 string
390 .chars()
391 .try_for_each(|character| self.write_char(character))
392 }
393
394 fn write_char(&mut self, character: char) -> std::fmt::Result {
395 self.0.try_push(character).map_err(|_| std::fmt::Error)
396 }
397}
398
399fn title_case(character: char, language: &LanguageIdentifier) -> CharacterTransformIteration {
400 let mut buffer = [0; 4];
401 let mut titlecase = CaseMappingSink::default();
402 let result = CaseMapper::new()
403 .titlecase_segment_with_only_case_data(
404 character.encode_utf8(&mut buffer),
405 language,
406 TitlecaseOptions::default(),
407 )
408 .write_to(&mut titlecase);
409 debug_assert!(
410 result.is_ok(),
411 "CaseMapping of {character} for language {language:?} required more than \
412 MAX_CASE_MAPPING_LENGTH ({MAX_CASE_MAPPING_LENGTH}) characters."
413 );
414 CharacterTransformIteration::case_mapped(titlecase.0.into_iter())
415}
416
417pub(crate) fn capitalization_iterator(
422 input_iterator: impl Iterator<Item = CharacterTransformIteration>,
423 size_hint: usize,
424 allow_word_at_start: bool,
425 language: LanguageIdentifier,
426) -> impl Iterator<Item = CharacterTransformIteration> {
427 let mut iterations: Vec<_> = input_iterator.collect();
428 let mut string = String::with_capacity(size_hint);
429 for iteration in &iterations {
430 string.extend(iteration.characters());
431 }
432
433 let word_segmenter =
436 WordSegmenter::new_for_non_complex_scripts(WordBreakInvariantOptions::default());
437 let mut bounds = word_segmenter.segment_str(&string).peekable();
438 let mut current_byte_index = 0;
439 let mut pending_word_start = false;
440 for iteration in iterations.iter_mut() {
441 let bytes_to_advance: usize = iteration
442 .characters()
443 .iter()
444 .map(|character| character.len_utf8())
445 .sum();
446 if bytes_to_advance == 0 {
447 continue;
448 }
449
450 if bounds.peek() == Some(¤t_byte_index) {
451 pending_word_start = current_byte_index != 0 || allow_word_at_start;
452 bounds.next();
453 }
454
455 if iteration.is_one_to_one() &&
459 pending_word_start &&
460 is_typographic_letter_unit(iteration.characters[0])
461 {
462 if iteration.characters[0].is_lowercase() {
464 *iteration = title_case(iteration.characters[0], &language);
465 }
466
467 pending_word_start = false;
468 }
469
470 current_byte_index += bytes_to_advance;
471 }
472
473 iterations.into_iter()
474}
475
476fn map_character_for_webkit_text_security(mode: &WebKitTextSecurity, character: char) -> char {
482 if let WebKitTextSecurity::None = mode {
483 return character;
484 }
485
486 match character {
488 '\n' => '\n',
490 _ => match mode {
491 WebKitTextSecurity::None => character, WebKitTextSecurity::Circle => '○',
493 WebKitTextSecurity::Disc => '●',
494 WebKitTextSecurity::Square => '■',
495 },
496 }
497}
498
499fn map_characters_with_phf(enabled: bool, characters: &mut [char], map: &phf::Map<char, char>) {
500 if enabled {
501 for character in characters {
504 if let Some(mapping) = map.get(character) {
505 *character = *mapping
506 }
507 }
508 }
509}
510
511#[derive(MallocSizeOf, Clone, Copy)]
512struct OffsetMapKnownPosition {
513 original_offset: Utf32CodeUnits,
514 final_offset: Utf32CodeUnits,
515}
516
517#[derive(Default, MallocSizeOf)]
518pub struct OffsetMap {
519 known_positions: Vec<OffsetMapKnownPosition>,
521 last_range_maps_one_to_one: bool,
523}
524
525impl std::fmt::Debug for OffsetMap {
526 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527 f.debug_struct("OffsetMap")
528 .field("total_original_size", &self.total_original_size())
529 .field("total_final_size", &self.total_final_size())
530 .finish()
531 }
532}
533
534static IMPLICIT_KNOWN_POSITION_AT_START: OffsetMapKnownPosition = OffsetMapKnownPosition {
535 original_offset: Utf32CodeUnits(0),
536 final_offset: Utf32CodeUnits(0),
537};
538
539impl OffsetMap {
540 fn last_known_position(&self) -> &OffsetMapKnownPosition {
541 self.known_positions
542 .last()
543 .unwrap_or(&IMPLICIT_KNOWN_POSITION_AT_START)
544 }
545
546 pub fn total_original_size(&self) -> Utf32CodeUnits {
547 self.last_known_position().original_offset
548 }
549
550 pub fn total_final_size(&self) -> Utf32CodeUnits {
551 self.last_known_position().final_offset
552 }
553
554 pub fn push_range(
555 &mut self,
556 additional_original_length: Utf32CodeUnits,
557 additional_final_length: Utf32CodeUnits,
558 ) {
559 let this_range_maps_one_to_one = additional_original_length == additional_final_length;
560 if this_range_maps_one_to_one &&
561 self.last_range_maps_one_to_one &&
562 let Some(last) = self.known_positions.last_mut()
563 {
564 last.original_offset += additional_original_length;
565 last.final_offset += additional_final_length;
566 } else {
567 let last = self.last_known_position();
568 self.known_positions.push(OffsetMapKnownPosition {
569 original_offset: last.original_offset + additional_original_length,
570 final_offset: last.final_offset + additional_final_length,
571 });
572 }
573 self.last_range_maps_one_to_one = this_range_maps_one_to_one;
574 }
575
576 pub(crate) fn push_iteration(&mut self, iteration: &CharacterTransformIteration) {
577 self.push_range(
578 iteration.consumed,
579 Utf32CodeUnits(iteration.characters.len() as u32),
580 );
581 }
582
583 pub fn map(&self, target_original_offset: Utf32CodeUnits) -> Utf32CodeUnits {
584 self.map_common(
585 target_original_offset,
586 |position| position.original_offset,
587 |position| position.final_offset,
588 )
589 }
590
591 pub fn reverse_map(&self, target_final_offset: Utf32CodeUnits) -> Utf32CodeUnits {
592 self.map_common(
593 target_final_offset,
594 |position| position.final_offset,
595 |position| position.original_offset,
596 )
597 }
598
599 fn map_common(
600 &self,
601 target_offset: Utf32CodeUnits,
602 get_input_offset: impl Copy + Fn(&OffsetMapKnownPosition) -> Utf32CodeUnits,
603 get_output_offset: impl Fn(&OffsetMapKnownPosition) -> Utf32CodeUnits,
604 ) -> Utf32CodeUnits {
605 if target_offset.0 == 0 {
606 return Utf32CodeUnits(0);
608 }
609 match self
610 .known_positions
611 .binary_search_by_key(&target_offset, get_input_offset)
612 {
613 Ok(index) => {
614 get_output_offset(&self.known_positions[index])
616 },
617 Err(index) => {
618 if let Some(position_after) = self.known_positions.get(index) {
620 let position_before = if index > 0 {
621 &self.known_positions[index - 1]
622 } else {
623 &IMPLICIT_KNOWN_POSITION_AT_START
624 };
625 debug_assert!(target_offset > get_input_offset(position_before));
626 debug_assert!(target_offset < get_input_offset(position_after));
627 let offset_within_range = target_offset - get_input_offset(position_before);
628 let candidate = get_output_offset(position_before) + offset_within_range;
629 let upper_bound = get_output_offset(position_after);
631 upper_bound.min(candidate)
632 } else {
633 get_output_offset(self.last_known_position())
635 }
636 },
637 }
638 }
639}
640
641#[test]
642fn test_offsetmap_basic_expansion() {
643 let original_string = "aßΰb";
644 let final_string = "ASS\u{3a5}\u{308}\u{301}B";
645 assert_eq!(original_string.to_uppercase(), final_string);
646
647 let mut offset_map = OffsetMap::default();
648 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
649 'a'.to_uppercase(),
650 ));
651 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
652 'ß'.to_uppercase(),
653 ));
654 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
655 'ΰ'.to_uppercase(),
656 ));
657 offset_map.push_iteration(&CharacterTransformIteration::case_mapped(
658 'b'.to_uppercase(),
659 ));
660
661 assert_eq!(offset_map.map(Utf32CodeUnits(0)).0, 0);
662 assert_eq!(offset_map.map(Utf32CodeUnits(1)).0, 1);
663 assert_eq!(offset_map.map(Utf32CodeUnits(2)).0, 3);
664 assert_eq!(offset_map.map(Utf32CodeUnits(3)).0, 6);
665 assert_eq!(offset_map.map(Utf32CodeUnits(4)).0, 7);
666
667 assert_eq!(offset_map.map(Utf32CodeUnits(5)).0, 7);
670 assert_eq!(offset_map.map(Utf32CodeUnits(100)).0, 7);
671
672 let map_substring = |offset: u32, length: u32| {
673 let start = usize::from(
674 offset_map
675 .map(Utf32CodeUnits(offset))
676 .to_utf8_code_units_in(AssumeUnder4GB, final_string),
677 );
678 let end = usize::from(
679 offset_map
680 .map(Utf32CodeUnits(offset + length))
681 .to_utf8_code_units_in(AssumeUnder4GB, final_string),
682 );
683 &final_string[start..end]
684 };
685 assert_eq!(map_substring(0, 1), "A");
686 assert_eq!(map_substring(0, 2), "ASS");
687 assert_eq!(map_substring(0, 3), "ASS\u{3a5}\u{308}\u{301}");
688 assert_eq!(map_substring(0, 4), "ASS\u{3a5}\u{308}\u{301}B");
689 assert_eq!(map_substring(1, 1), "SS");
690}
691
692#[test]
693fn test_offsetmap_basic_collapse() {
694 let _original_string = " aaa b \nc";
695 let final_string = "aaa b\nc";
696
697 let mut offset_map = OffsetMap::default();
698 offset_map.push_iteration(&CharacterTransformIteration::collapse(
699 Utf32CodeUnits(2),
700 None,
701 ));
702 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
703 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
704 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('a'));
705 assert_eq!(
706 offset_map.known_positions.len(),
707 2,
708 "Consecutive one-to-one mappings are merged"
709 );
710
711 offset_map.push_iteration(&CharacterTransformIteration::collapse(
712 Utf32CodeUnits(2),
713 Some(' '),
714 ));
715 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('b'));
716 offset_map.push_iteration(&CharacterTransformIteration::collapse(
717 Utf32CodeUnits(2),
718 Some('\n'),
719 ));
720 offset_map.push_iteration(&CharacterTransformIteration::one_to_one('c'));
721
722 assert_eq!(offset_map.map(Utf32CodeUnits(0)).0, 0);
723 assert_eq!(offset_map.map(Utf32CodeUnits(1)).0, 0);
724 assert_eq!(offset_map.map(Utf32CodeUnits(2)).0, 0);
725 assert_eq!(offset_map.map(Utf32CodeUnits(3)).0, 1);
726 assert_eq!(offset_map.map(Utf32CodeUnits(4)).0, 2);
727 assert_eq!(offset_map.map(Utf32CodeUnits(5)).0, 3);
728 assert_eq!(offset_map.map(Utf32CodeUnits(6)).0, 4);
730 assert_eq!(offset_map.map(Utf32CodeUnits(7)).0, 4);
731 assert_eq!(offset_map.map(Utf32CodeUnits(8)).0, 5);
732 assert_eq!(offset_map.map(Utf32CodeUnits(9)).0, 6);
734 assert_eq!(offset_map.map(Utf32CodeUnits(10)).0, 6);
735 assert_eq!(offset_map.map(Utf32CodeUnits(11)).0, 7);
736
737 assert_eq!(offset_map.map(Utf32CodeUnits(12)).0, 7);
740 assert_eq!(offset_map.map(Utf32CodeUnits(100)).0, 7);
741
742 let map_substring = |offset: u32, length: u32| {
743 let start = usize::from(offset_map.map(Utf32CodeUnits(offset)));
744 let end = usize::from(offset_map.map(Utf32CodeUnits(offset + length)));
745 &final_string[start..end]
746 };
747 assert_eq!(map_substring(0, 1), "");
748 assert_eq!(map_substring(0, 3), "a");
749 assert_eq!(map_substring(0, 5), "aaa");
750 assert_eq!(map_substring(0, 6), "aaa ");
751 assert_eq!(map_substring(0, 7), "aaa ");
752 assert_eq!(map_substring(0, 8), "aaa b");
753 assert_eq!(map_substring(0, 11), "aaa b\nc");
754}