[librsvg: 1/2] (#605): Compute 'bolder' and 'lighter' font-weight correctly



commit 88397d9f1cff6964ded0734790e72427a89ad940
Author: Federico Mena Quintero <federico gnome org>
Date:   Wed Jun 10 20:05:58 2020 -0500

    (#605): Compute 'bolder' and 'lighter' font-weight correctly
    
    Previously, font-weight="bolder" or "lighter" would map
    unconditionally to pango's Ultrabold and Light, which is wrong as
    those are relative values that must be computed with respect to the
    inherited value.
    
    We now do this:
    
    * First, support arbitrary numbers in the range [1, 1000] instead of
    the fixed set 100/200/.../900, per css-fonts-4 -
    https://drafts.csswg.org/css-fonts-4/#font-weight-prop
    
    * Second, use the "relative weights" table from
    https://drafts.csswg.org/css-fonts-4/#relative-weights
    
    Note that this is different from
    https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-weight
    which simply made "bolder" and "lighter" go up and down the 100..900
    scale.  That had the effect of not always performing a visible change
    in weight, since most font families only have a few weights available
    and do not cover the whole range.
    
    Fixes https://gitlab.gnome.org/GNOME/librsvg/-/issues/605

 Cargo.lock                                         |   1 +
 rsvg_internals/Cargo.toml                          |   1 +
 rsvg_internals/src/font_props.rs                   |  88 ++++++++++++++------
 rsvg_internals/src/property_defs.rs                |  14 +++-
 rsvg_internals/src/text.rs                         |  16 +---
 .../reftests/svg1.1/text-fonts-02-t-ref.png        | Bin 0 -> 17168 bytes
 tests/fixtures/reftests/svg1.1/text-fonts-02-t.svg |  92 +++++++++++++++++++++
 7 files changed, 172 insertions(+), 40 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index bb117eb6..f7a71ada 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1318,6 +1318,7 @@ version = "0.0.1"
 dependencies = [
  "cairo-rs 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cairo-sys-rs 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.27.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "data-url 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/rsvg_internals/Cargo.toml b/rsvg_internals/Cargo.toml
index 08a73243..d95ef8b2 100644
--- a/rsvg_internals/Cargo.toml
+++ b/rsvg_internals/Cargo.toml
@@ -12,6 +12,7 @@ edition = "2018"
 #   librsvg_crate/src/lib.rs - toplevel example in the docs
 cairo-rs = { version="0.8.0", features=["v1_16"] }
 cairo-sys-rs = "0.9.0"
+cast = "0.2.3"
 cssparser = "0.27.1"
 data-url = "0.1"
 encoding = "0.2.33"
diff --git a/rsvg_internals/src/font_props.rs b/rsvg_internals/src/font_props.rs
index e09061d9..3b00a6d8 100644
--- a/rsvg_internals/src/font_props.rs
+++ b/rsvg_internals/src/font_props.rs
@@ -1,5 +1,6 @@
 //! CSS font properties.
 
+use cast::u16;
 use cssparser::Parser;
 
 use crate::drawing_ctx::ViewParams;
@@ -104,22 +105,14 @@ impl Parse for FontSizeSpec {
     }
 }
 
-// https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-weight
+// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum FontWeightSpec {
     Normal,
     Bold,
     Bolder,
     Lighter,
-    W100,
-    W200,
-    W300,
-    W400,
-    W500,
-    W600,
-    W700,
-    W800,
-    W900,
+    Weight(u16),
 }
 
 impl Parse for FontWeightSpec {
@@ -137,22 +130,69 @@ impl Parse for FontWeightSpec {
             .or_else(|_: ParseError| {
                 let loc = parser.current_source_location();
                 let i = parser.expect_integer()?;
-                match i {
-                    100 => Ok(FontWeightSpec::W100),
-                    200 => Ok(FontWeightSpec::W200),
-                    300 => Ok(FontWeightSpec::W300),
-                    400 => Ok(FontWeightSpec::W400),
-                    500 => Ok(FontWeightSpec::W500),
-                    600 => Ok(FontWeightSpec::W600),
-                    700 => Ok(FontWeightSpec::W700),
-                    800 => Ok(FontWeightSpec::W800),
-                    900 => Ok(FontWeightSpec::W900),
-                    _ => Err(loc.new_custom_error(ValueErrorKind::parse_error("parse error"))),
+                if (1..=1000).contains(&i) {
+                    Ok(FontWeightSpec::Weight(u16(i).unwrap()))
+                } else {
+                    Err(loc.new_custom_error(ValueErrorKind::value_error(
+                        "value must be between 1 and 1000 inclusive",
+                    )))
                 }
             })
     }
 }
 
+impl FontWeightSpec {
+    #[rustfmt::skip]
+    pub fn compute(&self, v: &Self) -> Self {
+        use FontWeightSpec::*;
+
+        // Here, note that we assume that Normal=W400 and Bold=W700, per the spec.  Also,
+        // this must match `impl From<FontWeightSpec> for pango::Weight`.
+        //
+        // See the table at https://drafts.csswg.org/css-fonts-4/#relative-weights
+
+        match *self {
+            Bolder => match v.numeric_weight() {
+                w if (  1..100).contains(&w) => Weight(400),
+                w if (100..350).contains(&w) => Weight(400),
+                w if (350..550).contains(&w) => Weight(700),
+                w if (550..750).contains(&w) => Weight(900),
+                w if (750..900).contains(&w) => Weight(900),
+                w if 900 <= w                => Weight(w),
+
+                _ => unreachable!(),
+            }
+
+            Lighter => match v.numeric_weight() {
+                w if (  1..100).contains(&w) => Weight(w),
+                w if (100..350).contains(&w) => Weight(100),
+                w if (350..550).contains(&w) => Weight(100),
+                w if (550..750).contains(&w) => Weight(400),
+                w if (750..900).contains(&w) => Weight(700),
+                w if 900 <= w                => Weight(700),
+
+                _ => unreachable!(),
+            }
+
+            _ => *self,
+        }
+    }
+
+    // Converts the symbolic weights to numeric weights.  Will panic on `Bolder` or `Lighter`.
+    pub fn numeric_weight(self) -> u16 {
+        use FontWeightSpec::*;
+
+        // Here, note that we assume that Normal=W400 and Bold=W700, per the spec.  Also,
+        // this must match `impl From<FontWeightSpec> for pango::Weight`.
+        match self {
+            Normal => 400,
+            Bold => 700,
+            Bolder | Lighter => unreachable!(),
+            Weight(w) => w,
+        }
+    }
+}
+
 // https://www.w3.org/TR/css-text-3/#letter-spacing-property
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum LetterSpacingSpec {
@@ -296,7 +336,7 @@ mod tests {
         );
         assert_eq!(
             <FontWeightSpec as Parse>::parse_str("100"),
-            Ok(FontWeightSpec::W100)
+            Ok(FontWeightSpec::Weight(100))
         );
     }
 
@@ -304,7 +344,9 @@ mod tests {
     fn detects_invalid_font_weight() {
         assert!(<FontWeightSpec as Parse>::parse_str("").is_err());
         assert!(<FontWeightSpec as Parse>::parse_str("strange").is_err());
-        assert!(<FontWeightSpec as Parse>::parse_str("314").is_err());
+        assert!(<FontWeightSpec as Parse>::parse_str("0").is_err());
+        assert!(<FontWeightSpec as Parse>::parse_str("-1").is_err());
+        assert!(<FontWeightSpec as Parse>::parse_str("1001").is_err());
         assert!(<FontWeightSpec as Parse>::parse_str("3.14").is_err());
     }
 
diff --git a/rsvg_internals/src/property_defs.rs b/rsvg_internals/src/property_defs.rs
index dc67eacc..61e7ad3e 100644
--- a/rsvg_internals/src/property_defs.rs
+++ b/rsvg_internals/src/property_defs.rs
@@ -304,13 +304,23 @@ make_property!(
     "small-caps" => SmallCaps,
 );
 
-// https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-weight
+// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
 make_property!(
     ComputedValues,
     FontWeight,
     default: FontWeightSpec::Normal,
-    inherits_automatically: true,
     newtype_parse: FontWeightSpec,
+    property_impl: {
+        impl Property<ComputedValues> for FontWeight {
+            fn inherits_automatically() -> bool {
+                true
+            }
+
+            fn compute(&self, v: &ComputedValues) -> Self {
+                FontWeight(self.0.compute(&v.font_weight().0))
+            }
+        }
+    }
 );
 
 // https://www.w3.org/TR/SVG/text.html#LetterSpacingProperty
diff --git a/rsvg_internals/src/text.rs b/rsvg_internals/src/text.rs
index 1fc14009..d93a9773 100644
--- a/rsvg_internals/src/text.rs
+++ b/rsvg_internals/src/text.rs
@@ -846,21 +846,7 @@ impl From<FontStretch> for pango::Stretch {
 
 impl From<FontWeightSpec> for pango::Weight {
     fn from(w: FontWeightSpec) -> pango::Weight {
-        match w {
-            FontWeightSpec::Normal => pango::Weight::Normal,
-            FontWeightSpec::Bold => pango::Weight::Bold,
-            FontWeightSpec::Bolder => pango::Weight::Ultrabold,
-            FontWeightSpec::Lighter => pango::Weight::Light,
-            FontWeightSpec::W100 => pango::Weight::Thin,
-            FontWeightSpec::W200 => pango::Weight::Ultralight,
-            FontWeightSpec::W300 => pango::Weight::Semilight,
-            FontWeightSpec::W400 => pango::Weight::Normal,
-            FontWeightSpec::W500 => pango::Weight::Medium,
-            FontWeightSpec::W600 => pango::Weight::Semibold,
-            FontWeightSpec::W700 => pango::Weight::Bold,
-            FontWeightSpec::W800 => pango::Weight::Ultrabold,
-            FontWeightSpec::W900 => pango::Weight::Heavy,
-        }
+        pango::Weight::__Unknown(w.numeric_weight().into())
     }
 }
 
diff --git a/tests/fixtures/reftests/svg1.1/text-fonts-02-t-ref.png 
b/tests/fixtures/reftests/svg1.1/text-fonts-02-t-ref.png
new file mode 100644
index 00000000..36cdc385
Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/text-fonts-02-t-ref.png differ
diff --git a/tests/fixtures/reftests/svg1.1/text-fonts-02-t.svg 
b/tests/fixtures/reftests/svg1.1/text-fonts-02-t.svg
new file mode 100644
index 00000000..ba294714
--- /dev/null
+++ b/tests/fixtures/reftests/svg1.1/text-fonts-02-t.svg
@@ -0,0 +1,92 @@
+<svg version="1.1" baseProfile="tiny" id="svg-root"
+  width="100%" height="100%" viewBox="0 0 480 360"
+  xmlns="http://www.w3.org/2000/svg"; xmlns:xlink="http://www.w3.org/1999/xlink";>
+  <!--======================================================================-->
+  <!--=  SVG 1.1 2nd Edition Test Case                                     =-->
+  <!--======================================================================-->
+  <!--=  Copyright 2009 World Wide Web Consortium, (Massachusetts          =-->
+  <!--=  Institute of Technology, European Research Consortium for         =-->
+  <!--=  Informatics and Mathematics (ERCIM), Keio University).            =-->
+  <!--=  All Rights Reserved.                                              =-->
+  <!--=  See http://www.w3.org/Consortium/Legal/.                          =-->
+  <!--======================================================================-->
+  <d:SVGTestCase xmlns:d="http://www.w3.org/2000/02/svg/testsuite/description/";
+    template-version="1.4" reviewer="SVGWG" author="Chris lilley" status="accepted"
+    version="$Revision: 1.5 $" testname="$RCSfile: text-fonts-02-t.svg,v $">
+    <d:testDescription xmlns="http://www.w3.org/1999/xhtml"; 
href="http://www.w3.org/TR/SVG11/text.html#FontPropertiesUsedBySVG";>
+      <p>
+        Purpose of test is to determine if the font weight is being
+        correctly rendered. A number of font families are specified. The
+        numerical weight values (100 to 900) should show the lighter weights
+        on the lower numbers and the heavier weights on the larger numbers.
+        Heavier is defined to mean 'no lighter'.
+      </p>
+      <p>
+        If only one font weight is available, they should all display at the
+        same weight. The transition from black to green figures shows the
+        correct light to bold transition for the common case where two
+        weights are available. If three or more weights are available, see
+        the CSS2 specification for how these are allocated to the nine
+        weight numbers.
+      </p>
+      <p>
+        The absolute keywords 'normal' and bold' are tested
+        by the first two lines on the right hand side of the test,
+        the third line of text tests the to 'bolder'
+        relative keyword and the fourth tests the
+        'lighter' relative keyword.
+      </p>
+    </d:testDescription>
+    <d:operatorScript xmlns="http://www.w3.org/1999/xhtml";>
+      <p>
+        Run the test. No interaction required.
+      </p>
+    </d:operatorScript>
+    <d:passCriteria xmlns="http://www.w3.org/1999/xhtml";>
+      <p>
+        The numerical weight values (100 to 900) should show the lighter weights on the
+        lower numbers and the heavier weights on the larger numbers. Heavier is defined
+        to mean 'no lighter'.
+      </p>
+    </d:passCriteria>
+  </d:SVGTestCase>
+  <title id="test-title">$RCSfile: text-fonts-02-t.svg,v $</title>
+  <defs>
+    <font-face font-family="SVGFreeSansASCII" unicode-range="U+0-7F">
+      <font-face-src>
+        <font-face-uri xlink:href="../resources/SVGFreeSans.svg#ascii"/>
+      </font-face-src>
+    </font-face>
+  </defs>
+  <g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18">
+    <g font-family="Georgia,'Times New Roman',Times,'MS Mincho',serif" font-size="30">
+      <text font-weight="100" x="360" y="50">100</text>
+      <text font-weight="200" x="360" y="85">200</text>
+      <text font-weight="300" x="360" y="120">300</text>
+      <text font-weight="400" x="360" y="155">400</text>
+      <text font-weight="500" x="360" y="190">500</text>
+      <text fill="green" font-weight="600" x="360" y="225">600</text>
+      <text fill="green" font-weight="700" x="360" y="260">700</text>
+      <text fill="green" font-weight="800" x="360" y="295">800</text>
+      <text fill="green" font-weight="900" x="360" y="330">900</text>
+      <text font-weight="bold" x="60" y="80">This is bold</text>
+      <text font-weight="normal" x="60" y="130">This is normal</text>
+      <g font-weight="normal" fill="blue">
+        <text font-weight="bolder" x="60" y="180">Blue is bolder</text>
+      </g>
+      <g font-weight="bold" fill="blue">
+        <text font-weight="lighter" x="60" y="230">Blue is lighter</text>
+      </g>
+    </g>
+  </g>
+  <g font-family="SVGFreeSansASCII,sans-serif" font-size="32">
+    <text id="revision" x="10" y="340" stroke="none" fill="black">$Revision: 1.5 $</text>
+  </g>
+  <rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
+  <!-- comment out this watermark once the test is approved -->
+  <!--<g id="draft-watermark">
+    <rect x="1" y="1" width="478" height="20" fill="red" stroke="black" stroke-width="1"/>
+    <text font-family="SVGFreeSansASCII,sans-serif" font-weight="bold" font-size="20" x="240"
+      text-anchor="middle" y="18" stroke-width="0.5" stroke="black" fill="white">DRAFT</text>
+  </g>-->
+</svg>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]