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 librsvg::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, _cssparser_internal_to_lowercase};
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::ViewParams;
52 use crate::error::*;
53 use crate::parsers::{finite_f32, Parse};
54 use crate::properties::ComputedValues;
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 #[repr(C)]
61 #[derive(Debug, PartialEq, Copy, Clone)]
62 pub enum LengthUnit {
63     /// `1.0` means 100%
64     Percent,
65 
66     /// Pixels, or the CSS default unit
67     Px,
68 
69     /// Size of the current font
70     Em,
71 
72     /// x-height of the current font
73     Ex,
74 
75     /// Inches (25.4 mm)
76     In,
77 
78     /// Centimeters
79     Cm,
80 
81     /// Millimeters
82     Mm,
83 
84     /// Points (1/72 inch)
85     Pt,
86 
87     /// Picas (12 points)
88     Pc,
89 }
90 
91 /// A CSS length value.
92 ///
93 /// This is equivalent to [CSS lengths].
94 ///
95 /// [CSS lengths]: https://www.w3.org/TR/CSS22/syndata.html#length-units
96 ///
97 /// It is up to the calling application to convert lengths in non-pixel units (i.e. those
98 /// where the [`unit`][RsvgLength::unit] field is not [`LengthUnit::Px`]) into something
99 /// meaningful to the application.  For example, if your application knows the
100 /// dots-per-inch (DPI) it is using, it can convert lengths with [`unit`] in
101 /// [`LengthUnit::In`] or other physical units.
102 // Keep this in sync with rsvg.h:RsvgLength
103 #[repr(C)]
104 #[derive(Debug, PartialEq, Copy, Clone)]
105 pub struct RsvgLength {
106     /// Numeric part of the length
107     pub length: f64,
108 
109     /// Unit part of the length
110     pub unit: LengthUnit,
111 }
112 
113 impl RsvgLength {
new(l: f64, unit: LengthUnit) -> RsvgLength114     pub fn new(l: f64, unit: LengthUnit) -> RsvgLength {
115         RsvgLength { length: l, unit }
116     }
117 }
118 
119 /// Used for the `N` type parameter of `CssLength<N: Normalize, V: Validate>`.
120 pub trait Normalize {
121     /// Computes an orientation-based scaling factor.
122     ///
123     /// This is used in the [`CssLength::to_user`] method to resolve lengths with percentage
124     /// units; they need to be resolved with respect to the width, height, or [normalized
125     /// diagonal][diag] of the current viewport.
126     ///
127     /// [diag]: https://www.w3.org/TR/SVG/coords.html#Units
normalize(x: f64, y: f64) -> f64128     fn normalize(x: f64, y: f64) -> f64;
129 }
130 
131 /// Allows declaring `CssLength<Horizontal>`.
132 #[derive(Debug, PartialEq, Copy, Clone)]
133 pub struct Horizontal;
134 
135 /// Allows declaring `CssLength<Vertical>`.
136 #[derive(Debug, PartialEq, Copy, Clone)]
137 pub struct Vertical;
138 
139 /// Allows declaring `CssLength<Both>`.
140 #[derive(Debug, PartialEq, Copy, Clone)]
141 pub struct Both;
142 
143 impl Normalize for Horizontal {
144     #[inline]
normalize(x: f64, _y: f64) -> f64145     fn normalize(x: f64, _y: f64) -> f64 {
146         x
147     }
148 }
149 
150 impl Normalize for Vertical {
151     #[inline]
normalize(_x: f64, y: f64) -> f64152     fn normalize(_x: f64, y: f64) -> f64 {
153         y
154     }
155 }
156 
157 impl Normalize for Both {
158     #[inline]
normalize(x: f64, y: f64) -> f64159     fn normalize(x: f64, y: f64) -> f64 {
160         viewport_percentage(x, y)
161     }
162 }
163 
164 /// Used for the `V` type parameter of `CssLength<N: Normalize, V: Validate>`.
165 pub trait Validate {
166     /// Checks if the specified value is acceptable
167     ///
168     /// This is used when parsing a length value
validate(v: f64) -> Result<f64, ValueErrorKind>169     fn validate(v: f64) -> Result<f64, ValueErrorKind> {
170         Ok(v)
171     }
172 }
173 
174 #[derive(Debug, PartialEq, Copy, Clone)]
175 pub struct Signed;
176 
177 impl Validate for Signed {}
178 
179 #[derive(Debug, PartialEq, Copy, Clone)]
180 pub struct Unsigned;
181 
182 impl Validate for Unsigned {
validate(v: f64) -> Result<f64, ValueErrorKind>183     fn validate(v: f64) -> Result<f64, ValueErrorKind> {
184         if v >= 0.0 {
185             Ok(v)
186         } else {
187             Err(ValueErrorKind::Value(
188                 "value must be non-negative".to_string(),
189             ))
190         }
191     }
192 }
193 
194 /// A CSS length value.
195 ///
196 /// This is equivalent to [CSS lengths].
197 ///
198 /// [CSS lengths]: https://www.w3.org/TR/CSS22/syndata.html#length-units
199 ///
200 /// `CssLength` implements the [`Parse`] trait, so it can be parsed out of a
201 /// [`cssparser::Parser`].
202 ///
203 /// This type will be normally used through the type aliases [`Length`] and [`ULength`]
204 ///
205 /// Examples of construction:
206 ///
207 /// ```
208 /// # use librsvg::doctest_only::{Length,ULength,LengthUnit,Horizontal,Vertical,Both};
209 /// # use librsvg::doctest_only::Parse;
210 /// // Explicit type
211 /// let width: Length<Horizontal> = Length::new(42.0, LengthUnit::Cm);
212 ///
213 /// // Inferred type
214 /// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
215 ///
216 /// // Parsed
217 /// let radius = ULength::<Both>::parse_str("5px").unwrap();
218 /// ```
219 ///
220 /// During the rendering phase, a `CssLength` needs to be converted to user-space
221 /// coordinates with the [`CssLength::to_user`] method.
222 #[derive(Debug, PartialEq, Copy, Clone)]
223 pub struct CssLength<N: Normalize, V: Validate> {
224     /// Numeric part of the length
225     pub length: f64,
226 
227     /// Unit part of the length
228     pub unit: LengthUnit,
229 
230     /// Dummy; used internally for the type parameter `N`
231     orientation: PhantomData<N>,
232 
233     /// Dummy; used internally for the type parameter `V`
234     validation: PhantomData<V>,
235 }
236 
237 impl<N: Normalize, V: Validate> From<CssLength<N, V>> for RsvgLength {
from(l: CssLength<N, V>) -> RsvgLength238     fn from(l: CssLength<N, V>) -> RsvgLength {
239         RsvgLength {
240             length: l.length,
241             unit: l.unit,
242         }
243     }
244 }
245 
246 impl<N: Normalize, V: Validate> Default for CssLength<N, V> {
default() -> Self247     fn default() -> Self {
248         CssLength::new(0.0, LengthUnit::Px)
249     }
250 }
251 
252 pub const POINTS_PER_INCH: f64 = 72.0;
253 const CM_PER_INCH: f64 = 2.54;
254 const MM_PER_INCH: f64 = 25.4;
255 const PICA_PER_INCH: f64 = 6.0;
256 
257 impl<N: Normalize, V: Validate> Parse for CssLength<N, V> {
parse<'i>(parser: &mut Parser<'i, '_>) -> Result<CssLength<N, V>, ParseError<'i>>258     fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<CssLength<N, V>, ParseError<'i>> {
259         let l_value;
260         let l_unit;
261 
262         let token = parser.next()?.clone();
263 
264         match token {
265             Token::Number { value, .. } => {
266                 l_value = value;
267                 l_unit = LengthUnit::Px;
268             }
269 
270             Token::Percentage { unit_value, .. } => {
271                 l_value = unit_value;
272                 l_unit = LengthUnit::Percent;
273             }
274 
275             Token::Dimension {
276                 value, ref unit, ..
277             } => {
278                 l_value = value;
279 
280                 l_unit = match_ignore_ascii_case! {unit.as_ref(),
281                     "px" => LengthUnit::Px,
282                     "em" => LengthUnit::Em,
283                     "ex" => LengthUnit::Ex,
284                     "in" => LengthUnit::In,
285                     "cm" => LengthUnit::Cm,
286                     "mm" => LengthUnit::Mm,
287                     "pt" => LengthUnit::Pt,
288                     "pc" => LengthUnit::Pc,
289 
290                     _ => return Err(parser.new_unexpected_token_error(token)),
291                 };
292             }
293 
294             _ => return Err(parser.new_unexpected_token_error(token)),
295         }
296 
297         let l_value = f64::from(finite_f32(l_value).map_err(|e| parser.new_custom_error(e))?);
298 
299         <V as Validate>::validate(l_value)
300             .map_err(|e| parser.new_custom_error(e))
301             .map(|l_value| CssLength::new(l_value, l_unit))
302     }
303 }
304 
305 /// Parameters to normalize [`Length`] values to user-space distances.
306 pub struct NormalizeParams {
307     vbox: ViewBox,
308     font_size: f64,
309     dpi: Dpi,
310 }
311 
312 impl NormalizeParams {
313     /// Extracts the information needed to normalize [`Length`] values from a set of
314     /// [`ComputedValues`] and the viewport size in [`ViewParams`].
315     // TODO: It is awkward to pass a `ComputedValues` everywhere just to be able to get
316     // the font size in the end.  Can we instead have a `ComputedFontSize(FontSize)`
317     // newtype, extracted from the `ComputedValues`?
new(values: &ComputedValues, params: &ViewParams) -> NormalizeParams318     pub fn new(values: &ComputedValues, params: &ViewParams) -> NormalizeParams {
319         NormalizeParams {
320             vbox: params.vbox,
321             font_size: font_size_from_values(values, params.dpi),
322             dpi: params.dpi,
323         }
324     }
325 
326     /// Just used by rsvg-convert, where there is no font size nor viewport.
from_dpi(dpi: Dpi) -> NormalizeParams327     pub fn from_dpi(dpi: Dpi) -> NormalizeParams {
328         NormalizeParams {
329             vbox: ViewBox::from(Rect::default()),
330             font_size: 1.0,
331             dpi,
332         }
333     }
334 }
335 
336 impl<N: Normalize, V: Validate> CssLength<N, V> {
337     /// Creates a CssLength.
338     ///
339     /// The compiler needs to know the type parameters `N` and `V` which represents the
340     /// length's orientation and validation.
341     /// You can specify them explicitly, or call the parametrized method:
342     ///
343     /// ```
344     /// # use librsvg::doctest_only::{Length,LengthUnit,Horizontal,Vertical};
345     /// // Explicit type
346     /// let width: Length<Horizontal> = Length::new(42.0, LengthUnit::Cm);
347     ///
348     /// // Inferred type
349     /// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
350     /// ```
new(l: f64, unit: LengthUnit) -> CssLength<N, V>351     pub fn new(l: f64, unit: LengthUnit) -> CssLength<N, V> {
352         CssLength {
353             length: l,
354             unit,
355             orientation: PhantomData,
356             validation: PhantomData,
357         }
358     }
359 
360     /// Convert a Length with units into user-space coordinates.
361     ///
362     /// Lengths may come with non-pixel units, and when rendering, they need to be normalized
363     /// to pixels based on the current viewport (e.g. for lengths with percent units), and
364     /// based on the current element's set of [`ComputedValues`] (e.g. for lengths with `Em`
365     /// units that need to be resolved against the current font size).
366     ///
367     /// Those parameters can be obtained with [`NormalizeParams::new()`].
to_user(&self, params: &NormalizeParams) -> f64368     pub fn to_user(&self, params: &NormalizeParams) -> f64 {
369         match self.unit {
370             LengthUnit::Px => self.length,
371 
372             LengthUnit::Percent => {
373                 self.length * <N as Normalize>::normalize(params.vbox.width(), params.vbox.height())
374             }
375 
376             LengthUnit::Em => self.length * params.font_size,
377 
378             LengthUnit::Ex => self.length * params.font_size / 2.0,
379 
380             LengthUnit::In => self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y),
381 
382             LengthUnit::Cm => {
383                 self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y) / CM_PER_INCH
384             }
385 
386             LengthUnit::Mm => {
387                 self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y) / MM_PER_INCH
388             }
389 
390             LengthUnit::Pt => {
391                 self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y)
392                     / POINTS_PER_INCH
393             }
394 
395             LengthUnit::Pc => {
396                 self.length * <N as Normalize>::normalize(params.dpi.x, params.dpi.y)
397                     / PICA_PER_INCH
398             }
399         }
400     }
401 
402     /// Converts a Length to points.  Pixels are taken to be respect with the DPI.
403     ///
404     /// # Panics
405     ///
406     /// Will panic if the length is in Percent, Em, or Ex units.
to_points(&self, params: &NormalizeParams) -> f64407     pub fn to_points(&self, params: &NormalizeParams) -> f64 {
408         match self.unit {
409             LengthUnit::Px => {
410                 self.length / <N as Normalize>::normalize(params.dpi.x, params.dpi.y) * 72.0
411             }
412 
413             LengthUnit::Percent => {
414                 panic!("Cannot convert a percentage length into an absolute length");
415             }
416 
417             LengthUnit::Em => {
418                 panic!("Cannot convert an Em length into an absolute length");
419             }
420 
421             LengthUnit::Ex => {
422                 panic!("Cannot convert an Ex length into an absolute length");
423             }
424 
425             LengthUnit::In => self.length * POINTS_PER_INCH,
426 
427             LengthUnit::Cm => self.length / CM_PER_INCH * POINTS_PER_INCH,
428 
429             LengthUnit::Mm => self.length / MM_PER_INCH * POINTS_PER_INCH,
430 
431             LengthUnit::Pt => self.length,
432 
433             LengthUnit::Pc => self.length / PICA_PER_INCH * POINTS_PER_INCH,
434         }
435     }
436 
to_inches(&self, params: &NormalizeParams) -> f64437     pub fn to_inches(&self, params: &NormalizeParams) -> f64 {
438         self.to_points(params) / POINTS_PER_INCH
439     }
440 
to_cm(&self, params: &NormalizeParams) -> f64441     pub fn to_cm(&self, params: &NormalizeParams) -> f64 {
442         self.to_inches(params) * CM_PER_INCH
443     }
444 
to_mm(&self, params: &NormalizeParams) -> f64445     pub fn to_mm(&self, params: &NormalizeParams) -> f64 {
446         self.to_inches(params) * MM_PER_INCH
447     }
448 
to_picas(&self, params: &NormalizeParams) -> f64449     pub fn to_picas(&self, params: &NormalizeParams) -> f64 {
450         self.to_inches(params) * PICA_PER_INCH
451     }
452 }
453 
font_size_from_values(values: &ComputedValues, dpi: Dpi) -> f64454 fn font_size_from_values(values: &ComputedValues, dpi: Dpi) -> f64 {
455     let v = &values.font_size().value();
456 
457     match v.unit {
458         LengthUnit::Percent => unreachable!("ComputedValues can't have a relative font size"),
459 
460         LengthUnit::Px => v.length,
461 
462         // The following implies that our default font size is 12, which
463         // matches the default from the FontSize property.
464         LengthUnit::Em => v.length * 12.0,
465         LengthUnit::Ex => v.length * 12.0 / 2.0,
466 
467         // FontSize always is a Both, per properties.rs
468         LengthUnit::In => v.length * Both::normalize(dpi.x, dpi.y),
469         LengthUnit::Cm => v.length * Both::normalize(dpi.x, dpi.y) / CM_PER_INCH,
470         LengthUnit::Mm => v.length * Both::normalize(dpi.x, dpi.y) / MM_PER_INCH,
471         LengthUnit::Pt => v.length * Both::normalize(dpi.x, dpi.y) / POINTS_PER_INCH,
472         LengthUnit::Pc => v.length * Both::normalize(dpi.x, dpi.y) / PICA_PER_INCH,
473     }
474 }
475 
viewport_percentage(x: f64, y: f64) -> f64476 fn viewport_percentage(x: f64, y: f64) -> f64 {
477     // https://www.w3.org/TR/SVG/coords.html#Units
478     // "For any other length value expressed as a percentage of the viewport, the
479     // percentage is calculated as the specified percentage of
480     // sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
481     (x * x + y * y).sqrt() / SQRT_2
482 }
483 
484 /// Alias for `CssLength` types that can have negative values
485 pub type Length<N> = CssLength<N, Signed>;
486 
487 /// Alias for `CssLength` types that are non negative
488 pub type ULength<N> = CssLength<N, Unsigned>;
489 
490 #[derive(Debug, PartialEq, Copy, Clone)]
491 pub enum LengthOrAuto<N: Normalize> {
492     Length(CssLength<N, Unsigned>),
493     Auto,
494 }
495 
496 impl<N: Normalize> Default for LengthOrAuto<N> {
default() -> Self497     fn default() -> Self {
498         LengthOrAuto::Auto
499     }
500 }
501 
502 impl<N: Normalize> Parse for LengthOrAuto<N> {
parse<'i>(parser: &mut Parser<'i, '_>) -> Result<LengthOrAuto<N>, ParseError<'i>>503     fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<LengthOrAuto<N>, ParseError<'i>> {
504         if parser
505             .try_parse(|i| i.expect_ident_matching("auto"))
506             .is_ok()
507         {
508             Ok(LengthOrAuto::Auto)
509         } else {
510             Ok(LengthOrAuto::Length(CssLength::parse(parser)?))
511         }
512     }
513 }
514 
515 impl fmt::Display for LengthUnit {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result516     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517         let unit = match &self {
518             LengthUnit::Percent => "%",
519             LengthUnit::Px => "px",
520             LengthUnit::Em => "em",
521             LengthUnit::Ex => "ex",
522             LengthUnit::In => "in",
523             LengthUnit::Cm => "cm",
524             LengthUnit::Mm => "mm",
525             LengthUnit::Pt => "pt",
526             LengthUnit::Pc => "pc",
527         };
528 
529         write!(f, "{}", unit)
530     }
531 }
532 
533 #[cfg(test)]
534 mod tests {
535     use super::*;
536 
537     use crate::float_eq_cairo::ApproxEqCairo;
538 
539     #[test]
parses_default()540     fn parses_default() {
541         assert_eq!(
542             Length::<Horizontal>::parse_str("42").unwrap(),
543             Length::<Horizontal>::new(42.0, LengthUnit::Px)
544         );
545 
546         assert_eq!(
547             Length::<Horizontal>::parse_str("-42px").unwrap(),
548             Length::<Horizontal>::new(-42.0, LengthUnit::Px)
549         );
550     }
551 
552     #[test]
parses_percent()553     fn parses_percent() {
554         assert_eq!(
555             Length::<Horizontal>::parse_str("50.0%").unwrap(),
556             Length::<Horizontal>::new(0.5, LengthUnit::Percent)
557         );
558     }
559 
560     #[test]
parses_font_em()561     fn parses_font_em() {
562         assert_eq!(
563             Length::<Vertical>::parse_str("22.5em").unwrap(),
564             Length::<Vertical>::new(22.5, LengthUnit::Em)
565         );
566     }
567 
568     #[test]
parses_font_ex()569     fn parses_font_ex() {
570         assert_eq!(
571             Length::<Vertical>::parse_str("22.5ex").unwrap(),
572             Length::<Vertical>::new(22.5, LengthUnit::Ex)
573         );
574     }
575 
576     #[test]
parses_physical_units()577     fn parses_physical_units() {
578         assert_eq!(
579             Length::<Both>::parse_str("72pt").unwrap(),
580             Length::<Both>::new(72.0, LengthUnit::Pt)
581         );
582 
583         assert_eq!(
584             Length::<Both>::parse_str("-22.5in").unwrap(),
585             Length::<Both>::new(-22.5, LengthUnit::In)
586         );
587 
588         assert_eq!(
589             Length::<Both>::parse_str("-254cm").unwrap(),
590             Length::<Both>::new(-254.0, LengthUnit::Cm)
591         );
592 
593         assert_eq!(
594             Length::<Both>::parse_str("254mm").unwrap(),
595             Length::<Both>::new(254.0, LengthUnit::Mm)
596         );
597 
598         assert_eq!(
599             Length::<Both>::parse_str("60pc").unwrap(),
600             Length::<Both>::new(60.0, LengthUnit::Pc)
601         );
602     }
603 
604     #[test]
parses_unsigned()605     fn parses_unsigned() {
606         assert_eq!(
607             ULength::<Horizontal>::parse_str("42").unwrap(),
608             ULength::<Horizontal>::new(42.0, LengthUnit::Px)
609         );
610 
611         assert_eq!(
612             ULength::<Both>::parse_str("0pt").unwrap(),
613             ULength::<Both>::new(0.0, LengthUnit::Pt)
614         );
615 
616         assert!(ULength::<Horizontal>::parse_str("-42px").is_err());
617     }
618 
619     #[test]
empty_length_yields_error()620     fn empty_length_yields_error() {
621         assert!(Length::<Both>::parse_str("").is_err());
622     }
623 
624     #[test]
invalid_unit_yields_error()625     fn invalid_unit_yields_error() {
626         assert!(Length::<Both>::parse_str("8furlong").is_err());
627     }
628 
629     #[test]
normalize_default_works()630     fn normalize_default_works() {
631         let view_params = ViewParams::new(Dpi::new(40.0, 40.0), 100.0, 100.0);
632         let values = ComputedValues::default();
633         let params = NormalizeParams::new(&values, &view_params);
634 
635         assert_approx_eq_cairo!(
636             Length::<Both>::new(10.0, LengthUnit::Px).to_user(&params),
637             10.0
638         );
639     }
640 
641     #[test]
normalize_absolute_units_works()642     fn normalize_absolute_units_works() {
643         let view_params = ViewParams::new(Dpi::new(40.0, 50.0), 100.0, 100.0);
644         let values = ComputedValues::default();
645         let params = NormalizeParams::new(&values, &view_params);
646 
647         assert_approx_eq_cairo!(
648             Length::<Horizontal>::new(10.0, LengthUnit::In).to_user(&params),
649             400.0
650         );
651         assert_approx_eq_cairo!(
652             Length::<Vertical>::new(10.0, LengthUnit::In).to_user(&params),
653             500.0
654         );
655 
656         assert_approx_eq_cairo!(
657             Length::<Horizontal>::new(10.0, LengthUnit::Cm).to_user(&params),
658             400.0 / CM_PER_INCH
659         );
660         assert_approx_eq_cairo!(
661             Length::<Horizontal>::new(10.0, LengthUnit::Mm).to_user(&params),
662             400.0 / MM_PER_INCH
663         );
664         assert_approx_eq_cairo!(
665             Length::<Horizontal>::new(10.0, LengthUnit::Pt).to_user(&params),
666             400.0 / POINTS_PER_INCH
667         );
668         assert_approx_eq_cairo!(
669             Length::<Horizontal>::new(10.0, LengthUnit::Pc).to_user(&params),
670             400.0 / PICA_PER_INCH
671         );
672     }
673 
674     #[test]
normalize_percent_works()675     fn normalize_percent_works() {
676         let view_params = ViewParams::new(Dpi::new(40.0, 40.0), 100.0, 200.0);
677         let values = ComputedValues::default();
678         let params = NormalizeParams::new(&values, &view_params);
679 
680         assert_approx_eq_cairo!(
681             Length::<Horizontal>::new(0.05, LengthUnit::Percent).to_user(&params),
682             5.0
683         );
684         assert_approx_eq_cairo!(
685             Length::<Vertical>::new(0.05, LengthUnit::Percent).to_user(&params),
686             10.0
687         );
688     }
689 
690     #[test]
normalize_font_em_ex_works()691     fn normalize_font_em_ex_works() {
692         let view_params = ViewParams::new(Dpi::new(40.0, 40.0), 100.0, 200.0);
693         let values = ComputedValues::default();
694         let params = NormalizeParams::new(&values, &view_params);
695 
696         // These correspond to the default size for the font-size
697         // property and the way we compute Em/Ex from that.
698 
699         assert_approx_eq_cairo!(
700             Length::<Vertical>::new(1.0, LengthUnit::Em).to_user(&params),
701             12.0
702         );
703 
704         assert_approx_eq_cairo!(
705             Length::<Vertical>::new(1.0, LengthUnit::Ex).to_user(&params),
706             6.0
707         );
708     }
709 
710     #[test]
to_points_works()711     fn to_points_works() {
712         let params = NormalizeParams::from_dpi(Dpi::new(40.0, 96.0));
713 
714         assert_approx_eq_cairo!(
715             Length::<Horizontal>::new(80.0, LengthUnit::Px).to_points(&params),
716             2.0 * 72.0
717         );
718         assert_approx_eq_cairo!(
719             Length::<Vertical>::new(192.0, LengthUnit::Px).to_points(&params),
720             2.0 * 72.0
721         );
722     }
723 }
724