From ada48a1daf24203b90730f178f4466d2393702c5 Mon Sep 17 00:00:00 2001 From: MacDue Date: Mon, 2 Jan 2023 20:10:00 +0100 Subject: [PATCH] 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. --- Userland/Libraries/LibGfx/CMakeLists.txt | 1 + Userland/Libraries/LibGfx/Font/BitmapFont.h | 4 +++ Userland/Libraries/LibGfx/Font/Font.cpp | 29 +++++++++++++++++++ Userland/Libraries/LibGfx/Font/Font.h | 25 ++++++++++++++++ .../Libraries/LibGfx/Font/OpenType/Font.cpp | 4 +-- .../Libraries/LibGfx/Font/OpenType/Font.h | 2 +- .../Libraries/LibGfx/Font/OpenType/Glyf.cpp | 3 +- .../Libraries/LibGfx/Font/OpenType/Glyf.h | 12 ++++---- .../Libraries/LibGfx/Font/PathRasterizer.cpp | 5 ++-- .../Libraries/LibGfx/Font/PathRasterizer.h | 5 ++++ Userland/Libraries/LibGfx/Font/ScaledFont.cpp | 22 ++++++++++---- Userland/Libraries/LibGfx/Font/ScaledFont.h | 25 ++++++++++++++-- Userland/Libraries/LibGfx/Font/VectorFont.h | 3 +- Userland/Libraries/LibGfx/Font/WOFF/Font.h | 2 +- 14 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 Userland/Libraries/LibGfx/Font/Font.cpp diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 6127cf09b8..f51e7e9ca1 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/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 diff --git a/Userland/Libraries/LibGfx/Font/BitmapFont.h b/Userland/Libraries/LibGfx/Font/BitmapFont.h index 9a36fe8472..0d51de96f3 100644 --- a/Userland/Libraries/LibGfx/Font/BitmapFont.h +++ b/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; } diff --git a/Userland/Libraries/LibGfx/Font/Font.cpp b/Userland/Libraries/LibGfx/Font/Font.cpp new file mode 100644 index 0000000000..97748996d2 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/Font.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +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((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 } }; +} + +} diff --git a/Userland/Libraries/LibGfx/Font/Font.h b/Userland/Libraries/LibGfx/Font/Font.h index 38fffa31ce..e4be218719 100644 --- a/Userland/Libraries/LibGfx/Font/Font.h +++ b/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; diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp index bc0b7fc1af..7535d735bc 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp +++ b/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 Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const +RefPtr 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; } diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.h b/Userland/Libraries/LibGfx/Font/OpenType/Font.h index 28c15229e6..e7d3643ad5 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.h +++ b/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 rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const override; + virtual RefPtr 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); } diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp index 3e8e8e7197..d7c47a2a86 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.cpp +++ b/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 Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale) const +RefPtr 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(); diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h index b487534cc8..aa519377f7 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Glyf.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -55,13 +56,13 @@ public: } } template - RefPtr rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const + RefPtr 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 rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale) const; + RefPtr rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const; template void rasterize_composite_loop(Gfx::PathRasterizer& rasterizer, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const @@ -120,11 +121,12 @@ public: } template - RefPtr rasterize_composite(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const + RefPtr 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); diff --git a/Userland/Libraries/LibGfx/Font/PathRasterizer.cpp b/Userland/Libraries/LibGfx/Font/PathRasterizer.cpp index dbf0d4b0e0..094adff4a8 100644 --- a/Userland/Libraries/LibGfx/Font/PathRasterizer.cpp +++ b/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 PathRasterizer::accumulate() diff --git a/Userland/Libraries/LibGfx/Font/PathRasterizer.h b/Userland/Libraries/LibGfx/Font/PathRasterizer.h index 90020da3ef..a0d09daa66 100644 --- a/Userland/Libraries/LibGfx/Font/PathRasterizer.h +++ b/Userland/Libraries/LibGfx/Font/PathRasterizer.h @@ -18,10 +18,15 @@ public: void draw_path(Gfx::Path&); RefPtr 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 m_data; }; diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.cpp b/Userland/Libraries/LibGfx/Font/ScaledFont.cpp index aac9357173..9f2c5700d6 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.cpp +++ b/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 ScaledFont::rasterize_glyph(u32 glyph_id) const +RefPtr 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); diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.h b/Userland/Libraries/LibGfx/Font/ScaledFont.h index 15d3fa7d40..e346c7b581 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.h +++ b/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 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 rasterize_glyph(u32 glyph_id) const; + RefPtr rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const; // ^Gfx::Font virtual NonnullRefPtr 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> m_cached_glyph_bitmaps; + mutable HashMap> m_cached_glyph_bitmaps; template float unicode_view_width(T const& view) const; }; } + +namespace AK { + +template<> +struct Traits : public GenericTraits { + static unsigned hash(Gfx::GlyphIndexWithSubpixelOffset const& index) + { + return pair_int_hash(index.glyph_id, (index.subpixel_offset.x << 8) | index.subpixel_offset.y); + } +}; + +} diff --git a/Userland/Libraries/LibGfx/Font/VectorFont.h b/Userland/Libraries/LibGfx/Font/VectorFont.h index ddcef366a2..2a47fb3406 100644 --- a/Userland/Libraries/LibGfx/Font/VectorFont.h +++ b/Userland/Libraries/LibGfx/Font/VectorFont.h @@ -8,6 +8,7 @@ #include #include +#include #include 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 rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const = 0; + virtual RefPtr 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; diff --git a/Userland/Libraries/LibGfx/Font/WOFF/Font.h b/Userland/Libraries/LibGfx/Font/WOFF/Font.h index 621ebd4955..60e8d8c183 100644 --- a/Userland/Libraries/LibGfx/Font/WOFF/Font.h +++ b/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 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 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); }