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(¶ms),
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(¶ms),
649 400.0
650 );
651 assert_approx_eq_cairo!(
652 Length::<Vertical>::new(10.0, LengthUnit::In).to_user(¶ms),
653 500.0
654 );
655
656 assert_approx_eq_cairo!(
657 Length::<Horizontal>::new(10.0, LengthUnit::Cm).to_user(¶ms),
658 400.0 / CM_PER_INCH
659 );
660 assert_approx_eq_cairo!(
661 Length::<Horizontal>::new(10.0, LengthUnit::Mm).to_user(¶ms),
662 400.0 / MM_PER_INCH
663 );
664 assert_approx_eq_cairo!(
665 Length::<Horizontal>::new(10.0, LengthUnit::Pt).to_user(¶ms),
666 400.0 / POINTS_PER_INCH
667 );
668 assert_approx_eq_cairo!(
669 Length::<Horizontal>::new(10.0, LengthUnit::Pc).to_user(¶ms),
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(¶ms),
682 5.0
683 );
684 assert_approx_eq_cairo!(
685 Length::<Vertical>::new(0.05, LengthUnit::Percent).to_user(¶ms),
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(¶ms),
701 12.0
702 );
703
704 assert_approx_eq_cairo!(
705 Length::<Vertical>::new(1.0, LengthUnit::Ex).to_user(¶ms),
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(¶ms),
716 2.0 * 72.0
717 );
718 assert_approx_eq_cairo!(
719 Length::<Vertical>::new(192.0, LengthUnit::Px).to_points(¶ms),
720 2.0 * 72.0
721 );
722 }
723 }
724