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