mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:42:43 +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
	
	 MacDue
						MacDue