1
//! CSS length values.
2
//!
3
//! [`CssLength`] is the struct librsvg uses to represent CSS lengths.
4
//! See its documentation for examples of how to construct it.
5
//!
6
//! `CssLength` values need to know whether they will be normalized with respect to the width,
7
//! height, or both dimensions of the current viewport.  `CssLength` values can be signed or
8
//! unsigned.  So, a `CssLength` has two type parameters, [`Normalize`] and [`Validate`];
9
//! the full type is `CssLength<N: Normalize, V: Validate>`.  We provide [`Horizontal`],
10
//! [`Vertical`], and [`Both`] implementations of [`Normalize`]; these let length values know
11
//! how to normalize themselves with respect to the current viewport.  We also provide
12
//! [`Signed`] and [`Unsigned`] implementations of [`Validate`].
13
//!
14
//! For ease of use, we define two type aliases [`Length`] and [`ULength`] corresponding to
15
//! signed and unsigned.
16
//!
17
//! For example, the implementation of [`Circle`][crate::shapes::Circle] defines this
18
//! structure with fields for the `(center_x, center_y, radius)`:
19
//!
20
//! ```
21
//! # use rsvg::doctest_only::{Length,ULength,Horizontal,Vertical,Both};
22
//! pub struct Circle {
23
//!     cx: Length<Horizontal>,
24
//!     cy: Length<Vertical>,
25
//!     r: ULength<Both>,
26
//! }
27
//! ```
28
//!
29
//! This means that:
30
//!
31
//! * `cx` and `cy` define the center of the circle, they can be positive or negative, and
32
//!   they will be normalized with respect to the current viewport's width and height,
33
//!   respectively.  If the SVG document specified `<circle cx="50%" cy="30%">`, the values
34
//!   would be normalized to be at 50% of the the viewport's width, and 30% of the viewport's
35
//!   height.
36
//!
37
//! * `r` is non-negative and needs to be resolved against the [normalized diagonal][diag]
38
//!   of the current viewport.
39
//!
40
//! The `N` type parameter of `CssLength<N, I>` is enough to know how to normalize a length
41
//! value; the [`CssLength::to_user`] method will handle it automatically.
42
//!
43
//! [diag]: https://www.w3.org/TR/SVG/coords.html#Units
44

            
45
use cssparser::{match_ignore_ascii_case, Parser, Token};
46
use std::f64::consts::*;
47
use std::fmt;
48
use std::marker::PhantomData;
49

            
50
use crate::dpi::Dpi;
51
use crate::drawing_ctx::Viewport;
52
use crate::error::*;
53
use crate::parsers::{finite_f32, Parse};
54
use crate::properties::{ComputedValues, FontSize, TextOrientation, WritingMode};
55
use crate::rect::Rect;
56
use crate::viewbox::ViewBox;
57

            
58
/// Units for length values.
59
// This needs to be kept in sync with `rsvg.h:RsvgUnit`.
60
#[non_exhaustive]
61
#[repr(C)]
62
72883180
#[derive(Debug, PartialEq, Copy, Clone)]
63
pub enum LengthUnit {
64
    /// `1.0` means 100%
65
    Percent,
66

            
67
    /// Pixels, or the CSS default unit
68
    Px,
69

            
70
    /// Size of the current font
71
    Em,
72

            
73
    /// x-height of the current font
74
    Ex,
75

            
76
    /// Inches (25.4 mm)
77
    In,
78

            
79
    /// Centimeters
80
    Cm,
81

            
82
    /// Millimeters
83
    Mm,
84

            
85
    /// Points (1/72 inch)
86
    Pt,
87

            
88
14
    /// Picas (12 points)
89
    Pc,
90

            
91
    /// Advance measure of a '0' character (depends on the text orientation)
92
    Ch,
93
}
94

            
95
/// A CSS length value.
96
///
97
/// This is equivalent to [CSS lengths].
98
///
99
/// [CSS lengths]: https://www.w3.org/TR/CSS22/syndata.html#length-units
100
///
101
/// It is up to the calling application to convert lengths in non-pixel units (i.e. those
102
/// where the [`unit`][RsvgLength::unit] field is not [`LengthUnit::Px`]) into something
103
/// meaningful to the application.  For example, if your application knows the
104
/// dots-per-inch (DPI) it is using, it can convert lengths with [`unit`] in
105
/// [`LengthUnit::In`] or other physical units.
106
// Keep this in sync with rsvg.h:RsvgLength
107
#[repr(C)]
108
144
#[derive(Debug, PartialEq, Copy, Clone)]
109
pub struct RsvgLength {
110
    /// Numeric part of the length
111
72
    pub length: f64,
112

            
113
    /// Unit part of the length
114
72
    pub unit: LengthUnit,
115
}
116

            
117
impl RsvgLength {
118
    /// Constructs a CSS length value.
119
10
    pub fn new(l: f64, unit: LengthUnit) -> RsvgLength {
120
        RsvgLength { length: l, unit }
121
10
    }
122
}
123

            
124
/// Used for the `N` type parameter of `CssLength<N: Normalize, V: Validate>`.
125
pub trait Normalize {
126
    /// Computes an orientation-based scaling factor.
127
    ///
128
    /// This is used in the [`CssLength::to_user`] method to resolve lengths with percentage
129
    /// units; they need to be resolved with respect to the width, height, or [normalized
130
    /// diagonal][diag] of the current viewport.
131
    ///
132
    /// [diag]: https://www.w3.org/TR/SVG/coords.html#Units
133
    fn normalize(x: f64, y: f64) -> f64;
134
}
135

            
136
/// Allows declaring `CssLength<Horizontal>`.
137
#[derive(Debug, PartialEq, Copy, Clone)]
138
pub struct Horizontal;
139

            
140
/// Allows declaring `CssLength<Vertical>`.
141
#[derive(Debug, PartialEq, Copy, Clone)]
142
pub struct Vertical;
143

            
144
/// Allows declaring `CssLength<Both>`.
145
#[derive(Debug, PartialEq, Copy, Clone)]
146
pub struct Both;
147

            
148
impl Normalize for Horizontal {
149
    #[inline]
150
501307
    fn normalize(x: f64, _y: f64) -> f64 {
151
        x
152
501307
    }
153
}
154

            
155
impl Normalize for Vertical {
156
    #[inline]
157
501292
    fn normalize(_x: f64, y: f64) -> f64 {
158
        y
159
501292
    }
160
}
161

            
162
impl Normalize for Both {
163
    #[inline]
164
600
    fn normalize(x: f64, y: f64) -> f64 {
165
600
        viewport_percentage(x, y)
166
600
    }
167
}
168

            
169
/// Used for the `V` type parameter of `CssLength<N: Normalize, V: Validate>`.
170
pub trait Validate {
171
    /// Checks if the specified value is acceptable
172
    ///
173
    /// This is used when parsing a length value
174
14431607
    fn validate(v: f64) -> Result<f64, ValueErrorKind> {
175
14431607
        Ok(v)
176
14431607
    }
177
}
178

            
179
#[derive(Debug, PartialEq, Copy, Clone)]
180
pub struct Signed;
181

            
182
impl Validate for Signed {}
183

            
184
#[derive(Debug, PartialEq, Copy, Clone)]
185
pub struct Unsigned;
186

            
187
impl Validate for Unsigned {
188
2504447
    fn validate(v: f64) -> Result<f64, ValueErrorKind> {
189
2504447
        if v >= 0.0 {
190
2504440
            Ok(v)
191
        } else {
192
7
            Err(ValueErrorKind::Value(
193
7
                "value must be non-negative".to_string(),
194
            ))
195
        }
196
2504447
    }
197
}
198

            
199
/// A CSS length value.
200
///
201
/// This is equivalent to [CSS lengths].
202
///
203
/// [CSS lengths]: https://www.w3.org/TR/CSS22/syndata.html#length-units
204
///
205
/// `CssLength` implements the [`Parse`] trait, so it can be parsed out of a
206
/// [`cssparser::Parser`].
207
///
208
/// This type will be normally used through the type aliases [`Length`] and [`ULength`]
209
///
210
/// Examples of construction:
211
///
212
/// ```
213
/// # use rsvg::doctest_only::{Length,ULength,LengthUnit,Horizontal,Vertical,Both};
214
/// # use rsvg::doctest_only::Parse;
215
/// // Explicit type
216
/// let width: Length<Horizontal> = Length::new(42.0, LengthUnit::Cm);
217
///
218
/// // Inferred type
219
/// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
220
///
221
/// // Parsed
222
/// let radius = ULength::<Both>::parse_str("5px").unwrap();
223
/// ```
224
///
225
/// During the rendering phase, a `CssLength` needs to be converted to user-space
226
/// coordinates with the [`CssLength::to_user`] method.
227
125915728
#[derive(Debug, PartialEq, Copy, Clone)]
228
pub struct CssLength<N: Normalize, V: Validate> {
229
    /// Numeric part of the length
230
62957864
    pub length: f64,
231

            
232
    /// Unit part of the length
233
62957864
    pub unit: LengthUnit,
234

            
235
    /// Dummy; used internally for the type parameter `N`
236
62957864
    orientation: PhantomData<N>,
237

            
238
    /// Dummy; used internally for the type parameter `V`
239
62957864
    validation: PhantomData<V>,
240
}
241

            
242
impl<N: Normalize, V: Validate> From<CssLength<N, V>> for RsvgLength {
243
1967
    fn from(l: CssLength<N, V>) -> RsvgLength {
244
        RsvgLength {
245
            length: l.length,
246
            unit: l.unit,
247
        }
248
1967
    }
249
}
250

            
251
impl<N: Normalize, V: Validate> Default for CssLength<N, V> {
252
1033509
    fn default() -> Self {
253
1033509
        CssLength::new(0.0, LengthUnit::Px)
254
1033509
    }
255
}
256

            
257
pub const POINTS_PER_INCH: f64 = 72.0;
258
const CM_PER_INCH: f64 = 2.54;
259
const MM_PER_INCH: f64 = 25.4;
260
const PICA_PER_INCH: f64 = 6.0;
261

            
262
impl<N: Normalize, V: Validate> Parse for CssLength<N, V> {
263
17019133
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<CssLength<N, V>, ParseError<'i>> {
264
17019133
        let l_value;
265
        let l_unit;
266

            
267
17019133
        let token = parser.next()?.clone();
268

            
269
17019124
        match token {
270
17010804
            Token::Number { value, .. } => {
271
17010804
                l_value = value;
272
17010804
                l_unit = LengthUnit::Px;
273
17010804
            }
274

            
275
6376
            Token::Percentage { unit_value, .. } => {
276
6376
                l_value = unit_value;
277
6376
                l_unit = LengthUnit::Percent;
278
6376
            }
279

            
280
            Token::Dimension {
281
363
                value, ref unit, ..
282
            } => {
283
363
                l_value = value;
284

            
285
721
                l_unit = match_ignore_ascii_case! {unit.as_ref(),
286
                    "px" => LengthUnit::Px,
287
                    "em" => LengthUnit::Em,
288
                    "ex" => LengthUnit::Ex,
289
                    "in" => LengthUnit::In,
290
                    "cm" => LengthUnit::Cm,
291
                    "mm" => LengthUnit::Mm,
292
                    "pt" => LengthUnit::Pt,
293
                    "pc" => LengthUnit::Pc,
294
                    "ch" => LengthUnit::Ch,
295

            
296
                    _ => return Err(parser.new_unexpected_token_error(token)),
297
                };
298
358
            }
299

            
300
1581
            _ => return Err(parser.new_unexpected_token_error(token)),
301
        }
302

            
303
17017539
        let l_value = f64::from(finite_f32(l_value).map_err(|e| parser.new_custom_error(e))?);
304

            
305
17010065
        <V as Validate>::validate(l_value)
306
7
            .map_err(|e| parser.new_custom_error(e))
307
17008133
            .map(|l_value| CssLength::new(l_value, l_unit))
308
17019147
    }
309
}
310

            
311
/// Parameters for length normalization extracted from [`ComputedValues`].
312
///
313
/// This is a precursor to [`NormalizeParams::from_values`], for cases where it is inconvenient
314
/// to keep a [`ComputedValues`] around.
315
pub struct NormalizeValues {
316
    font_size: FontSize,
317
    is_vertical_text: bool,
318
}
319

            
320
impl NormalizeValues {
321
2404136
    pub fn new(values: &ComputedValues) -> NormalizeValues {
322
2404136
        let is_vertical_text = matches!(
323
2404136
            (values.writing_mode(), values.text_orientation()),
324
            (WritingMode::VerticalLr, TextOrientation::Upright)
325
                | (WritingMode::VerticalRl, TextOrientation::Upright)
326
        );
327

            
328
2404136
        NormalizeValues {
329
2404136
            font_size: values.font_size(),
330
2404136
            is_vertical_text,
331
        }
332
2404136
    }
333
}
334

            
335
/// Parameters to normalize [`Length`] values to user-space distances.
336
pub struct NormalizeParams {
337
    vbox: ViewBox,
338
    font_size: f64,
339
    dpi: Dpi,
340
    is_vertical_text: bool,
341
}
342

            
343
impl NormalizeParams {
344
    /// Extracts the information needed to normalize [`Length`] values from a set of
345
    /// [`ComputedValues`] and the viewport size in [`Viewport`].
346
1455869
    pub fn new(values: &ComputedValues, viewport: &Viewport) -> NormalizeParams {
347
1455869
        let v = NormalizeValues::new(values);
348
1455869
        NormalizeParams::from_values(&v, viewport)
349
1455869
    }
350

            
351
1954954
    pub fn from_values(v: &NormalizeValues, viewport: &Viewport) -> NormalizeParams {
352
1954954
        NormalizeParams {
353
1954954
            vbox: viewport.vbox,
354
1954954
            font_size: font_size_from_values(v, viewport.dpi),
355
1954954
            dpi: viewport.dpi,
356
1954954
            is_vertical_text: v.is_vertical_text,
357
        }
358
1954954
    }
359

            
360
    /// Just used by rsvg-convert, where there is no font size nor viewport.
361
94
    pub fn from_dpi(dpi: Dpi) -> NormalizeParams {
362
94
        NormalizeParams {
363
94
            vbox: ViewBox::from(Rect::default()),
364
            font_size: 1.0,
365
            dpi,
366
            is_vertical_text: false,
367
        }
368
94
    }
369
}
370

            
371
impl<N: Normalize, V: Validate> CssLength<N, V> {
372
    /// Creates a CssLength.
373
    ///
374
    /// The compiler needs to know the type parameters `N` and `V` which represents the
375
    /// length's orientation and validation.
376
    /// You can specify them explicitly, or call the parametrized method:
377
    ///
378
    /// ```
379
    /// # use rsvg::doctest_only::{Length,LengthUnit,Horizontal,Vertical};
380
    /// // Explicit type
381
    /// let width: Length<Horizontal> = Length::new(42.0, LengthUnit::Cm);
382
    ///
383
    /// // Inferred type
384
    /// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
385
    /// ```
386
19506114
    pub fn new(l: f64, unit: LengthUnit) -> CssLength<N, V> {
387
        CssLength {
388
            length: l,
389
            unit,
390
            orientation: PhantomData,
391
            validation: PhantomData,
392
        }
393
19506114
    }
394

            
395
    /// Convert a Length with units into user-space coordinates.
396
    ///
397
    /// Lengths may come with non-pixel units, and when rendering, they need to be normalized
398
    /// to pixels based on the current viewport (e.g. for lengths with percent units), and
399
    /// based on the current element's set of [`ComputedValues`] (e.g. for lengths with `Em`
400
    /// units that need to be resolved against the current font size).
401
    ///
402
    /// Those parameters can be obtained with [`NormalizeParams::new()`].
403
9693099
    pub fn to_user(&self, params: &NormalizeParams) -> f64 {
404
9693099
        match self.unit {
405
8690495
            LengthUnit::Px => self.length,
406

            
407
            LengthUnit::Percent => {
408
1002417
                self.length * <N as Normalize>::normalize(params.vbox.width(), params.vbox.height())
409
1002417
            }
410

            
411
23
            LengthUnit::Em => self.length * params.font_size,
412

            
413
1
            LengthUnit::Ex => self.length * params.font_size / 2.0,
414

            
415
            // how far "0" advances the text, so it varies depending on orientation
416
            // we're using the 0.5em or 1.0em (based on orientation) fallback from the spec
417
            LengthUnit::Ch => {
418
2
                if params.is_vertical_text {
419
1
                    self.length * params.font_size
420
                } else {
421
1
                    self.length * params.font_size / 2.0
422
                }
423
            }
424

            
425
112
            LengthUnit::In => self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y),
426

            
427
            LengthUnit::Cm => {
428
7
                self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y) / CM_PER_INCH
429
            }
430

            
431
            LengthUnit::Mm => {
432
4
                self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y) / MM_PER_INCH
433
            }
434

            
435
            LengthUnit::Pt => {
436
37
                self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y)
437
37
                    / POINTS_PER_INCH
438
            }
439

            
440
            LengthUnit::Pc => {
441
1
                self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y)
442
1
                    / PICA_PER_INCH
443
            }
444
        }
445
9693099
    }
446

            
447
    /// Converts a Length to points.  Pixels are taken to be respect with the DPI.
448
    ///
449
    /// # Panics
450
    ///
451
    /// Will panic if the length is in Percent, Em, or Ex units.
452
106
    pub fn to_points(&self, params: &NormalizeParams) -> f64 {
453
106
        match self.unit {
454
            LengthUnit::Px => {
455
68
                self.length / <N as Normalize>::normalize(params.dpi.x, params.dpi.y) * 72.0
456
            }
457

            
458
            LengthUnit::Percent => {
459
                panic!("Cannot convert a percentage length into an absolute length");
460
            }
461

            
462
            LengthUnit::Em => {
463
                panic!("Cannot convert an Em length into an absolute length");
464
            }
465

            
466
            LengthUnit::Ex => {
467
                panic!("Cannot convert an Ex length into an absolute length");
468
            }
469

            
470
22
            LengthUnit::In => self.length * POINTS_PER_INCH,
471

            
472
4
            LengthUnit::Cm => self.length / CM_PER_INCH * POINTS_PER_INCH,
473

            
474
6
            LengthUnit::Mm => self.length / MM_PER_INCH * POINTS_PER_INCH,
475

            
476
6
            LengthUnit::Pt => self.length,
477

            
478
            LengthUnit::Pc => self.length / PICA_PER_INCH * POINTS_PER_INCH,
479

            
480
            LengthUnit::Ch => {
481
                panic!("Cannot convert a Ch length into an absolute length");
482
            }
483
        }
484
106
    }
485

            
486
12
    pub fn to_inches(&self, params: &NormalizeParams) -> f64 {
487
12
        self.to_points(params) / POINTS_PER_INCH
488
12
    }
489

            
490
4
    pub fn to_cm(&self, params: &NormalizeParams) -> f64 {
491
4
        self.to_inches(params) * CM_PER_INCH
492
4
    }
493

            
494
8
    pub fn to_mm(&self, params: &NormalizeParams) -> f64 {
495
8
        self.to_inches(params) * MM_PER_INCH
496
8
    }
497

            
498
    pub fn to_picas(&self, params: &NormalizeParams) -> f64 {
499
        self.to_inches(params) * PICA_PER_INCH
500
    }
501
}
502

            
503
1955052
fn font_size_from_values(values: &NormalizeValues, dpi: Dpi) -> f64 {
504
1955052
    let v = values.font_size.value();
505

            
506
1955052
    match v.unit {
507
        LengthUnit::Percent => unreachable!("ComputedValues can't have a relative font size"),
508

            
509
1954499
        LengthUnit::Px => v.length,
510

            
511
        // The following implies that our default font size is 12, which
512
        // matches the default from the FontSize property.
513
        LengthUnit::Em => v.length * 12.0,
514
        LengthUnit::Ex => v.length * 12.0 / 2.0,
515
        LengthUnit::Ch => v.length * 12.0 / 2.0,
516

            
517
        // FontSize always is a Both, per properties.rs
518
548
        LengthUnit::In => v.length * Both::normalize(dpi.x, dpi.y),
519
        LengthUnit::Cm => v.length * Both::normalize(dpi.x, dpi.y) / CM_PER_INCH,
520
        LengthUnit::Mm => v.length * Both::normalize(dpi.x, dpi.y) / MM_PER_INCH,
521
5
        LengthUnit::Pt => v.length * Both::normalize(dpi.x, dpi.y) / POINTS_PER_INCH,
522
        LengthUnit::Pc => v.length * Both::normalize(dpi.x, dpi.y) / PICA_PER_INCH,
523
    }
524
1955052
}
525

            
526
600
fn viewport_percentage(x: f64, y: f64) -> f64 {
527
    // https://www.w3.org/TR/SVG/coords.html#Units
528
    // "For any other length value expressed as a percentage of the viewport, the
529
    // percentage is calculated as the specified percentage of
530
    // sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
531
600
    (x * x + y * y).sqrt() / SQRT_2
532
600
}
533

            
534
/// Alias for `CssLength` types that can have negative values
535
pub type Length<N> = CssLength<N, Signed>;
536

            
537
/// Alias for `CssLength` types that are non negative
538
pub type ULength<N> = CssLength<N, Unsigned>;
539

            
540
23312114
#[derive(Debug, Default, PartialEq, Copy, Clone)]
541
pub enum LengthOrAuto<N: Normalize> {
542
    #[default]
543
    Auto,
544
4636013
    Length(CssLength<N, Unsigned>),
545
}
546

            
547
impl<N: Normalize> Parse for LengthOrAuto<N> {
548
7880
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<LengthOrAuto<N>, ParseError<'i>> {
549
15731
        if parser
550
7889
            .try_parse(|i| i.expect_ident_matching("auto"))
551
7880
            .is_ok()
552
        {
553
4
            Ok(LengthOrAuto::Auto)
554
        } else {
555
7876
            Ok(LengthOrAuto::Length(CssLength::parse(parser)?))
556
        }
557
7880
    }
558
}
559

            
560
impl fmt::Display for LengthUnit {
561
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562
        let unit = match &self {
563
            LengthUnit::Percent => "%",
564
            LengthUnit::Px => "px",
565
            LengthUnit::Em => "em",
566
            LengthUnit::Ex => "ex",
567
            LengthUnit::In => "in",
568
            LengthUnit::Cm => "cm",
569
            LengthUnit::Mm => "mm",
570
            LengthUnit::Pt => "pt",
571
            LengthUnit::Pc => "pc",
572
            LengthUnit::Ch => "ch",
573
        };
574

            
575
        write!(f, "{unit}")
576
    }
577
}
578

            
579
#[cfg(test)]
580
mod tests {
581
    use super::*;
582

            
583
    use crate::properties::{ParsedProperty, SpecifiedValue, SpecifiedValues};
584
    use crate::{assert_approx_eq_cairo, float_eq_cairo::ApproxEqCairo};
585

            
586
    #[test]
587
2
    fn parses_default() {
588
1
        assert_eq!(
589
1
            Length::<Horizontal>::parse_str("42").unwrap(),
590
1
            Length::<Horizontal>::new(42.0, LengthUnit::Px)
591
        );
592

            
593
1
        assert_eq!(
594
1
            Length::<Horizontal>::parse_str("-42px").unwrap(),
595
1
            Length::<Horizontal>::new(-42.0, LengthUnit::Px)
596
        );
597
2
    }
598

            
599
    #[test]
600
2
    fn parses_percent() {
601
1
        assert_eq!(
602
1
            Length::<Horizontal>::parse_str("50.0%").unwrap(),
603
1
            Length::<Horizontal>::new(0.5, LengthUnit::Percent)
604
        );
605
2
    }
606

            
607
    #[test]
608
2
    fn parses_font_em() {
609
1
        assert_eq!(
610
1
            Length::<Vertical>::parse_str("22.5em").unwrap(),
611
1
            Length::<Vertical>::new(22.5, LengthUnit::Em)
612
        );
613
2
    }
614

            
615
    #[test]
616
2
    fn parses_font_ex() {
617
1
        assert_eq!(
618
1
            Length::<Vertical>::parse_str("22.5ex").unwrap(),
619
1
            Length::<Vertical>::new(22.5, LengthUnit::Ex)
620
        );
621
2
    }
622

            
623
    #[test]
624
2
    fn parses_font_ch() {
625
1
        assert_eq!(
626
1
            Length::<Vertical>::parse_str("22.5ch").unwrap(),
627
1
            Length::<Vertical>::new(22.5, LengthUnit::Ch)
628
        );
629
2
    }
630

            
631
    #[test]
632
2
    fn parses_physical_units() {
633
1
        assert_eq!(
634
1
            Length::<Both>::parse_str("72pt").unwrap(),
635
1
            Length::<Both>::new(72.0, LengthUnit::Pt)
636
        );
637

            
638
1
        assert_eq!(
639
1
            Length::<Both>::parse_str("-22.5in").unwrap(),
640
1
            Length::<Both>::new(-22.5, LengthUnit::In)
641
        );
642

            
643
1
        assert_eq!(
644
1
            Length::<Both>::parse_str("-254cm").unwrap(),
645
1
            Length::<Both>::new(-254.0, LengthUnit::Cm)
646
        );
647

            
648
1
        assert_eq!(
649
1
            Length::<Both>::parse_str("254mm").unwrap(),
650
1
            Length::<Both>::new(254.0, LengthUnit::Mm)
651
        );
652

            
653
1
        assert_eq!(
654
1
            Length::<Both>::parse_str("60pc").unwrap(),
655
1
            Length::<Both>::new(60.0, LengthUnit::Pc)
656
        );
657
2
    }
658

            
659
    #[test]
660
2
    fn parses_unsigned() {
661
1
        assert_eq!(
662
1
            ULength::<Horizontal>::parse_str("42").unwrap(),
663
1
            ULength::<Horizontal>::new(42.0, LengthUnit::Px)
664
        );
665

            
666
1
        assert_eq!(
667
1
            ULength::<Both>::parse_str("0pt").unwrap(),
668
1
            ULength::<Both>::new(0.0, LengthUnit::Pt)
669
        );
670

            
671
1
        assert!(ULength::<Horizontal>::parse_str("-42px").is_err());
672
2
    }
673

            
674
    #[test]
675
2
    fn empty_length_yields_error() {
676
1
        assert!(Length::<Both>::parse_str("").is_err());
677
2
    }
678

            
679
    #[test]
680
2
    fn invalid_unit_yields_error() {
681
1
        assert!(Length::<Both>::parse_str("8furlong").is_err());
682
2
    }
683

            
684
    #[test]
685
2
    fn normalize_default_works() {
686
1
        let viewport = Viewport::new(Dpi::new(40.0, 40.0), 100.0, 100.0);
687
1
        let values = ComputedValues::default();
688
1
        let params = NormalizeParams::new(&values, &viewport);
689

            
690
        assert_approx_eq_cairo!(
691
1
            Length::<Both>::new(10.0, LengthUnit::Px).to_user(&params),
692
            10.0
693
        );
694
2
    }
695

            
696
    #[test]
697
2
    fn normalize_absolute_units_works() {
698
1
        let viewport = Viewport::new(Dpi::new(40.0, 50.0), 100.0, 100.0);
699
1
        let values = ComputedValues::default();
700
1
        let params = NormalizeParams::new(&values, &viewport);
701

            
702
        assert_approx_eq_cairo!(
703
1
            Length::<Horizontal>::new(10.0, LengthUnit::In).to_user(&params),
704
            400.0
705
        );
706
        assert_approx_eq_cairo!(
707
1
            Length::<Vertical>::new(10.0, LengthUnit::In).to_user(&params),
708
            500.0
709
        );
710

            
711
        assert_approx_eq_cairo!(
712
1
            Length::<Horizontal>::new(10.0, LengthUnit::Cm).to_user(&params),
713
            400.0 / CM_PER_INCH
714
        );
715
        assert_approx_eq_cairo!(
716
1
            Length::<Horizontal>::new(10.0, LengthUnit::Mm).to_user(&params),
717
            400.0 / MM_PER_INCH
718
        );
719
        assert_approx_eq_cairo!(
720
1
            Length::<Horizontal>::new(10.0, LengthUnit::Pt).to_user(&params),
721
            400.0 / POINTS_PER_INCH
722
        );
723
        assert_approx_eq_cairo!(
724
1
            Length::<Horizontal>::new(10.0, LengthUnit::Pc).to_user(&params),
725
            400.0 / PICA_PER_INCH
726
        );
727
2
    }
728

            
729
    #[test]
730
2
    fn normalize_percent_works() {
731
1
        let viewport = Viewport::new(Dpi::new(40.0, 40.0), 100.0, 200.0);
732
1
        let values = ComputedValues::default();
733
1
        let params = NormalizeParams::new(&values, &viewport);
734

            
735
        assert_approx_eq_cairo!(
736
1
            Length::<Horizontal>::new(0.05, LengthUnit::Percent).to_user(&params),
737
            5.0
738
        );
739
        assert_approx_eq_cairo!(
740
1
            Length::<Vertical>::new(0.05, LengthUnit::Percent).to_user(&params),
741
            10.0
742
        );
743
2
    }
744

            
745
    #[test]
746
2
    fn normalize_font_em_ex_ch_works() {
747
1
        let mut values = ComputedValues::default();
748
1
        let viewport = Viewport::new(Dpi::new(40.0, 40.0), 100.0, 200.0);
749
1
        let mut params = NormalizeParams::new(&values, &viewport);
750

            
751
        // These correspond to the default size for the font-size
752
        // property and the way we compute Em/Ex from that.
753

            
754
        assert_approx_eq_cairo!(
755
1
            Length::<Vertical>::new(1.0, LengthUnit::Em).to_user(&params),
756
            12.0
757
        );
758

            
759
        assert_approx_eq_cairo!(
760
1
            Length::<Vertical>::new(1.0, LengthUnit::Ex).to_user(&params),
761
            6.0
762
        );
763

            
764
        assert_approx_eq_cairo!(
765
1
            Length::<Vertical>::new(1.0, LengthUnit::Ch).to_user(&params),
766
            6.0
767
        );
768

            
769
        // check for vertical upright text
770
1
        let mut specified = SpecifiedValues::default();
771
1
        specified.set_parsed_property(&ParsedProperty::TextOrientation(SpecifiedValue::Specified(
772
            TextOrientation::Upright,
773
        )));
774
1
        specified.set_parsed_property(&ParsedProperty::WritingMode(SpecifiedValue::Specified(
775
            WritingMode::VerticalLr,
776
        )));
777
1
        specified.to_computed_values(&mut values);
778
1
        params = NormalizeParams::new(&values, &viewport);
779
        assert_approx_eq_cairo!(
780
1
            Length::<Vertical>::new(1.0, LengthUnit::Ch).to_user(&params),
781
            12.0
782
        );
783
2
    }
784

            
785
    #[test]
786
2
    fn to_points_works() {
787
1
        let params = NormalizeParams::from_dpi(Dpi::new(40.0, 96.0));
788

            
789
        assert_approx_eq_cairo!(
790
1
            Length::<Horizontal>::new(80.0, LengthUnit::Px).to_points(&params),
791
            2.0 * 72.0
792
        );
793
        assert_approx_eq_cairo!(
794
1
            Length::<Vertical>::new(192.0, LengthUnit::Px).to_points(&params),
795
            2.0 * 72.0
796
        );
797
2
    }
798
}