1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 04:47:35 +00:00

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.
This commit is contained in:
MacDue 2023-01-02 20:10:00 +01:00 committed by Andreas Kling
parent a1726b1ba5
commit ada48a1daf
14 changed files with 121 additions and 21 deletions

View file

@ -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

View file

@ -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; }

View file

@ -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 } };
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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); }

View file

@ -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();

View file

@ -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);

View file

@ -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()

View file

@ -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;
};

View file

@ -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);

View file

@ -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);
}
};
}

View file

@ -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;

View file

@ -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); }