mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 06:27:45 +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:
parent
a1726b1ba5
commit
ada48a1daf
14 changed files with 121 additions and 21 deletions
|
@ -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
|
||||
|
|
|
@ -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
Userland/Libraries/LibGfx/Font/Font.cpp
Normal file
29
Userland/Libraries/LibGfx/Font/Font.cpp
Normal 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 } };
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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); }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue