1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for text properties.
6 
7 use crate::parser::{Parse, ParserContext};
8 use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
9 use crate::values::computed::text::LineHeight as ComputedLineHeight;
10 use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
11 use crate::values::computed::text::TextOverflow as ComputedTextOverflow;
12 use crate::values::computed::{Context, ToComputedValue};
13 use crate::values::generics::text::InitialLetter as GenericInitialLetter;
14 use crate::values::generics::text::LineHeight as GenericLineHeight;
15 use crate::values::generics::text::{GenericTextDecorationLength, Spacing};
16 use crate::values::specified::length::NonNegativeLengthPercentage;
17 use crate::values::specified::length::{FontRelativeLength, Length};
18 use crate::values::specified::length::{LengthPercentage, NoCalcLength};
19 use crate::values::specified::{AllowQuirks, Integer, NonNegativeNumber, Number};
20 use cssparser::{Parser, Token};
21 use selectors::parser::SelectorParseErrorKind;
22 use std::fmt::{self, Write};
23 use style_traits::values::SequenceWriter;
24 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
25 use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
26 use unicode_segmentation::UnicodeSegmentation;
27 
28 /// A specified type for the `initial-letter` property.
29 pub type InitialLetter = GenericInitialLetter<Number, Integer>;
30 
31 /// A specified value for the `letter-spacing` property.
32 pub type LetterSpacing = Spacing<Length>;
33 
34 /// A specified value for the `word-spacing` property.
35 pub type WordSpacing = Spacing<LengthPercentage>;
36 
37 /// A specified value for the `line-height` property.
38 pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>;
39 
40 /// A value for the `hyphenate-character` property.
41 #[derive(
42     Clone,
43     Debug,
44     MallocSizeOf,
45     Parse,
46     PartialEq,
47     SpecifiedValueInfo,
48     ToComputedValue,
49     ToCss,
50     ToResolvedValue,
51     ToShmem,
52 )]
53 #[repr(C, u8)]
54 pub enum HyphenateCharacter {
55     /// `auto`
56     Auto,
57     /// `<string>`
58     String(crate::OwnedStr),
59 }
60 
61 impl Parse for InitialLetter {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>62     fn parse<'i, 't>(
63         context: &ParserContext,
64         input: &mut Parser<'i, 't>,
65     ) -> Result<Self, ParseError<'i>> {
66         if input
67             .try_parse(|i| i.expect_ident_matching("normal"))
68             .is_ok()
69         {
70             return Ok(GenericInitialLetter::Normal);
71         }
72         let size = Number::parse_at_least_one(context, input)?;
73         let sink = input
74             .try_parse(|i| Integer::parse_positive(context, i))
75             .ok();
76         Ok(GenericInitialLetter::Specified(size, sink))
77     }
78 }
79 
80 impl Parse for LetterSpacing {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>81     fn parse<'i, 't>(
82         context: &ParserContext,
83         input: &mut Parser<'i, 't>,
84     ) -> Result<Self, ParseError<'i>> {
85         Spacing::parse_with(context, input, |c, i| {
86             Length::parse_quirky(c, i, AllowQuirks::Yes)
87         })
88     }
89 }
90 
91 impl Parse for WordSpacing {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>92     fn parse<'i, 't>(
93         context: &ParserContext,
94         input: &mut Parser<'i, 't>,
95     ) -> Result<Self, ParseError<'i>> {
96         Spacing::parse_with(context, input, |c, i| {
97             LengthPercentage::parse_quirky(c, i, AllowQuirks::Yes)
98         })
99     }
100 }
101 
102 impl ToComputedValue for LineHeight {
103     type ComputedValue = ComputedLineHeight;
104 
105     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue106     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
107         use crate::values::specified::length::FontBaseSize;
108         match *self {
109             GenericLineHeight::Normal => GenericLineHeight::Normal,
110             #[cfg(feature = "gecko")]
111             GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
112             GenericLineHeight::Number(number) => {
113                 GenericLineHeight::Number(number.to_computed_value(context))
114             },
115             GenericLineHeight::Length(ref non_negative_lp) => {
116                 let result = match non_negative_lp.0 {
117                     LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => {
118                         context.maybe_zoom_text(abs.to_computed_value(context))
119                     },
120                     LengthPercentage::Length(ref length) => length.to_computed_value(context),
121                     LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0)
122                         .to_computed_value(context, FontBaseSize::CurrentStyle),
123                     LengthPercentage::Calc(ref calc) => {
124                         let computed_calc =
125                             calc.to_computed_value_zoomed(context, FontBaseSize::CurrentStyle);
126                         let base = context.style().get_font().clone_font_size().size();
127                         computed_calc.resolve(base)
128                     },
129                 };
130                 GenericLineHeight::Length(result.into())
131             },
132         }
133     }
134 
135     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self136     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
137         match *computed {
138             GenericLineHeight::Normal => GenericLineHeight::Normal,
139             #[cfg(feature = "gecko")]
140             GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight,
141             GenericLineHeight::Number(ref number) => {
142                 GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number))
143             },
144             GenericLineHeight::Length(ref length) => {
145                 GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
146             },
147         }
148     }
149 }
150 
151 /// A generic value for the `text-overflow` property.
152 #[derive(
153     Clone,
154     Debug,
155     Eq,
156     MallocSizeOf,
157     PartialEq,
158     Parse,
159     SpecifiedValueInfo,
160     ToComputedValue,
161     ToCss,
162     ToResolvedValue,
163     ToShmem,
164 )]
165 #[repr(C, u8)]
166 pub enum TextOverflowSide {
167     /// Clip inline content.
168     Clip,
169     /// Render ellipsis to represent clipped inline content.
170     Ellipsis,
171     /// Render a given string to represent clipped inline content.
172     String(crate::OwnedStr),
173 }
174 
175 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
176 /// text-overflow. Specifies rendering when inline content overflows its line box edge.
177 pub struct TextOverflow {
178     /// First value. Applies to end line box edge if no second is supplied; line-left edge otherwise.
179     pub first: TextOverflowSide,
180     /// Second value. Applies to the line-right edge if supplied.
181     pub second: Option<TextOverflowSide>,
182 }
183 
184 impl Parse for TextOverflow {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<TextOverflow, ParseError<'i>>185     fn parse<'i, 't>(
186         context: &ParserContext,
187         input: &mut Parser<'i, 't>,
188     ) -> Result<TextOverflow, ParseError<'i>> {
189         let first = TextOverflowSide::parse(context, input)?;
190         let second = input
191             .try_parse(|input| TextOverflowSide::parse(context, input))
192             .ok();
193         Ok(TextOverflow { first, second })
194     }
195 }
196 
197 impl ToComputedValue for TextOverflow {
198     type ComputedValue = ComputedTextOverflow;
199 
200     #[inline]
to_computed_value(&self, _context: &Context) -> Self::ComputedValue201     fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
202         if let Some(ref second) = self.second {
203             Self::ComputedValue {
204                 first: self.first.clone(),
205                 second: second.clone(),
206                 sides_are_logical: false,
207             }
208         } else {
209             Self::ComputedValue {
210                 first: TextOverflowSide::Clip,
211                 second: self.first.clone(),
212                 sides_are_logical: true,
213             }
214         }
215     }
216 
217     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self218     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
219         if computed.sides_are_logical {
220             assert_eq!(computed.first, TextOverflowSide::Clip);
221             TextOverflow {
222                 first: computed.second.clone(),
223                 second: None,
224             }
225         } else {
226             TextOverflow {
227                 first: computed.first.clone(),
228                 second: Some(computed.second.clone()),
229             }
230         }
231     }
232 }
233 
234 bitflags! {
235     #[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
236     #[value_info(other_values = "none,underline,overline,line-through,blink")]
237     #[repr(C)]
238     /// Specified keyword values for the text-decoration-line property.
239     pub struct TextDecorationLine: u8 {
240         /// No text decoration line is specified.
241         const NONE = 0;
242         /// underline
243         const UNDERLINE = 1 << 0;
244         /// overline
245         const OVERLINE = 1 << 1;
246         /// line-through
247         const LINE_THROUGH = 1 << 2;
248         /// blink
249         const BLINK = 1 << 3;
250         /// Only set by presentation attributes
251         ///
252         /// Setting this will mean that text-decorations use the color
253         /// specified by `color` in quirks mode.
254         ///
255         /// For example, this gives <a href=foo><font color="red">text</font></a>
256         /// a red text decoration
257         #[cfg(feature = "gecko")]
258         const COLOR_OVERRIDE = 0x10;
259     }
260 }
261 
262 impl Default for TextDecorationLine {
default() -> Self263     fn default() -> Self {
264         TextDecorationLine::NONE
265     }
266 }
267 
268 impl Parse for TextDecorationLine {
269     /// none | [ underline || overline || line-through || blink ]
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>270     fn parse<'i, 't>(
271         _context: &ParserContext,
272         input: &mut Parser<'i, 't>,
273     ) -> Result<Self, ParseError<'i>> {
274         let mut result = TextDecorationLine::empty();
275 
276         // NOTE(emilio): this loop has this weird structure because we run this
277         // code to parse the text-decoration shorthand as well, so we need to
278         // ensure we don't return an error if we don't consume the whole thing
279         // because we find an invalid identifier or other kind of token.
280         loop {
281             let flag: Result<_, ParseError<'i>> = input.try_parse(|input| {
282                 let flag = try_match_ident_ignore_ascii_case! { input,
283                     "none" if result.is_empty() => TextDecorationLine::NONE,
284                     "underline" => TextDecorationLine::UNDERLINE,
285                     "overline" => TextDecorationLine::OVERLINE,
286                     "line-through" => TextDecorationLine::LINE_THROUGH,
287                     "blink" => TextDecorationLine::BLINK,
288                 };
289 
290                 Ok(flag)
291             });
292 
293             let flag = match flag {
294                 Ok(flag) => flag,
295                 Err(..) => break,
296             };
297 
298             if flag.is_empty() {
299                 return Ok(TextDecorationLine::NONE);
300             }
301 
302             if result.contains(flag) {
303                 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
304             }
305 
306             result.insert(flag)
307         }
308 
309         if !result.is_empty() {
310             Ok(result)
311         } else {
312             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
313         }
314     }
315 }
316 
317 impl ToCss for TextDecorationLine {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,318     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
319     where
320         W: Write,
321     {
322         if self.is_empty() {
323             return dest.write_str("none");
324         }
325 
326         #[cfg(feature = "gecko")]
327         {
328             if *self == TextDecorationLine::COLOR_OVERRIDE {
329                 return Ok(());
330             }
331         }
332 
333         let mut writer = SequenceWriter::new(dest, " ");
334         let mut any = false;
335 
336         macro_rules! maybe_write {
337             ($ident:ident => $str:expr) => {
338                 if self.contains(TextDecorationLine::$ident) {
339                     any = true;
340                     writer.raw_item($str)?;
341                 }
342             };
343         }
344 
345         maybe_write!(UNDERLINE => "underline");
346         maybe_write!(OVERLINE => "overline");
347         maybe_write!(LINE_THROUGH => "line-through");
348         maybe_write!(BLINK => "blink");
349 
350         debug_assert!(any);
351 
352         Ok(())
353     }
354 }
355 
356 impl TextDecorationLine {
357     #[inline]
358     /// Returns the initial value of text-decoration-line
none() -> Self359     pub fn none() -> Self {
360         TextDecorationLine::NONE
361     }
362 }
363 
364 #[derive(
365     Clone,
366     Copy,
367     Debug,
368     Eq,
369     MallocSizeOf,
370     PartialEq,
371     SpecifiedValueInfo,
372     ToComputedValue,
373     ToResolvedValue,
374     ToShmem,
375 )]
376 #[repr(C)]
377 /// Specified value of the text-transform property, stored in two parts:
378 /// the case-related transforms (mutually exclusive, only one may be in effect), and others (non-exclusive).
379 pub struct TextTransform {
380     /// Case transform, if any.
381     pub case_: TextTransformCase,
382     /// Non-case transforms.
383     pub other_: TextTransformOther,
384 }
385 
386 impl TextTransform {
387     #[inline]
388     /// Returns the initial value of text-transform
none() -> Self389     pub fn none() -> Self {
390         TextTransform {
391             case_: TextTransformCase::None,
392             other_: TextTransformOther::empty(),
393         }
394     }
395     #[inline]
396     /// Returns whether the value is 'none'
is_none(&self) -> bool397     pub fn is_none(&self) -> bool {
398         self.case_ == TextTransformCase::None && self.other_.is_empty()
399     }
400 }
401 
402 impl Parse for TextTransform {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>403     fn parse<'i, 't>(
404         _context: &ParserContext,
405         input: &mut Parser<'i, 't>,
406     ) -> Result<Self, ParseError<'i>> {
407         let mut result = TextTransform::none();
408 
409         // Case keywords are mutually exclusive; other transforms may co-occur.
410         loop {
411             let location = input.current_source_location();
412             let ident = match input.next() {
413                 Ok(&Token::Ident(ref ident)) => ident,
414                 Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
415                 Err(..) => break,
416             };
417 
418             match_ignore_ascii_case! { ident,
419                 "none" if result.is_none() => {
420                     return Ok(result);
421                 },
422                 "uppercase" if result.case_ == TextTransformCase::None => {
423                     result.case_ = TextTransformCase::Uppercase
424                 },
425                 "lowercase" if result.case_ == TextTransformCase::None => {
426                     result.case_ = TextTransformCase::Lowercase
427                 },
428                 "capitalize" if result.case_ == TextTransformCase::None => {
429                     result.case_ = TextTransformCase::Capitalize
430                 },
431                 "full-width" if !result.other_.intersects(TextTransformOther::FULL_WIDTH) => {
432                     result.other_.insert(TextTransformOther::FULL_WIDTH)
433                 },
434                 "full-size-kana" if !result.other_.intersects(TextTransformOther::FULL_SIZE_KANA) => {
435                     result.other_.insert(TextTransformOther::FULL_SIZE_KANA)
436                 },
437                 _ => return Err(location.new_custom_error(
438                     SelectorParseErrorKind::UnexpectedIdent(ident.clone())
439                 )),
440             }
441         }
442 
443         if result.is_none() {
444             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
445         } else {
446             Ok(result)
447         }
448     }
449 }
450 
451 impl ToCss for TextTransform {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,452     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
453     where
454         W: Write,
455     {
456         if self.is_none() {
457             return dest.write_str("none");
458         }
459 
460         if self.case_ != TextTransformCase::None {
461             self.case_.to_css(dest)?;
462             if !self.other_.is_empty() {
463                 dest.write_str(" ")?;
464             }
465         }
466 
467         self.other_.to_css(dest)
468     }
469 }
470 
471 #[derive(
472     Clone,
473     Copy,
474     Debug,
475     Eq,
476     MallocSizeOf,
477     PartialEq,
478     SpecifiedValueInfo,
479     ToComputedValue,
480     ToCss,
481     ToResolvedValue,
482     ToShmem,
483 )]
484 #[repr(C)]
485 /// Specified keyword values for case transforms in the text-transform property. (These are exclusive.)
486 pub enum TextTransformCase {
487     /// No case transform.
488     None,
489     /// All uppercase.
490     Uppercase,
491     /// All lowercase.
492     Lowercase,
493     /// Capitalize each word.
494     Capitalize,
495 }
496 
497 bitflags! {
498     #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
499     #[value_info(other_values = "none,full-width,full-size-kana")]
500     #[repr(C)]
501     /// Specified keyword values for non-case transforms in the text-transform property. (Non-exclusive.)
502     pub struct TextTransformOther: u8 {
503         /// full-width
504         const FULL_WIDTH = 1 << 0;
505         /// full-size-kana
506         const FULL_SIZE_KANA = 1 << 1;
507     }
508 }
509 
510 impl ToCss for TextTransformOther {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,511     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
512     where
513         W: Write,
514     {
515         let mut writer = SequenceWriter::new(dest, " ");
516         let mut any = false;
517         macro_rules! maybe_write {
518             ($ident:ident => $str:expr) => {
519                 if self.contains(TextTransformOther::$ident) {
520                     writer.raw_item($str)?;
521                     any = true;
522                 }
523             };
524         }
525 
526         maybe_write!(FULL_WIDTH => "full-width");
527         maybe_write!(FULL_SIZE_KANA => "full-size-kana");
528 
529         debug_assert!(any || self.is_empty());
530 
531         Ok(())
532     }
533 }
534 
535 /// Specified and computed value of text-align-last.
536 #[derive(
537     Clone,
538     Copy,
539     Debug,
540     Eq,
541     FromPrimitive,
542     Hash,
543     MallocSizeOf,
544     Parse,
545     PartialEq,
546     SpecifiedValueInfo,
547     ToComputedValue,
548     ToCss,
549     ToResolvedValue,
550     ToShmem,
551 )]
552 #[allow(missing_docs)]
553 #[repr(u8)]
554 pub enum TextAlignLast {
555     Auto,
556     Start,
557     End,
558     Left,
559     Right,
560     Center,
561     Justify,
562 }
563 
564 /// Specified value of text-align keyword value.
565 #[derive(
566     Clone,
567     Copy,
568     Debug,
569     Eq,
570     FromPrimitive,
571     Hash,
572     MallocSizeOf,
573     Parse,
574     PartialEq,
575     SpecifiedValueInfo,
576     ToComputedValue,
577     ToCss,
578     ToResolvedValue,
579     ToShmem,
580 )]
581 #[allow(missing_docs)]
582 #[repr(u8)]
583 pub enum TextAlignKeyword {
584     Start,
585     Left,
586     Right,
587     Center,
588     #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
589     Justify,
590     #[css(skip)]
591     #[cfg(feature = "gecko")]
592     Char,
593     End,
594     #[cfg(feature = "gecko")]
595     MozCenter,
596     #[cfg(feature = "gecko")]
597     MozLeft,
598     #[cfg(feature = "gecko")]
599     MozRight,
600     #[cfg(feature = "servo-layout-2013")]
601     ServoCenter,
602     #[cfg(feature = "servo-layout-2013")]
603     ServoLeft,
604     #[cfg(feature = "servo-layout-2013")]
605     ServoRight,
606 }
607 
608 /// Specified value of text-align property.
609 #[derive(
610     Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
611 )]
612 pub enum TextAlign {
613     /// Keyword value of text-align property.
614     Keyword(TextAlignKeyword),
615     /// `match-parent` value of text-align property. It has a different handling
616     /// unlike other keywords.
617     #[cfg(feature = "gecko")]
618     MatchParent,
619     /// `MozCenterOrInherit` value of text-align property. It cannot be parsed,
620     /// only set directly on the elements and it has a different handling
621     /// unlike other values.
622     #[cfg(feature = "gecko")]
623     #[css(skip)]
624     MozCenterOrInherit,
625 }
626 
627 impl ToComputedValue for TextAlign {
628     type ComputedValue = TextAlignKeyword;
629 
630     #[inline]
to_computed_value(&self, _context: &Context) -> Self::ComputedValue631     fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
632         match *self {
633             TextAlign::Keyword(key) => key,
634             #[cfg(feature = "gecko")]
635             TextAlign::MatchParent => {
636                 // on the root <html> element we should still respect the dir
637                 // but the parent dir of that element is LTR even if it's <html dir=rtl>
638                 // and will only be RTL if certain prefs have been set.
639                 // In that case, the default behavior here will set it to left,
640                 // but we want to set it to right -- instead set it to the default (`start`),
641                 // which will do the right thing in this case (but not the general case)
642                 if _context.builder.is_root_element {
643                     return TextAlignKeyword::Start;
644                 }
645                 let parent = _context
646                     .builder
647                     .get_parent_inherited_text()
648                     .clone_text_align();
649                 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
650                 match (parent, ltr) {
651                     (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
652                     (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
653                     (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
654                     (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
655                     _ => parent,
656                 }
657             },
658             #[cfg(feature = "gecko")]
659             TextAlign::MozCenterOrInherit => {
660                 let parent = _context
661                     .builder
662                     .get_parent_inherited_text()
663                     .clone_text_align();
664                 if parent == TextAlignKeyword::Start {
665                     TextAlignKeyword::Center
666                 } else {
667                     parent
668                 }
669             },
670         }
671     }
672 
673     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self674     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
675         TextAlign::Keyword(*computed)
676     }
677 }
678 
fill_mode_is_default_and_shape_exists( fill: &TextEmphasisFillMode, shape: &Option<TextEmphasisShapeKeyword>, ) -> bool679 fn fill_mode_is_default_and_shape_exists(
680     fill: &TextEmphasisFillMode,
681     shape: &Option<TextEmphasisShapeKeyword>,
682 ) -> bool {
683     shape.is_some() && fill.is_filled()
684 }
685 
686 /// Specified value of text-emphasis-style property.
687 ///
688 /// https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style
689 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
690 #[allow(missing_docs)]
691 pub enum TextEmphasisStyle {
692     /// [ <fill> || <shape> ]
693     Keyword {
694         #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
695         fill: TextEmphasisFillMode,
696         shape: Option<TextEmphasisShapeKeyword>,
697     },
698     /// `none`
699     None,
700     /// `<string>` (of which only the first grapheme cluster will be used).
701     String(crate::OwnedStr),
702 }
703 
704 /// Fill mode for the text-emphasis-style property
705 #[derive(
706     Clone,
707     Copy,
708     Debug,
709     MallocSizeOf,
710     Parse,
711     PartialEq,
712     SpecifiedValueInfo,
713     ToCss,
714     ToComputedValue,
715     ToResolvedValue,
716     ToShmem,
717 )]
718 #[repr(u8)]
719 pub enum TextEmphasisFillMode {
720     /// `filled`
721     Filled,
722     /// `open`
723     Open,
724 }
725 
726 impl TextEmphasisFillMode {
727     /// Whether the value is `filled`.
728     #[inline]
is_filled(&self) -> bool729     pub fn is_filled(&self) -> bool {
730         matches!(*self, TextEmphasisFillMode::Filled)
731     }
732 }
733 
734 /// Shape keyword for the text-emphasis-style property
735 #[derive(
736     Clone,
737     Copy,
738     Debug,
739     Eq,
740     MallocSizeOf,
741     Parse,
742     PartialEq,
743     SpecifiedValueInfo,
744     ToCss,
745     ToComputedValue,
746     ToResolvedValue,
747     ToShmem,
748 )]
749 #[repr(u8)]
750 pub enum TextEmphasisShapeKeyword {
751     /// `dot`
752     Dot,
753     /// `circle`
754     Circle,
755     /// `double-circle`
756     DoubleCircle,
757     /// `triangle`
758     Triangle,
759     /// `sesame`
760     Sesame,
761 }
762 
763 impl ToComputedValue for TextEmphasisStyle {
764     type ComputedValue = ComputedTextEmphasisStyle;
765 
766     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue767     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
768         match *self {
769             TextEmphasisStyle::Keyword { fill, shape } => {
770                 let shape = shape.unwrap_or_else(|| {
771                     // FIXME(emilio, bug 1572958): This should set the
772                     // rule_cache_conditions properly.
773                     //
774                     // Also should probably use WritingMode::is_vertical rather
775                     // than the computed value of the `writing-mode` property.
776                     if context.style().get_inherited_box().clone_writing_mode() ==
777                         SpecifiedWritingMode::HorizontalTb
778                     {
779                         TextEmphasisShapeKeyword::Circle
780                     } else {
781                         TextEmphasisShapeKeyword::Sesame
782                     }
783                 });
784                 ComputedTextEmphasisStyle::Keyword { fill, shape }
785             },
786             TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
787             TextEmphasisStyle::String(ref s) => {
788                 // Passing `true` to iterate over extended grapheme clusters, following
789                 // recommendation at http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
790                 //
791                 // FIXME(emilio): Doing this at computed value time seems wrong.
792                 // The spec doesn't say that this should be a computed-value
793                 // time operation. This is observable from getComputedStyle().
794                 let s = s.graphemes(true).next().unwrap_or("").to_string();
795                 ComputedTextEmphasisStyle::String(s.into())
796             },
797         }
798     }
799 
800     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self801     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
802         match *computed {
803             ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
804                 fill,
805                 shape: Some(shape),
806             },
807             ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
808             ComputedTextEmphasisStyle::String(ref string) => {
809                 TextEmphasisStyle::String(string.clone())
810             },
811         }
812     }
813 }
814 
815 impl Parse for TextEmphasisStyle {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>816     fn parse<'i, 't>(
817         _context: &ParserContext,
818         input: &mut Parser<'i, 't>,
819     ) -> Result<Self, ParseError<'i>> {
820         if input
821             .try_parse(|input| input.expect_ident_matching("none"))
822             .is_ok()
823         {
824             return Ok(TextEmphasisStyle::None);
825         }
826 
827         if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
828             // Handle <string>
829             return Ok(TextEmphasisStyle::String(s.into()));
830         }
831 
832         // Handle a pair of keywords
833         let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
834         let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
835         if shape.is_none() {
836             shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
837         }
838 
839         if shape.is_none() && fill.is_none() {
840             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
841         }
842 
843         // If a shape keyword is specified but neither filled nor open is
844         // specified, filled is assumed.
845         let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
846 
847         // We cannot do the same because the default `<shape>` depends on the
848         // computed writing-mode.
849         Ok(TextEmphasisStyle::Keyword { fill, shape })
850     }
851 }
852 
853 /// The allowed horizontal values for the `text-emphasis-position` property.
854 #[derive(
855     Clone,
856     Copy,
857     Debug,
858     Eq,
859     MallocSizeOf,
860     Parse,
861     PartialEq,
862     SpecifiedValueInfo,
863     ToComputedValue,
864     ToCss,
865     ToResolvedValue,
866     ToShmem,
867 )]
868 pub enum TextEmphasisHorizontalWritingModeValue {
869     /// Draw marks over the text in horizontal writing mode.
870     Over,
871     /// Draw marks under the text in horizontal writing mode.
872     Under,
873 }
874 
875 /// The allowed vertical values for the `text-emphasis-position` property.
876 #[derive(
877     Clone,
878     Copy,
879     Debug,
880     Eq,
881     MallocSizeOf,
882     Parse,
883     PartialEq,
884     SpecifiedValueInfo,
885     ToComputedValue,
886     ToCss,
887     ToResolvedValue,
888     ToShmem,
889 )]
890 pub enum TextEmphasisVerticalWritingModeValue {
891     /// Draws marks to the right of the text in vertical writing mode.
892     Right,
893     /// Draw marks to the left of the text in vertical writing mode.
894     Left,
895 }
896 
897 /// Specified value of `text-emphasis-position` property.
898 #[derive(
899     Clone,
900     Copy,
901     Debug,
902     MallocSizeOf,
903     PartialEq,
904     SpecifiedValueInfo,
905     ToComputedValue,
906     ToCss,
907     ToResolvedValue,
908     ToShmem,
909 )]
910 pub struct TextEmphasisPosition(
911     pub TextEmphasisHorizontalWritingModeValue,
912     pub TextEmphasisVerticalWritingModeValue,
913 );
914 
915 impl TextEmphasisPosition {
916     #[inline]
917     /// Returns the initial value of `text-emphasis-position`
over_right() -> Self918     pub fn over_right() -> Self {
919         TextEmphasisPosition(
920             TextEmphasisHorizontalWritingModeValue::Over,
921             TextEmphasisVerticalWritingModeValue::Right,
922         )
923     }
924 
925     #[cfg(feature = "gecko")]
926     /// Converts an enumerated value coming from Gecko to a `TextEmphasisPosition`.
from_gecko_keyword(kw: u32) -> Self927     pub fn from_gecko_keyword(kw: u32) -> Self {
928         use crate::gecko_bindings::structs;
929 
930         let vert = if kw & structs::NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT != 0 {
931             TextEmphasisVerticalWritingModeValue::Right
932         } else {
933             debug_assert!(kw & structs::NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT != 0);
934             TextEmphasisVerticalWritingModeValue::Left
935         };
936         let horiz = if kw & structs::NS_STYLE_TEXT_EMPHASIS_POSITION_OVER != 0 {
937             TextEmphasisHorizontalWritingModeValue::Over
938         } else {
939             debug_assert!(kw & structs::NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER != 0);
940             TextEmphasisHorizontalWritingModeValue::Under
941         };
942         TextEmphasisPosition(horiz, vert)
943     }
944 }
945 
946 impl Parse for TextEmphasisPosition {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>947     fn parse<'i, 't>(
948         _context: &ParserContext,
949         input: &mut Parser<'i, 't>,
950     ) -> Result<Self, ParseError<'i>> {
951         if let Ok(horizontal) =
952             input.try_parse(|input| TextEmphasisHorizontalWritingModeValue::parse(input))
953         {
954             let vertical = TextEmphasisVerticalWritingModeValue::parse(input)?;
955             Ok(TextEmphasisPosition(horizontal, vertical))
956         } else {
957             let vertical = TextEmphasisVerticalWritingModeValue::parse(input)?;
958             let horizontal = TextEmphasisHorizontalWritingModeValue::parse(input)?;
959             Ok(TextEmphasisPosition(horizontal, vertical))
960         }
961     }
962 }
963 
964 #[cfg(feature = "gecko")]
965 impl From<u8> for TextEmphasisPosition {
from(bits: u8) -> Self966     fn from(bits: u8) -> Self {
967         TextEmphasisPosition::from_gecko_keyword(bits as u32)
968     }
969 }
970 
971 #[cfg(feature = "gecko")]
972 impl From<TextEmphasisPosition> for u8 {
from(v: TextEmphasisPosition) -> u8973     fn from(v: TextEmphasisPosition) -> u8 {
974         use crate::gecko_bindings::structs;
975 
976         let mut result = match v.0 {
977             TextEmphasisHorizontalWritingModeValue::Over => {
978                 structs::NS_STYLE_TEXT_EMPHASIS_POSITION_OVER
979             },
980             TextEmphasisHorizontalWritingModeValue::Under => {
981                 structs::NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER
982             },
983         };
984         match v.1 {
985             TextEmphasisVerticalWritingModeValue::Right => {
986                 result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT;
987             },
988             TextEmphasisVerticalWritingModeValue::Left => {
989                 result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT;
990             },
991         };
992         result as u8
993     }
994 }
995 
996 /// Values for the `word-break` property.
997 #[repr(u8)]
998 #[derive(
999     Clone,
1000     Copy,
1001     Debug,
1002     Eq,
1003     MallocSizeOf,
1004     Parse,
1005     PartialEq,
1006     SpecifiedValueInfo,
1007     ToComputedValue,
1008     ToCss,
1009     ToResolvedValue,
1010     ToShmem,
1011 )]
1012 #[allow(missing_docs)]
1013 pub enum WordBreak {
1014     Normal,
1015     BreakAll,
1016     KeepAll,
1017     /// The break-word value, needed for compat.
1018     ///
1019     /// Specifying `word-break: break-word` makes `overflow-wrap` behave as
1020     /// `anywhere`, and `word-break` behave like `normal`.
1021     #[cfg(feature = "gecko")]
1022     BreakWord,
1023 }
1024 
1025 /// Values for the `text-justify` CSS property.
1026 #[repr(u8)]
1027 #[derive(
1028     Clone,
1029     Copy,
1030     Debug,
1031     Eq,
1032     MallocSizeOf,
1033     Parse,
1034     PartialEq,
1035     SpecifiedValueInfo,
1036     ToComputedValue,
1037     ToCss,
1038     ToResolvedValue,
1039     ToShmem,
1040 )]
1041 #[allow(missing_docs)]
1042 pub enum TextJustify {
1043     Auto,
1044     None,
1045     InterWord,
1046     // See https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute
1047     // and https://github.com/w3c/csswg-drafts/issues/6156 for the alias.
1048     #[parse(aliases = "distribute")]
1049     InterCharacter,
1050 }
1051 
1052 /// Values for the `-moz-control-character-visibility` CSS property.
1053 #[repr(u8)]
1054 #[derive(
1055     Clone,
1056     Copy,
1057     Debug,
1058     Eq,
1059     MallocSizeOf,
1060     Parse,
1061     PartialEq,
1062     SpecifiedValueInfo,
1063     ToComputedValue,
1064     ToCss,
1065     ToResolvedValue,
1066     ToShmem,
1067 )]
1068 #[allow(missing_docs)]
1069 pub enum MozControlCharacterVisibility {
1070     Hidden,
1071     Visible,
1072 }
1073 
1074 impl Default for MozControlCharacterVisibility {
default() -> Self1075     fn default() -> Self {
1076         if static_prefs::pref!("layout.css.control-characters.visible") {
1077             Self::Visible
1078         } else {
1079             Self::Hidden
1080         }
1081     }
1082 }
1083 
1084 /// Values for the `line-break` property.
1085 #[repr(u8)]
1086 #[derive(
1087     Clone,
1088     Copy,
1089     Debug,
1090     Eq,
1091     MallocSizeOf,
1092     Parse,
1093     PartialEq,
1094     SpecifiedValueInfo,
1095     ToComputedValue,
1096     ToCss,
1097     ToResolvedValue,
1098     ToShmem,
1099 )]
1100 #[allow(missing_docs)]
1101 pub enum LineBreak {
1102     Auto,
1103     Loose,
1104     Normal,
1105     Strict,
1106     Anywhere,
1107 }
1108 
1109 /// Values for the `overflow-wrap` property.
1110 #[repr(u8)]
1111 #[derive(
1112     Clone,
1113     Copy,
1114     Debug,
1115     Eq,
1116     MallocSizeOf,
1117     Parse,
1118     PartialEq,
1119     SpecifiedValueInfo,
1120     ToComputedValue,
1121     ToCss,
1122     ToResolvedValue,
1123     ToShmem,
1124 )]
1125 #[allow(missing_docs)]
1126 pub enum OverflowWrap {
1127     Normal,
1128     BreakWord,
1129     Anywhere,
1130 }
1131 
1132 /// Implements text-decoration-skip-ink which takes the keywords auto | none | all
1133 ///
1134 /// https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property
1135 #[repr(u8)]
1136 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1137 #[derive(
1138     Clone,
1139     Copy,
1140     Debug,
1141     Eq,
1142     MallocSizeOf,
1143     Parse,
1144     PartialEq,
1145     SpecifiedValueInfo,
1146     ToComputedValue,
1147     ToCss,
1148     ToResolvedValue,
1149     ToShmem,
1150 )]
1151 #[allow(missing_docs)]
1152 pub enum TextDecorationSkipInk {
1153     Auto,
1154     None,
1155     All,
1156 }
1157 
1158 /// Implements type for `text-decoration-thickness` property
1159 pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1160 
1161 impl TextDecorationLength {
1162     /// `Auto` value.
1163     #[inline]
auto() -> Self1164     pub fn auto() -> Self {
1165         GenericTextDecorationLength::Auto
1166     }
1167 
1168     /// Whether this is the `Auto` value.
1169     #[inline]
is_auto(&self) -> bool1170     pub fn is_auto(&self) -> bool {
1171         matches!(*self, GenericTextDecorationLength::Auto)
1172     }
1173 }
1174 
1175 bitflags! {
1176     #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
1177     #[value_info(other_values = "auto,from-font,under,left,right")]
1178     #[repr(C)]
1179     /// Specified keyword values for the text-underline-position property.
1180     /// (Non-exclusive, but not all combinations are allowed: the spec grammar gives
1181     /// `auto | [ from-font | under ] || [ left | right ]`.)
1182     /// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property
1183     pub struct TextUnderlinePosition: u8 {
1184         /// Use automatic positioning below the alphabetic baseline.
1185         const AUTO = 0;
1186         /// Use underline position from the first available font.
1187         const FROM_FONT = 1 << 0;
1188         /// Below the glyph box.
1189         const UNDER = 1 << 1;
1190         /// In vertical mode, place to the left of the text.
1191         const LEFT = 1 << 2;
1192         /// In vertical mode, place to the right of the text.
1193         const RIGHT = 1 << 3;
1194     }
1195 }
1196 
1197 impl Parse for TextUnderlinePosition {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<TextUnderlinePosition, ParseError<'i>>1198     fn parse<'i, 't>(
1199         _context: &ParserContext,
1200         input: &mut Parser<'i, 't>,
1201     ) -> Result<TextUnderlinePosition, ParseError<'i>> {
1202         let mut result = TextUnderlinePosition::empty();
1203 
1204         loop {
1205             let location = input.current_source_location();
1206             let ident = match input.next() {
1207                 Ok(&Token::Ident(ref ident)) => ident,
1208                 Ok(other) => return Err(location.new_unexpected_token_error(other.clone())),
1209                 Err(..) => break,
1210             };
1211 
1212             match_ignore_ascii_case! { ident,
1213                 "auto" if result.is_empty() => {
1214                     return Ok(result);
1215                 },
1216                 "from-font" if !result.intersects(TextUnderlinePosition::FROM_FONT |
1217                                                   TextUnderlinePosition::UNDER) => {
1218                     result.insert(TextUnderlinePosition::FROM_FONT);
1219                 },
1220                 "under" if !result.intersects(TextUnderlinePosition::FROM_FONT |
1221                                               TextUnderlinePosition::UNDER) => {
1222                     result.insert(TextUnderlinePosition::UNDER);
1223                 },
1224                 "left" if !result.intersects(TextUnderlinePosition::LEFT |
1225                                              TextUnderlinePosition::RIGHT) => {
1226                     result.insert(TextUnderlinePosition::LEFT);
1227                 },
1228                 "right" if !result.intersects(TextUnderlinePosition::LEFT |
1229                                               TextUnderlinePosition::RIGHT) => {
1230                     result.insert(TextUnderlinePosition::RIGHT);
1231                 },
1232                 _ => return Err(location.new_custom_error(
1233                     SelectorParseErrorKind::UnexpectedIdent(ident.clone())
1234                 )),
1235             }
1236         }
1237 
1238         if !result.is_empty() {
1239             Ok(result)
1240         } else {
1241             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1242         }
1243     }
1244 }
1245 
1246 impl ToCss for TextUnderlinePosition {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,1247     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1248     where
1249         W: Write,
1250     {
1251         if self.is_empty() {
1252             return dest.write_str("auto");
1253         }
1254 
1255         let mut writer = SequenceWriter::new(dest, " ");
1256         let mut any = false;
1257 
1258         macro_rules! maybe_write {
1259             ($ident:ident => $str:expr) => {
1260                 if self.contains(TextUnderlinePosition::$ident) {
1261                     any = true;
1262                     writer.raw_item($str)?;
1263                 }
1264             };
1265         }
1266 
1267         maybe_write!(FROM_FONT => "from-font");
1268         maybe_write!(UNDER => "under");
1269         maybe_write!(LEFT => "left");
1270         maybe_write!(RIGHT => "right");
1271 
1272         debug_assert!(any);
1273 
1274         Ok(())
1275     }
1276 }
1277 
1278 /// Values for `ruby-position` property
1279 #[repr(u8)]
1280 #[derive(
1281     Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
1282 )]
1283 #[allow(missing_docs)]
1284 pub enum RubyPosition {
1285     AlternateOver,
1286     AlternateUnder,
1287     Over,
1288     Under,
1289 }
1290 
1291 impl Parse for RubyPosition {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<RubyPosition, ParseError<'i>>1292     fn parse<'i, 't>(
1293         _context: &ParserContext,
1294         input: &mut Parser<'i, 't>,
1295     ) -> Result<RubyPosition, ParseError<'i>> {
1296         // Parse alternate before
1297         let alternate = input
1298             .try_parse(|i| i.expect_ident_matching("alternate"))
1299             .is_ok();
1300         if alternate && input.is_exhausted() {
1301             return Ok(RubyPosition::AlternateOver);
1302         }
1303         // Parse over / under
1304         let over = try_match_ident_ignore_ascii_case! { input,
1305             "over" => true,
1306             "under" => false,
1307         };
1308         // Parse alternate after
1309         let alternate = alternate ||
1310             input
1311                 .try_parse(|i| i.expect_ident_matching("alternate"))
1312                 .is_ok();
1313 
1314         Ok(match (over, alternate) {
1315             (true, true) => RubyPosition::AlternateOver,
1316             (false, true) => RubyPosition::AlternateUnder,
1317             (true, false) => RubyPosition::Over,
1318             (false, false) => RubyPosition::Under,
1319         })
1320     }
1321 }
1322 
1323 impl ToCss for RubyPosition {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,1324     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1325     where
1326         W: Write,
1327     {
1328         dest.write_str(match self {
1329             RubyPosition::AlternateOver => "alternate",
1330             RubyPosition::AlternateUnder => "alternate under",
1331             RubyPosition::Over => "over",
1332             RubyPosition::Under => "under",
1333         })
1334     }
1335 }
1336 
1337 impl SpecifiedValueInfo for RubyPosition {
collect_completion_keywords(f: KeywordsCollectFn)1338     fn collect_completion_keywords(f: KeywordsCollectFn) {
1339         f(&["alternate", "over", "under"])
1340     }
1341 }
1342