Browse Source

LibGfx/OpenType: Load x-height metrics from OS/2 table if available

Before this change we always returned the font's point size as the
x-height which was basically never correct.

We now get it from the OS/2 table (if one with version >= 2 is available
in the file). Otherwise we fall back to using the ascent of the 'x'
glyph. Most fonts appear to have a sufficiently modern OS/2 table.
Andreas Kling 2 years ago
parent
commit
69c33bd4ca

+ 4 - 0
Tests/LibWeb/Layout/expected/css-ex-unit.txt

@@ -0,0 +1,4 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+  BlockContainer <html> at (0,0) content-size 800x177.132812 [BFC] children: not-inline
+    BlockContainer <body> at (8,8) content-size 784x161.132812 children: not-inline
+      BlockContainer <div> at (8,8) content-size 107.421875x161.132812 children: not-inline

+ 8 - 0
Tests/LibWeb/Layout/input/css-ex-unit.html

@@ -0,0 +1,8 @@
+<!doctype html><style>
+div {
+    font: 20px SerenitySans;
+    width: 10ex;
+    height: 15ex;
+    background: red;
+}
+</style><div>

+ 14 - 0
Userland/Libraries/LibGfx/Font/OpenType/Font.cpp

@@ -574,21 +574,28 @@ Gfx::ScaledFontMetrics Font::metrics([[maybe_unused]] float x_scale, float y_sca
     i16 raw_ascender;
     i16 raw_ascender;
     i16 raw_descender;
     i16 raw_descender;
     i16 raw_line_gap;
     i16 raw_line_gap;
+    Optional<i16> x_height;
 
 
     if (m_os2.has_value() && m_os2->use_typographic_metrics()) {
     if (m_os2.has_value() && m_os2->use_typographic_metrics()) {
         raw_ascender = m_os2->typographic_ascender();
         raw_ascender = m_os2->typographic_ascender();
         raw_descender = m_os2->typographic_descender();
         raw_descender = m_os2->typographic_descender();
         raw_line_gap = m_os2->typographic_line_gap();
         raw_line_gap = m_os2->typographic_line_gap();
+        x_height = m_os2->x_height();
     } else {
     } else {
         raw_ascender = m_hhea.ascender();
         raw_ascender = m_hhea.ascender();
         raw_descender = m_hhea.descender();
         raw_descender = m_hhea.descender();
         raw_line_gap = m_hhea.line_gap();
         raw_line_gap = m_hhea.line_gap();
     }
     }
 
 
+    if (!x_height.has_value()) {
+        x_height = glyph_metrics(glyph_id_for_code_point('x'), 1, 1, 1, 1).ascender;
+    }
+
     return Gfx::ScaledFontMetrics {
     return Gfx::ScaledFontMetrics {
         .ascender = static_cast<float>(raw_ascender) * y_scale,
         .ascender = static_cast<float>(raw_ascender) * y_scale,
         .descender = -static_cast<float>(raw_descender) * y_scale,
         .descender = -static_cast<float>(raw_descender) * y_scale,
         .line_gap = static_cast<float>(raw_line_gap) * y_scale,
         .line_gap = static_cast<float>(raw_line_gap) * y_scale,
+        .x_height = static_cast<float>(x_height.value()) * y_scale,
     };
     };
 }
 }
 
 
@@ -854,6 +861,13 @@ bool OS2::use_typographic_metrics() const
     return header().fs_selection & 0x80;
     return header().fs_selection & 0x80;
 }
 }
 
 
+Optional<i16> OS2::x_height() const
+{
+    if (header().version < 2)
+        return {};
+    return header_v2().sx_height;
+}
+
 Optional<ReadonlyBytes> Font::font_program() const
 Optional<ReadonlyBytes> Font::font_program() const
 {
 {
     if (m_fpgm.has_value())
     if (m_fpgm.has_value())

+ 22 - 0
Userland/Libraries/LibGfx/Font/OpenType/Tables.h

@@ -243,6 +243,8 @@ public:
 
 
     bool use_typographic_metrics() const;
     bool use_typographic_metrics() const;
 
 
+    [[nodiscard]] Optional<i16> x_height() const;
+
     explicit OS2(ReadonlyBytes slice)
     explicit OS2(ReadonlyBytes slice)
         : m_slice(slice)
         : m_slice(slice)
     {
     {
@@ -282,7 +284,27 @@ private:
         BigEndian<u16> us_win_descent;
         BigEndian<u16> us_win_descent;
     };
     };
 
 
+    struct Version1 {
+        Version0 version0;
+        BigEndian<u32> ul_code_page_range1;
+        BigEndian<u32> ul_code_page_range2;
+    };
+
+    struct Version2 {
+        Version1 version1;
+        BigEndian<i16> sx_height;
+        BigEndian<i16> s_cap_height;
+        BigEndian<u16> us_default_char;
+        BigEndian<u16> us_break_char;
+        BigEndian<u16> us_max_context;
+    };
+
     Version0 const& header() const { return *bit_cast<Version0 const*>(m_slice.data()); }
     Version0 const& header() const { return *bit_cast<Version0 const*>(m_slice.data()); }
+    Version2 const& header_v2() const
+    {
+        VERIFY(header().version >= 2);
+        return *bit_cast<Version2 const*>(m_slice.data());
+    }
 
 
     ReadonlyBytes m_slice;
     ReadonlyBytes m_slice;
 };
 };

+ 1 - 1
Userland/Libraries/LibGfx/Font/ScaledFont.cpp

@@ -27,7 +27,7 @@ ScaledFont::ScaledFont(NonnullRefPtr<VectorFont> font, float point_width, float
 
 
     m_pixel_metrics = Gfx::FontPixelMetrics {
     m_pixel_metrics = Gfx::FontPixelMetrics {
         .size = (float)pixel_size(),
         .size = (float)pixel_size(),
-        .x_height = (float)x_height(),
+        .x_height = metrics.x_height,
         .advance_of_ascii_zero = (float)glyph_width('0'),
         .advance_of_ascii_zero = (float)glyph_width('0'),
         .glyph_spacing = (float)glyph_spacing(),
         .glyph_spacing = (float)glyph_spacing(),
         .ascent = metrics.ascender,
         .ascent = metrics.ascender,

+ 1 - 0
Userland/Libraries/LibGfx/Font/VectorFont.h

@@ -17,6 +17,7 @@ struct ScaledFontMetrics {
     float ascender { 0 };
     float ascender { 0 };
     float descender { 0 };
     float descender { 0 };
     float line_gap { 0 };
     float line_gap { 0 };
+    float x_height { 0 };
 
 
     float height() const
     float height() const
     {
     {