1
//! CSS font properties.
2
//!
3
//! Do not import things directly from this module; use the `properties` module instead,
4
//! which re-exports things from here.
5

            
6
use cast::{f64, u16};
7
use cssparser::{Parser, Token};
8

            
9
use crate::error::*;
10
use crate::length::*;
11
use crate::parse_identifiers;
12
use crate::parsers::{finite_f32, Parse};
13
use crate::properties::ComputedValues;
14
use crate::property_defs::{FontStretch, FontStyle, FontVariant};
15

            
16
/// `font` shorthand property.
17
///
18
/// CSS2: <https://www.w3.org/TR/CSS2/fonts.html#font-shorthand>
19
///
20
/// CSS Fonts 3: <https://www.w3.org/TR/css-fonts-3/#propdef-font>
21
///
22
/// CSS Fonts 4: <https://drafts.csswg.org/css-fonts-4/#font-prop>
23
///
24
/// This is a shorthand, which expands to the longhands `font-family`, `font-size`, etc.
25
// servo/components/style/properties/shorthands/font.mako.rs is a good reference for this
26
17
#[derive(Debug, Clone, PartialEq)]
27
pub enum Font {
28
    Caption,
29
    Icon,
30
    Menu,
31
    MessageBox,
32
    SmallCaption,
33
    StatusBar,
34
8
    Spec(FontSpec),
35
}
36

            
37
/// Parameters from the `font` shorthand property.
38
80
#[derive(Debug, Default, Clone, PartialEq)]
39
pub struct FontSpec {
40
40
    pub style: FontStyle,
41
40
    pub variant: FontVariant,
42
40
    pub weight: FontWeight,
43
40
    pub stretch: FontStretch,
44
40
    pub size: FontSize,
45
40
    pub line_height: LineHeight,
46
40
    pub family: FontFamily,
47
}
48

            
49
impl Parse for Font {
50
35
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Font, ParseError<'i>> {
51
35
        if let Ok(f) = parse_font_spec_identifiers(parser) {
52
1
            return Ok(f);
53
35
        }
54

            
55
        // The following is stolen from servo/components/style/properties/shorthands/font.mako.rs
56

            
57
34
        let mut nb_normals = 0;
58
34
        let mut style = None;
59
34
        let mut variant_caps = None;
60
34
        let mut weight = None;
61
34
        let mut stretch = None;
62
        let size;
63

            
64
34
        loop {
65
            // Special-case 'normal' because it is valid in each of
66
            // font-style, font-weight, font-variant and font-stretch.
67
            // Leaves the values to None, 'normal' is the initial value for each of them.
68
59
            if parser
69
59
                .try_parse(|input| input.expect_ident_matching("normal"))
70
59
                .is_ok()
71
            {
72
17
                nb_normals += 1;
73
                continue;
74
            }
75
42
            if style.is_none() {
76
40
                if let Ok(value) = parser.try_parse(FontStyle::parse) {
77
1
                    style = Some(value);
78
                    continue;
79
                }
80
40
            }
81
41
            if weight.is_none() {
82
36
                if let Ok(value) = parser.try_parse(FontWeight::parse) {
83
5
                    weight = Some(value);
84
                    continue;
85
                }
86
36
            }
87
36
            if variant_caps.is_none() {
88
34
                if let Ok(value) = parser.try_parse(FontVariant::parse) {
89
1
                    variant_caps = Some(value);
90
                    continue;
91
                }
92
34
            }
93
35
            if stretch.is_none() {
94
34
                if let Ok(value) = parser.try_parse(FontStretch::parse) {
95
1
                    stretch = Some(value);
96
                    continue;
97
                }
98
34
            }
99
34
            size = FontSize::parse(parser)?;
100
            break;
101
        }
102

            
103
68
        let line_height = if parser.try_parse(|input| input.expect_delim('/')).is_ok() {
104
1
            Some(LineHeight::parse(parser)?)
105
        } else {
106
33
            None
107
        };
108

            
109
        #[inline]
110
136
        fn count<T>(opt: &Option<T>) -> u8 {
111
136
            if opt.is_some() {
112
8
                1
113
            } else {
114
128
                0
115
            }
116
136
        }
117

            
118
34
        if (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals)
119
            > 4
120
        {
121
1
            return Err(parser.new_custom_error(ValueErrorKind::parse_error(
122
                "invalid syntax for 'font' property",
123
            )));
124
        }
125

            
126
33
        let family = FontFamily::parse(parser)?;
127

            
128
33
        Ok(Font::Spec(FontSpec {
129
33
            style: style.unwrap_or_default(),
130
33
            variant: variant_caps.unwrap_or_default(),
131
33
            weight: weight.unwrap_or_default(),
132
33
            stretch: stretch.unwrap_or_default(),
133
            size,
134
33
            line_height: line_height.unwrap_or_default(),
135
33
            family,
136
        }))
137
35
    }
138
}
139

            
140
impl Font {
141
32
    pub fn to_font_spec(&self) -> FontSpec {
142
32
        match *self {
143
            Font::Caption
144
            | Font::Icon
145
            | Font::Menu
146
            | Font::MessageBox
147
            | Font::SmallCaption
148
            | Font::StatusBar => {
149
                // We don't actually pick up the systme fonts, so reduce them to a default.
150
                FontSpec::default()
151
            }
152

            
153
32
            Font::Spec(ref spec) => spec.clone(),
154
        }
155
32
    }
156
}
157

            
158
/// Parses identifiers used for system fonts.
159
#[rustfmt::skip]
160
35
fn parse_font_spec_identifiers<'i>(parser: &mut Parser<'i, '_>) -> Result<Font, ParseError<'i>> {
161
70
    Ok(parser.try_parse(|p| {
162
        parse_identifiers! {
163
            p,
164
            "caption"       => Font::Caption,
165
            "icon"          => Font::Icon,
166
            "menu"          => Font::Menu,
167
            "message-box"   => Font::MessageBox,
168
            "small-caption" => Font::SmallCaption,
169
            "status-bar"    => Font::StatusBar,
170
        }
171
69
    })?)
172
35
}
173

            
174
/// `font-size` property.
175
///
176
/// SVG1.1: <https://www.w3.org/TR/SVG11/text.html#FontSizeProperty>
177
///
178
/// CSS2: <https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-size>
179
///
180
/// CSS Fonts 3: <https://www.w3.org/TR/css-fonts-3/#font-size-prop>
181
#[allow(clippy::upper_case_acronyms)]
182
9769384
#[derive(Debug, Clone, PartialEq)]
183
pub enum FontSize {
184
    Smaller,
185
    Larger,
186
    XXSmall,
187
    XSmall,
188
    Small,
189
    Medium,
190
    Large,
191
    XLarge,
192
    XXLarge,
193
9768511
    Value(Length<Both>),
194
}
195

            
196
impl FontSize {
197
4889161
    pub fn value(&self) -> Length<Both> {
198
4889161
        match self {
199
4889161
            FontSize::Value(s) => *s,
200
            _ => unreachable!(),
201
        }
202
4889161
    }
203

            
204
1470054
    pub fn compute(&self, v: &ComputedValues) -> Self {
205
1470603
        let compute_points = |p| 12.0 * 1.2f64.powf(p) / POINTS_PER_INCH;
206

            
207
1470054
        let parent = v.font_size().value();
208

            
209
        // The parent must already have resolved to an absolute unit
210
        assert!(
211
1470054
            parent.unit != LengthUnit::Percent
212
1470054
                && parent.unit != LengthUnit::Em
213
1470054
                && parent.unit != LengthUnit::Ex
214
        );
215

            
216
        use FontSize::*;
217

            
218
        #[rustfmt::skip]
219
1470054
        let new_size = match self {
220
3
            Smaller => Length::<Both>::new(parent.length / 1.2,  parent.unit),
221
1
            Larger  => Length::<Both>::new(parent.length * 1.2,  parent.unit),
222
12
            XXSmall => Length::<Both>::new(compute_points(-3.0), LengthUnit::In),
223
            XSmall  => Length::<Both>::new(compute_points(-2.0), LengthUnit::In),
224
            Small   => Length::<Both>::new(compute_points(-1.0), LengthUnit::In),
225
537
            Medium  => Length::<Both>::new(compute_points(0.0),  LengthUnit::In),
226
            Large   => Length::<Both>::new(compute_points(1.0),  LengthUnit::In),
227
            XLarge  => Length::<Both>::new(compute_points(2.0),  LengthUnit::In),
228
            XXLarge => Length::<Both>::new(compute_points(3.0),  LengthUnit::In),
229

            
230
1469501
            Value(s) if s.unit == LengthUnit::Percent => {
231
1793
                Length::<Both>::new(parent.length * s.length, parent.unit)
232
            }
233
1791

            
234
1471218
            Value(s) if s.unit == LengthUnit::Em => {
235
1
                Length::<Both>::new(parent.length * s.length, parent.unit)
236
89
            }
237

            
238
1469499
            Value(s) if s.unit == LengthUnit::Ex => {
239
                // FIXME: it would be nice to know the actual Ex-height
240
                // of the font.
241
2
                Length::<Both>::new(parent.length * s.length / 2.0, parent.unit)
242
            }
243

            
244
1469497
            Value(s) => *s,
245
        };
246

            
247
1470054
        FontSize::Value(new_size)
248
1470054
    }
249

            
250
1028
    pub fn to_user(&self, params: &NormalizeParams) -> f64 {
251
1028
        self.value().to_user(params)
252
1028
    }
253
}
254

            
255
impl Parse for FontSize {
256
    #[rustfmt::skip]
257
1345
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<FontSize, ParseError<'i>> {
258
1345
        parser
259
1345
            .try_parse(|p| Length::<Both>::parse(p))
260
            .map(FontSize::Value)
261
554
            .or_else(|_| {
262
554
                Ok(parse_identifiers!(
263
554
                    parser,
264
                    "smaller"  => FontSize::Smaller,
265
                    "larger"   => FontSize::Larger,
266
                    "xx-small" => FontSize::XXSmall,
267
                    "x-small"  => FontSize::XSmall,
268
                    "small"    => FontSize::Small,
269
                    "medium"   => FontSize::Medium,
270
                    "large"    => FontSize::Large,
271
                    "x-large"  => FontSize::XLarge,
272
                    "xx-large" => FontSize::XXLarge,
273
1
                )?)
274
554
            })
275
1345
    }
276
}
277

            
278
/// `font-weight` property.
279
///
280
/// CSS Fonts 3: <https://www.w3.org/TR/css-fonts-3/#propdef-font-weight>
281
///
282
/// CSS Fonts 4: <https://drafts.csswg.org/css-fonts-4/#font-weight-prop>
283
5908773
#[derive(Debug, Copy, Clone, PartialEq)]
284
pub enum FontWeight {
285
    Normal,
286
    Bold,
287
    Bolder,
288
    Lighter,
289
1
    Weight(u16),
290
}
291

            
292
impl Parse for FontWeight {
293
652
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<FontWeight, ParseError<'i>> {
294
652
        parser
295
652
            .try_parse(|p| {
296
651
                Ok(parse_identifiers!(
297
                    p,
298
                    "normal" => FontWeight::Normal,
299
                    "bold" => FontWeight::Bold,
300
                    "bolder" => FontWeight::Bolder,
301
                    "lighter" => FontWeight::Lighter,
302
53
                )?)
303
652
            })
304
54
            .or_else(|_: ParseError<'_>| {
305
54
                let loc = parser.current_source_location();
306
54
                let i = parser.expect_integer()?;
307
40
                if (1..=1000).contains(&i) {
308
17
                    Ok(FontWeight::Weight(u16(i).unwrap()))
309
                } else {
310
3
                    Err(loc.new_custom_error(ValueErrorKind::value_error(
311
                        "value must be between 1 and 1000 inclusive",
312
                    )))
313
                }
314
54
            })
315
652
    }
316
}
317

            
318
impl FontWeight {
319
    #[rustfmt::skip]
320
1470003
    pub fn compute(&self, v: &Self) -> Self {
321
        use FontWeight::*;
322

            
323
        // Here, note that we assume that Normal=W400 and Bold=W700, per the spec.  Also,
324
        // this must match `impl From<FontWeight> for pango::Weight`.
325
        //
326
        // See the table at https://drafts.csswg.org/css-fonts-4/#relative-weights
327

            
328
1470003
        match *self {
329
1
            Bolder => match v.numeric_weight() {
330
1
                w if (  1..100).contains(&w) => Weight(400),
331
1
                w if (100..350).contains(&w) => Weight(400),
332
1
                w if (350..550).contains(&w) => Weight(700),
333
                w if (550..750).contains(&w) => Weight(900),
334
                w if (750..900).contains(&w) => Weight(900),
335
                w if 900 <= w                => Weight(w),
336

            
337
                _ => unreachable!(),
338
            }
339

            
340
1
            Lighter => match v.numeric_weight() {
341
1
                w if (  1..100).contains(&w) => Weight(w),
342
1
                w if (100..350).contains(&w) => Weight(100),
343
1
                w if (350..550).contains(&w) => Weight(100),
344
1
                w if (550..750).contains(&w) => Weight(400),
345
                w if (750..900).contains(&w) => Weight(700),
346
                w if 900 <= w                => Weight(700),
347

            
348
                _ => unreachable!(),
349
            }
350

            
351
1470001
            _ => *self,
352
        }
353
1470003
    }
354

            
355
    // Converts the symbolic weights to numeric weights.  Will panic on `Bolder` or `Lighter`.
356
1026
    pub fn numeric_weight(self) -> u16 {
357
        use FontWeight::*;
358

            
359
        // Here, note that we assume that Normal=W400 and Bold=W700, per the spec.  Also,
360
        // this must match `impl From<FontWeight> for pango::Weight`.
361
1026
        match self {
362
963
            Normal => 400,
363
51
            Bold => 700,
364
            Bolder | Lighter => unreachable!(),
365
12
            Weight(w) => w,
366
        }
367
1026
    }
368
}
369

            
370
/// `letter-spacing` property.
371
///
372
/// SVG1.1: <https://www.w3.org/TR/SVG11/text.html#LetterSpacingProperty>
373
///
374
/// CSS Text 3: <https://www.w3.org/TR/css-text-3/#letter-spacing-property>
375
4438595
#[derive(Debug, Clone, PartialEq)]
376
pub enum LetterSpacing {
377
    Normal,
378
4433600
    Value(Length<Horizontal>),
379
}
380

            
381
impl LetterSpacing {
382
1028
    pub fn value(&self) -> Length<Horizontal> {
383
1028
        match self {
384
1028
            LetterSpacing::Value(s) => *s,
385
            _ => unreachable!(),
386
        }
387
1028
    }
388

            
389
1470001
    pub fn compute(&self) -> Self {
390
1470001
        let spacing = match self {
391
1669
            LetterSpacing::Normal => Length::<Horizontal>::new(0.0, LengthUnit::Px),
392
1468332
            LetterSpacing::Value(s) => *s,
393
        };
394

            
395
1470001
        LetterSpacing::Value(spacing)
396
1470001
    }
397

            
398
1028
    pub fn to_user(&self, params: &NormalizeParams) -> f64 {
399
1028
        self.value().to_user(params)
400
1028
    }
401
}
402

            
403
impl Parse for LetterSpacing {
404
556
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<LetterSpacing, ParseError<'i>> {
405
556
        parser
406
556
            .try_parse(|p| Length::<Horizontal>::parse(p))
407
            .map(LetterSpacing::Value)
408
551
            .or_else(|_| {
409
551
                Ok(parse_identifiers!(
410
551
                    parser,
411
                    "normal" => LetterSpacing::Normal,
412
1
                )?)
413
551
            })
414
556
    }
415
}
416

            
417
/// `line-height` property.
418
///
419
/// CSS2: <https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height>
420
1497161
#[derive(Debug, Clone, PartialEq)]
421
pub enum LineHeight {
422
    Normal,
423
2
    Number(f32),
424
5
    Length(Length<Both>),
425
3
    Percentage(f32),
426
}
427

            
428
impl LineHeight {
429
    pub fn value(&self) -> Length<Both> {
430
        match self {
431
            LineHeight::Length(l) => *l,
432
            _ => unreachable!(),
433
        }
434
    }
435

            
436
4
    pub fn compute(&self, values: &ComputedValues) -> Self {
437
4
        let font_size = values.font_size().value();
438

            
439
4
        match *self {
440
1
            LineHeight::Normal => LineHeight::Length(font_size),
441

            
442
2
            LineHeight::Number(f) | LineHeight::Percentage(f) => {
443
2
                LineHeight::Length(Length::new(font_size.length * f64(f), font_size.unit))
444
2
            }
445

            
446
1
            LineHeight::Length(l) => LineHeight::Length(l),
447
        }
448
4
    }
449

            
450
    pub fn to_user(&self, params: &NormalizeParams) -> f64 {
451
        self.value().to_user(params)
452
    }
453
}
454

            
455
impl Parse for LineHeight {
456
561
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<LineHeight, ParseError<'i>> {
457
561
        let state = parser.state();
458
561
        let loc = parser.current_source_location();
459

            
460
561
        let token = parser.next()?.clone();
461

            
462
560
        match token {
463
552
            Token::Ident(ref cow) => {
464
553
                if cow.eq_ignore_ascii_case("normal") {
465
551
                    Ok(LineHeight::Normal)
466
                } else {
467
1
                    Err(parser
468
1
                        .new_basic_unexpected_token_error(token.clone())
469
                        .into())
470
                }
471
            }
472

            
473
4
            Token::Number { value, .. } => Ok(LineHeight::Number(
474
2
                finite_f32(value).map_err(|e| loc.new_custom_error(e))?,
475
2
            )),
476

            
477
4
            Token::Percentage { unit_value, .. } => Ok(LineHeight::Percentage(unit_value)),
478

            
479
            Token::Dimension { .. } => {
480
2
                parser.reset(&state);
481
2
                Ok(LineHeight::Length(Length::<Both>::parse(parser)?))
482
1
            }
483

            
484
            _ => Err(parser.new_basic_unexpected_token_error(token).into()),
485
        }
486
561
    }
487
}
488

            
489
/// `font-family` property.
490
///
491
/// SVG1.1: <https://www.w3.org/TR/SVG11/text.html#FontFamilyProperty>
492
///
493
/// CSS 2: <https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-family>
494
///
495
/// CSS Fonts 3: <https://www.w3.org/TR/css-fonts-3/#font-family-prop>
496
11820450
#[derive(Debug, Clone, PartialEq)]
497
5910225
pub struct FontFamily(pub String);
498

            
499
impl Parse for FontFamily {
500
1202
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<FontFamily, ParseError<'i>> {
501
1202
        let loc = parser.current_source_location();
502

            
503
2763
        let fonts = parser.parse_comma_separated(|parser| {
504
3122
            if let Ok(cow) = parser.try_parse(|p| p.expect_string_cloned()) {
505
10
                if cow == "" {
506
1
                    return Err(loc.new_custom_error(ValueErrorKind::value_error(
507
                        "empty string is not a valid font family name",
508
                    )));
509
                }
510

            
511
9
                return Ok(cow);
512
1561
            }
513

            
514
1551
            let first_ident = parser.expect_ident()?.clone();
515
1548
            let mut value = first_ident.as_ref().to_owned();
516

            
517
3450
            while let Ok(cow) = parser.try_parse(|p| p.expect_ident_cloned()) {
518
177
                value.push(' ');
519
177
                value.push_str(&cow);
520
1725
            }
521
1548
            Ok(cssparser::CowRcStr::from(value))
522
1565
        })?;
523

            
524
1198
        Ok(FontFamily(fonts.join(",")))
525
1202
    }
526
}
527

            
528
impl FontFamily {
529
1026
    pub fn as_str(&self) -> &str {
530
1026
        &self.0
531
1026
    }
532
}
533

            
534
/// `glyph-orientation-vertical` property.
535
///
536
/// Note that in SVG1.1 this could be `auto` or `<angle>`, but in SVG2/CSS3 it is
537
/// deprecated, and turned into a shorthand for the `text-orientation` property.  Also,
538
/// now it only takes values `auto`, `0deg`, `90deg`, `0`, `90`.  At parsing time, this
539
/// gets translated to fixed enum values.
540
///
541
/// CSS Writing Modes 3: <https://www.w3.org/TR/css-writing-modes-3/#propdef-glyph-orientation-vertical>
542
///
543
/// Obsolete SVG1.1: <https://www.w3.org/TR/SVG11/text.html#GlyphOrientationVerticalProperty>
544
4436545
#[derive(Debug, Copy, Clone, PartialEq)]
545
pub enum GlyphOrientationVertical {
546
    Auto,
547
    Angle0,
548
    Angle90,
549
}
550

            
551
impl Parse for GlyphOrientationVertical {
552
14
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<GlyphOrientationVertical, ParseError<'i>> {
553
14
        let loc = parser.current_source_location();
554

            
555
14
        if parser
556
12
            .try_parse(|p| p.expect_ident_matching("auto"))
557
14
            .is_ok()
558
        {
559
1
            return Ok(GlyphOrientationVertical::Auto);
560
        }
561

            
562
13
        let token = parser.next()?.clone();
563

            
564
        // Apart from "auto" (handled above),
565
        // https://www.w3.org/TR/css-writing-modes-3/#propdef-glyph-orientation-vertical
566
        // only allows the values "0", "90", "0deg", "90deg".  So, we will look at
567
        // individual tokens.  We'll reject non-integer numbers or non-integer dimensions.
568
12
        match token {
569
            Token::Number {
570
                int_value: Some(0), ..
571
1
            } => Ok(GlyphOrientationVertical::Angle0),
572

            
573
            Token::Number {
574
                int_value: Some(90),
575
                ..
576
1
            } => Ok(GlyphOrientationVertical::Angle90),
577

            
578
            Token::Dimension {
579
                int_value: Some(0),
580
3
                unit,
581
                ..
582
2
            } if unit.eq_ignore_ascii_case("deg") => Ok(GlyphOrientationVertical::Angle0),
583

            
584
            Token::Dimension {
585
                int_value: Some(90),
586
2
                unit,
587
                ..
588
1
            } if unit.eq_ignore_ascii_case("deg") => Ok(GlyphOrientationVertical::Angle90),
589
8
            _ => Err(loc.new_unexpected_token_error(token.clone())),
590
        }
591
12
    }
592
}
593

            
594
#[cfg(test)]
595
mod tests {
596
    use super::*;
597

            
598
    use crate::properties::{ParsedProperty, SpecifiedValue, SpecifiedValues};
599

            
600
    #[test]
601
2
    fn parses_font_shorthand() {
602
2
        assert_eq!(
603
1
            Font::parse_str("small-caption").unwrap(),
604
            Font::SmallCaption,
605
        );
606

            
607
2
        assert_eq!(
608
1
            Font::parse_str("italic bold 12px sans").unwrap(),
609
1
            Font::Spec(FontSpec {
610
1
                style: FontStyle::Italic,
611
1
                variant: Default::default(),
612
1
                weight: FontWeight::Bold,
613
1
                stretch: Default::default(),
614
1
                size: FontSize::Value(Length::new(12.0, LengthUnit::Px)),
615
1
                line_height: Default::default(),
616
1
                family: FontFamily("sans".to_string()),
617
            }),
618
        );
619

            
620
2
        assert_eq!(
621
1
            Font::parse_str("bold 14cm/2 serif").unwrap(),
622
1
            Font::Spec(FontSpec {
623
1
                style: Default::default(),
624
1
                variant: Default::default(),
625
1
                weight: FontWeight::Bold,
626
1
                stretch: Default::default(),
627
1
                size: FontSize::Value(Length::new(14.0, LengthUnit::Cm)),
628
1
                line_height: LineHeight::Number(2.0),
629
1
                family: FontFamily("serif".to_string()),
630
            }),
631
        );
632

            
633
2
        assert_eq!(
634
1
            Font::parse_str("small-caps condensed 12pt serif").unwrap(),
635
1
            Font::Spec(FontSpec {
636
1
                style: Default::default(),
637
1
                variant: FontVariant::SmallCaps,
638
1
                weight: FontWeight::Normal,
639
1
                stretch: FontStretch::Condensed,
640
1
                size: FontSize::Value(Length::new(12.0, LengthUnit::Pt)),
641
1
                line_height: Default::default(),
642
1
                family: FontFamily("serif".to_string()),
643
            }),
644
        );
645
2
    }
646

            
647
    #[test]
648
2
    fn parses_font_shorthand_with_normal_values() {
649
1
        let expected_font = Font::Spec(FontSpec {
650
1
            style: Default::default(),
651
1
            variant: Default::default(),
652
1
            weight: Default::default(),
653
1
            stretch: Default::default(),
654
1
            size: FontSize::Value(Length::new(12.0, LengthUnit::Pt)),
655
1
            line_height: Default::default(),
656
1
            family: FontFamily("serif".to_string()),
657
        });
658

            
659
        // One through four instances of "normal" - they all resolve to default values for
660
        // each property.
661
2
        assert_eq!(Font::parse_str("normal 12pt serif").unwrap(), expected_font,);
662
2
        assert_eq!(
663
1
            Font::parse_str("normal normal 12pt serif").unwrap(),
664
            expected_font,
665
        );
666
2
        assert_eq!(
667
1
            Font::parse_str("normal normal normal 12pt serif").unwrap(),
668
            expected_font,
669
        );
670
2
        assert_eq!(
671
1
            Font::parse_str("normal normal normal normal 12pt serif").unwrap(),
672
            expected_font,
673
        );
674

            
675
        // But more than four "normal" is an error.
676
1
        assert!(Font::parse_str("normal normal normal normal normal 12pt serif").is_err());
677

            
678
        // Let's throw in an actual keyword in the middle
679
2
        assert_eq!(
680
1
            Font::parse_str("normal bold normal 12pt serif").unwrap(),
681
1
            Font::Spec(FontSpec {
682
1
                style: Default::default(),
683
1
                variant: Default::default(),
684
1
                weight: FontWeight::Bold,
685
1
                stretch: Default::default(),
686
1
                size: FontSize::Value(Length::new(12.0, LengthUnit::Pt)),
687
1
                line_height: Default::default(),
688
1
                family: FontFamily("serif".to_string()),
689
            }),
690
        );
691
2
    }
692

            
693
    #[test]
694
2
    fn detects_invalid_invalid_font_size() {
695
1
        assert!(FontSize::parse_str("furlong").is_err());
696
2
    }
697

            
698
    #[test]
699
2
    fn computes_parent_relative_font_size() {
700
1
        let mut specified = SpecifiedValues::default();
701
1
        specified.set_parsed_property(&ParsedProperty::FontSize(SpecifiedValue::Specified(
702
1
            FontSize::parse_str("10px").unwrap(),
703
1
        )));
704

            
705
1
        let mut values = ComputedValues::default();
706
1
        specified.to_computed_values(&mut values);
707

            
708
1
        assert_eq!(
709
1
            FontSize::parse_str("150%").unwrap().compute(&values),
710
1
            FontSize::parse_str("15px").unwrap()
711
        );
712

            
713
1
        assert_eq!(
714
1
            FontSize::parse_str("1.5em").unwrap().compute(&values),
715
1
            FontSize::parse_str("15px").unwrap()
716
        );
717

            
718
1
        assert_eq!(
719
1
            FontSize::parse_str("1ex").unwrap().compute(&values),
720
1
            FontSize::parse_str("5px").unwrap()
721
        );
722

            
723
1
        let smaller = FontSize::parse_str("smaller").unwrap().compute(&values);
724
1
        if let FontSize::Value(v) = smaller {
725
1
            assert!(v.length < 10.0);
726
1
            assert_eq!(v.unit, LengthUnit::Px);
727
        } else {
728
            unreachable!();
729
        }
730

            
731
1
        let larger = FontSize::parse_str("larger").unwrap().compute(&values);
732
1
        if let FontSize::Value(v) = larger {
733
1
            assert!(v.length > 10.0);
734
1
            assert_eq!(v.unit, LengthUnit::Px);
735
        } else {
736
            unreachable!();
737
        }
738
2
    }
739

            
740
    #[test]
741
2
    fn parses_font_weight() {
742
1
        assert_eq!(
743
1
            <FontWeight as Parse>::parse_str("normal").unwrap(),
744
            FontWeight::Normal
745
        );
746
1
        assert_eq!(
747
1
            <FontWeight as Parse>::parse_str("bold").unwrap(),
748
            FontWeight::Bold
749
        );
750
1
        assert_eq!(
751
1
            <FontWeight as Parse>::parse_str("100").unwrap(),
752
            FontWeight::Weight(100)
753
        );
754
2
    }
755

            
756
    #[test]
757
2
    fn detects_invalid_font_weight() {
758
1
        assert!(<FontWeight as Parse>::parse_str("").is_err());
759
1
        assert!(<FontWeight as Parse>::parse_str("strange").is_err());
760
1
        assert!(<FontWeight as Parse>::parse_str("0").is_err());
761
1
        assert!(<FontWeight as Parse>::parse_str("-1").is_err());
762
1
        assert!(<FontWeight as Parse>::parse_str("1001").is_err());
763
1
        assert!(<FontWeight as Parse>::parse_str("3.14").is_err());
764
2
    }
765

            
766
    #[test]
767
2
    fn parses_letter_spacing() {
768
1
        assert_eq!(
769
1
            <LetterSpacing as Parse>::parse_str("normal").unwrap(),
770
            LetterSpacing::Normal
771
        );
772
1
        assert_eq!(
773
1
            <LetterSpacing as Parse>::parse_str("10em").unwrap(),
774
1
            LetterSpacing::Value(Length::<Horizontal>::new(10.0, LengthUnit::Em,))
775
        );
776
2
    }
777

            
778
    #[test]
779
2
    fn computes_letter_spacing() {
780
1
        assert_eq!(
781
1
            <LetterSpacing as Parse>::parse_str("normal")
782
1
                .map(|s| s.compute())
783
                .unwrap(),
784
1
            LetterSpacing::Value(Length::<Horizontal>::new(0.0, LengthUnit::Px,))
785
        );
786
1
        assert_eq!(
787
1
            <LetterSpacing as Parse>::parse_str("10em")
788
1
                .map(|s| s.compute())
789
                .unwrap(),
790
1
            LetterSpacing::Value(Length::<Horizontal>::new(10.0, LengthUnit::Em,))
791
        );
792
2
    }
793

            
794
    #[test]
795
2
    fn detects_invalid_invalid_letter_spacing() {
796
1
        assert!(LetterSpacing::parse_str("furlong").is_err());
797
2
    }
798

            
799
    #[test]
800
2
    fn parses_font_family() {
801
2
        assert_eq!(
802
1
            <FontFamily as Parse>::parse_str("'Hello world'").unwrap(),
803
1
            FontFamily("Hello world".to_owned())
804
        );
805

            
806
2
        assert_eq!(
807
1
            <FontFamily as Parse>::parse_str("\"Hello world\"").unwrap(),
808
1
            FontFamily("Hello world".to_owned())
809
        );
810

            
811
2
        assert_eq!(
812
1
            <FontFamily as Parse>::parse_str("\"Hello world  with  spaces\"").unwrap(),
813
1
            FontFamily("Hello world  with  spaces".to_owned())
814
        );
815

            
816
2
        assert_eq!(
817
1
            <FontFamily as Parse>::parse_str("  Hello  world  ").unwrap(),
818
1
            FontFamily("Hello world".to_owned())
819
        );
820

            
821
2
        assert_eq!(
822
1
            <FontFamily as Parse>::parse_str("Plonk").unwrap(),
823
1
            FontFamily("Plonk".to_owned())
824
        );
825
2
    }
826

            
827
    #[test]
828
2
    fn parses_multiple_font_family() {
829
2
        assert_eq!(
830
1
            <FontFamily as Parse>::parse_str("serif,monospace,\"Hello world\", with  spaces ")
831
                .unwrap(),
832
1
            FontFamily("serif,monospace,Hello world,with spaces".to_owned())
833
        );
834
2
    }
835

            
836
    #[test]
837
2
    fn detects_invalid_font_family() {
838
1
        assert!(<FontFamily as Parse>::parse_str("").is_err());
839
1
        assert!(<FontFamily as Parse>::parse_str("''").is_err());
840
1
        assert!(<FontFamily as Parse>::parse_str("42").is_err());
841
2
    }
842

            
843
    #[test]
844
2
    fn parses_line_height() {
845
1
        assert_eq!(
846
1
            <LineHeight as Parse>::parse_str("normal").unwrap(),
847
            LineHeight::Normal
848
        );
849

            
850
1
        assert_eq!(
851
1
            <LineHeight as Parse>::parse_str("2").unwrap(),
852
            LineHeight::Number(2.0)
853
        );
854

            
855
1
        assert_eq!(
856
1
            <LineHeight as Parse>::parse_str("2cm").unwrap(),
857
1
            LineHeight::Length(Length::new(2.0, LengthUnit::Cm))
858
        );
859

            
860
1
        assert_eq!(
861
1
            <LineHeight as Parse>::parse_str("150%").unwrap(),
862
            LineHeight::Percentage(1.5)
863
        );
864
2
    }
865

            
866
    #[test]
867
2
    fn detects_invalid_line_height() {
868
1
        assert!(<LineHeight as Parse>::parse_str("").is_err());
869
1
        assert!(<LineHeight as Parse>::parse_str("florp").is_err());
870
1
        assert!(<LineHeight as Parse>::parse_str("3florp").is_err());
871
2
    }
872

            
873
    #[test]
874
2
    fn computes_line_height() {
875
1
        let mut specified = SpecifiedValues::default();
876
1
        specified.set_parsed_property(&ParsedProperty::FontSize(SpecifiedValue::Specified(
877
1
            FontSize::parse_str("10px").unwrap(),
878
1
        )));
879

            
880
1
        let mut values = ComputedValues::default();
881
1
        specified.to_computed_values(&mut values);
882

            
883
1
        assert_eq!(
884
1
            LineHeight::Normal.compute(&values),
885
1
            LineHeight::Length(Length::new(10.0, LengthUnit::Px)),
886
        );
887

            
888
1
        assert_eq!(
889
1
            LineHeight::Number(2.0).compute(&values),
890
1
            LineHeight::Length(Length::new(20.0, LengthUnit::Px)),
891
        );
892

            
893
1
        assert_eq!(
894
1
            LineHeight::Length(Length::new(3.0, LengthUnit::Cm)).compute(&values),
895
1
            LineHeight::Length(Length::new(3.0, LengthUnit::Cm)),
896
        );
897

            
898
1
        assert_eq!(
899
1
            LineHeight::parse_str("150%").unwrap().compute(&values),
900
1
            LineHeight::Length(Length::new(15.0, LengthUnit::Px)),
901
        );
902
2
    }
903

            
904
    #[test]
905
2
    fn parses_glyph_orientation_vertical() {
906
1
        assert_eq!(
907
1
            <GlyphOrientationVertical as Parse>::parse_str("auto").unwrap(),
908
            GlyphOrientationVertical::Auto
909
        );
910
1
        assert_eq!(
911
1
            <GlyphOrientationVertical as Parse>::parse_str("0").unwrap(),
912
            GlyphOrientationVertical::Angle0
913
        );
914
1
        assert_eq!(
915
1
            <GlyphOrientationVertical as Parse>::parse_str("0deg").unwrap(),
916
            GlyphOrientationVertical::Angle0
917
        );
918
1
        assert_eq!(
919
1
            <GlyphOrientationVertical as Parse>::parse_str("90").unwrap(),
920
            GlyphOrientationVertical::Angle90
921
        );
922
1
        assert_eq!(
923
1
            <GlyphOrientationVertical as Parse>::parse_str("90deg").unwrap(),
924
            GlyphOrientationVertical::Angle90
925
        );
926
2
    }
927

            
928
    #[test]
929
2
    fn detects_invalid_glyph_orientation_vertical() {
930
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("").is_err());
931
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("0.0").is_err());
932
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("90.0").is_err());
933
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("0.0deg").is_err());
934
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("90.0deg").is_err());
935
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("0rad").is_err());
936
1
        assert!(<GlyphOrientationVertical as Parse>::parse_str("0.0rad").is_err());
937
2
    }
938
}