Jelajahi Sumber

LibGfx: Add ability to request glyphs at subpixel offsets to fonts

This adds the option to pass a subpixel offset when fetching a glyph
from a font, this offset is currently snapped to thirds of a pixel
(i.e. 0, 0.33, 0.66). This is then used when rasterizing the glyph,
which is then cached like usual.

Note that when using subpixel offsets you're trading a bit of space
for accuracy. With the current third of a pixel offsets you can end
up with up to 9 bitmaps per glyph.
MacDue 2 tahun lalu
induk
melakukan
ada48a1daf

+ 1 - 0
Userland/Libraries/LibGfx/CMakeLists.txt

@@ -16,6 +16,7 @@ set(SOURCES
     Filters/StackBlurFilter.cpp
     Font/BitmapFont.cpp
     Font/Emoji.cpp
+    Font/Font.cpp
     Font/FontDatabase.cpp
     Font/OpenType/Cmap.cpp
     Font/OpenType/Font.cpp

+ 4 - 0
Userland/Libraries/LibGfx/Font/BitmapFont.h

@@ -52,6 +52,10 @@ public:
     void set_slope(u8 slope) { m_slope = slope; }
 
     Glyph glyph(u32 code_point) const override;
+    Glyph glyph(u32 code_point, GlyphSubpixelOffset) const override { return glyph(code_point); }
+
+    float glyph_left_bearing(u32) const override { return 0; }
+
     Glyph raw_glyph(u32 code_point) const;
     bool contains_glyph(u32 code_point) const override;
     bool contains_raw_glyph(u32 code_point) const { return m_glyph_widths[code_point] > 0; }

+ 29 - 0
Userland/Libraries/LibGfx/Font/Font.cpp

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGfx/Font/Font.h>
+
+namespace Gfx {
+
+GlyphRasterPosition GlyphRasterPosition::get_nearest_fit_for(FloatPoint position)
+{
+    constexpr auto subpixel_divisions = GlyphSubpixelOffset::subpixel_divisions();
+    auto fit = [](float pos, int& blit_pos, u8& subpixel_offset) {
+        blit_pos = floorf(pos);
+        subpixel_offset = round_to<u8>((pos - blit_pos) / (1.0f / subpixel_divisions));
+        if (subpixel_offset >= subpixel_divisions) {
+            blit_pos += 1;
+            subpixel_offset = 0;
+        }
+    };
+    int blit_x, blit_y;
+    u8 subpixel_x, subpixel_y;
+    fit(position.x(), blit_x, subpixel_x);
+    fit(position.y(), blit_y, subpixel_y);
+    return GlyphRasterPosition { { blit_x, blit_y }, { subpixel_x, subpixel_y } };
+}
+
+}

+ 25 - 0
Userland/Libraries/LibGfx/Font/Font.h

@@ -86,6 +86,29 @@ private:
     float m_ascent;
 };
 
+struct GlyphSubpixelOffset {
+    u8 x;
+    u8 y;
+
+    // TODO: Allow setting this at runtime via some config?
+    static constexpr int subpixel_divisions() { return 3; }
+    FloatPoint to_float_point() const { return FloatPoint(x / float(subpixel_divisions()), y / float(subpixel_divisions())); }
+
+    bool operator==(GlyphSubpixelOffset const&) const = default;
+};
+
+struct GlyphRasterPosition {
+    // Where the glyph bitmap should be drawn/blitted.
+    IntPoint blit_position;
+
+    // A subpixel offset to be used when rendering the glyph.
+    // This improves kerning and alignment at the expense of caching a few extra bitmaps.
+    // This is (currently) snapped to thirds of a subpixel (i.e. 0, 0.33, 0.66).
+    GlyphSubpixelOffset subpixel_offset;
+
+    static GlyphRasterPosition get_nearest_fit_for(FloatPoint position);
+};
+
 struct FontPixelMetrics {
     float size { 0 };
     float x_height { 0 };
@@ -124,8 +147,10 @@ public:
 
     virtual u16 weight() const = 0;
     virtual Glyph glyph(u32 code_point) const = 0;
+    virtual Glyph glyph(u32 code_point, GlyphSubpixelOffset) const = 0;
     virtual bool contains_glyph(u32 code_point) const = 0;
 
+    virtual float glyph_left_bearing(u32 code_point) const = 0;
     virtual float glyph_width(u32 code_point) const = 0;
     virtual float glyph_or_emoji_width(u32 code_point) const = 0;
     virtual float glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const = 0;

+ 2 - 2
Userland/Libraries/LibGfx/Font/OpenType/Font.cpp

@@ -554,14 +554,14 @@ float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, flo
 }
 
 // FIXME: "loca" and "glyf" are not available for CFF fonts.
-RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const
+RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const
 {
     if (glyph_id >= glyph_count()) {
         glyph_id = 0;
     }
     auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
     auto glyph = m_glyf.glyph(glyph_offset);
-    return glyph.rasterize(m_hhea.ascender(), m_hhea.descender(), x_scale, y_scale, [&](u16 glyph_id) {
+    return glyph.rasterize(m_hhea.ascender(), m_hhea.descender(), x_scale, y_scale, subpixel_offset, [&](u16 glyph_id) {
         if (glyph_id >= glyph_count()) {
             glyph_id = 0;
         }

+ 1 - 1
Userland/Libraries/LibGfx/Font/OpenType/Font.h

@@ -28,7 +28,7 @@ public:
     virtual Gfx::ScaledFontMetrics metrics(float x_scale, float y_scale) const override;
     virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const override;
     virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override;
-    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const override;
+    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const override;
     virtual u32 glyph_count() const override;
     virtual u16 units_per_em() const override;
     virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_cmap.glyph_id_for_code_point(code_point); }

+ 2 - 1
Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp

@@ -346,11 +346,12 @@ void Glyf::Glyph::rasterize_impl(Gfx::PathRasterizer& rasterizer, Gfx::AffineTra
     rasterizer.draw_path(path);
 }
 
-RefPtr<Gfx::Bitmap> Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale) const
+RefPtr<Gfx::Bitmap> Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const
 {
     u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 2;
     u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 2;
     Gfx::PathRasterizer rasterizer(Gfx::IntSize(width, height));
+    rasterizer.translate(subpixel_offset.to_float_point());
     auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -font_ascender);
     rasterize_impl(rasterizer, affine);
     return rasterizer.accumulate();

+ 7 - 5
Userland/Libraries/LibGfx/Font/OpenType/Glyf.h

@@ -10,6 +10,7 @@
 #include <AK/Vector.h>
 #include <LibGfx/AffineTransform.h>
 #include <LibGfx/Bitmap.h>
+#include <LibGfx/Font/Font.h>
 #include <LibGfx/Font/OpenType/Tables.h>
 #include <LibGfx/Font/PathRasterizer.h>
 #include <math.h>
@@ -55,13 +56,13 @@ public:
             }
         }
         template<typename GlyphCb>
-        RefPtr<Gfx::Bitmap> rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
+        RefPtr<Gfx::Bitmap> rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset, GlyphCb glyph_callback) const
         {
             switch (m_type) {
             case Type::Simple:
-                return rasterize_simple(font_ascender, font_descender, x_scale, y_scale);
+                return rasterize_simple(font_ascender, font_descender, x_scale, y_scale, subpixel_offset);
             case Type::Composite:
-                return rasterize_composite(font_ascender, font_descender, x_scale, y_scale, glyph_callback);
+                return rasterize_composite(font_ascender, font_descender, x_scale, y_scale, subpixel_offset, glyph_callback);
             }
             VERIFY_NOT_REACHED();
         }
@@ -94,7 +95,7 @@ public:
         };
 
         void rasterize_impl(Gfx::PathRasterizer&, Gfx::AffineTransform const&) const;
-        RefPtr<Gfx::Bitmap> rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale) const;
+        RefPtr<Gfx::Bitmap> rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const;
 
         template<typename GlyphCb>
         void rasterize_composite_loop(Gfx::PathRasterizer& rasterizer, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const
@@ -120,11 +121,12 @@ public:
         }
 
         template<typename GlyphCb>
-        RefPtr<Gfx::Bitmap> rasterize_composite(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
+        RefPtr<Gfx::Bitmap> rasterize_composite(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset, GlyphCb glyph_callback) const
         {
             u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 1;
             u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 1;
             Gfx::PathRasterizer rasterizer(Gfx::IntSize(width, height));
+            rasterizer.translate(subpixel_offset.to_float_point());
             auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -font_ascender);
 
             rasterize_composite_loop(rasterizer, affine, glyph_callback);

+ 2 - 3
Userland/Libraries/LibGfx/Font/PathRasterizer.cpp

@@ -19,9 +19,8 @@ PathRasterizer::PathRasterizer(Gfx::IntSize size)
 
 void PathRasterizer::draw_path(Gfx::Path& path)
 {
-    for (auto& line : path.split_lines()) {
-        draw_line(line.from, line.to);
-    }
+    for (auto& line : path.split_lines())
+        draw_line(line.from.translated(translation()), line.to.translated(translation()));
 }
 
 RefPtr<Gfx::Bitmap> PathRasterizer::accumulate()

+ 5 - 0
Userland/Libraries/LibGfx/Font/PathRasterizer.h

@@ -18,10 +18,15 @@ public:
     void draw_path(Gfx::Path&);
     RefPtr<Gfx::Bitmap> accumulate();
 
+    void translate(Gfx::FloatPoint delta) { m_translation.translate_by(delta); }
+    Gfx::FloatPoint translation() const { return m_translation; }
+
 private:
     void draw_line(Gfx::FloatPoint, Gfx::FloatPoint);
 
     Gfx::IntSize m_size;
+    Gfx::FloatPoint m_translation;
+
     Vector<float> m_data;
 };
 

+ 17 - 5
Userland/Libraries/LibGfx/Font/ScaledFont.cpp

@@ -38,25 +38,37 @@ ALWAYS_INLINE float ScaledFont::unicode_view_width(T const& view) const
     return longest_width;
 }
 
-RefPtr<Gfx::Bitmap> ScaledFont::rasterize_glyph(u32 glyph_id) const
+RefPtr<Gfx::Bitmap> ScaledFont::rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset subpixel_offset) const
 {
-    auto glyph_iterator = m_cached_glyph_bitmaps.find(glyph_id);
+    GlyphIndexWithSubpixelOffset index { glyph_id, subpixel_offset };
+    auto glyph_iterator = m_cached_glyph_bitmaps.find(index);
     if (glyph_iterator != m_cached_glyph_bitmaps.end())
         return glyph_iterator->value;
 
-    auto glyph_bitmap = m_font->rasterize_glyph(glyph_id, m_x_scale, m_y_scale);
-    m_cached_glyph_bitmaps.set(glyph_id, glyph_bitmap);
+    auto glyph_bitmap = m_font->rasterize_glyph(glyph_id, m_x_scale, m_y_scale, subpixel_offset);
+    m_cached_glyph_bitmaps.set(index, glyph_bitmap);
     return glyph_bitmap;
 }
 
 Gfx::Glyph ScaledFont::glyph(u32 code_point) const
+{
+    return glyph(code_point, GlyphSubpixelOffset { 0, 0 });
+}
+
+Gfx::Glyph ScaledFont::glyph(u32 code_point, GlyphSubpixelOffset subpixel_offset) const
 {
     auto id = glyph_id_for_code_point(code_point);
-    auto bitmap = rasterize_glyph(id);
+    auto bitmap = rasterize_glyph(id, subpixel_offset);
     auto metrics = glyph_metrics(id);
     return Gfx::Glyph(bitmap, metrics.left_side_bearing, metrics.advance_width, metrics.ascender);
 }
 
+float ScaledFont::glyph_left_bearing(u32 code_point) const
+{
+    auto id = glyph_id_for_code_point(code_point);
+    return glyph_metrics(id).left_side_bearing;
+}
+
 float ScaledFont::glyph_width(u32 code_point) const
 {
     auto id = glyph_id_for_code_point(code_point);

+ 23 - 2
Userland/Libraries/LibGfx/Font/ScaledFont.h

@@ -16,6 +16,13 @@
 
 namespace Gfx {
 
+struct GlyphIndexWithSubpixelOffset {
+    u32 glyph_id;
+    GlyphSubpixelOffset subpixel_offset;
+
+    bool operator==(GlyphIndexWithSubpixelOffset const&) const = default;
+};
+
 class ScaledFont : public Gfx::Font {
 public:
     ScaledFont(NonnullRefPtr<VectorFont> font, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI)
@@ -30,7 +37,7 @@ public:
     u32 glyph_id_for_code_point(u32 code_point) const { return m_font->glyph_id_for_code_point(code_point); }
     ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); }
     ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale); }
-    RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id) const;
+    RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const;
 
     // ^Gfx::Font
     virtual NonnullRefPtr<Font> clone() const override { return MUST(try_clone()); } // FIXME: clone() should not need to be implemented
@@ -42,6 +49,8 @@ public:
     virtual u8 slope() const override { return m_font->slope(); }
     virtual u16 weight() const override { return m_font->weight(); }
     virtual Gfx::Glyph glyph(u32 code_point) const override;
+    virtual float glyph_left_bearing(u32 code_point) const override;
+    virtual Glyph glyph(u32 code_point, GlyphSubpixelOffset) const override;
     virtual bool contains_glyph(u32 code_point) const override { return m_font->glyph_id_for_code_point(code_point) > 0; }
     virtual float glyph_width(u32 code_point) const override;
     virtual float glyph_or_emoji_width(u32 code_point) const override;
@@ -72,10 +81,22 @@ private:
     float m_y_scale { 0.0f };
     float m_point_width { 0.0f };
     float m_point_height { 0.0f };
-    mutable HashMap<u32, RefPtr<Gfx::Bitmap>> m_cached_glyph_bitmaps;
+    mutable HashMap<GlyphIndexWithSubpixelOffset, RefPtr<Gfx::Bitmap>> m_cached_glyph_bitmaps;
 
     template<typename T>
     float unicode_view_width(T const& view) const;
 };
 
 }
+
+namespace AK {
+
+template<>
+struct Traits<Gfx::GlyphIndexWithSubpixelOffset> : public GenericTraits<Gfx::GlyphIndexWithSubpixelOffset> {
+    static unsigned hash(Gfx::GlyphIndexWithSubpixelOffset const& index)
+    {
+        return pair_int_hash(index.glyph_id, (index.subpixel_offset.x << 8) | index.subpixel_offset.y);
+    }
+};
+
+}

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

@@ -8,6 +8,7 @@
 
 #include <AK/Noncopyable.h>
 #include <AK/RefCounted.h>
+#include <LibGfx/Font/Font.h>
 #include <LibGfx/Forward.h>
 
 namespace Gfx {
@@ -36,7 +37,7 @@ public:
     virtual ScaledFontMetrics metrics(float x_scale, float y_scale) const = 0;
     virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const = 0;
     virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const = 0;
-    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const = 0;
+    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, GlyphSubpixelOffset) const = 0;
     virtual u32 glyph_count() const = 0;
     virtual u16 units_per_em() const = 0;
     virtual u32 glyph_id_for_code_point(u32 code_point) const = 0;

+ 1 - 1
Userland/Libraries/LibGfx/Font/WOFF/Font.h

@@ -26,7 +26,7 @@ public:
     virtual Gfx::ScaledFontMetrics metrics(float x_scale, float y_scale) const override { return m_input_font->metrics(x_scale, y_scale); }
     virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->glyph_metrics(glyph_id, x_scale, y_scale); }
     virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override { return m_input_font->glyphs_horizontal_kerning(left_glyph_id, right_glyph_id, x_scale); }
-    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale); }
+    virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const override { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset); }
     virtual u32 glyph_count() const override { return m_input_font->glyph_count(); }
     virtual u16 units_per_em() const override { return m_input_font->units_per_em(); }
     virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_input_font->glyph_id_for_code_point(code_point); }