1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 22:57:44 +00:00

LibGfx: Allow extracting paths from fonts and add Gfx::Path::text()

This updates fonts so rather than rastering directly to a bitmap, you
can extract paths for glyphs. This is then used to implement a
Gfx::Path::text("some text", font) API, that if given a vector font
appends the path of the text to your Gfx::Path. This then allows
arbitrary manipulation of the text (rotation, skewing, etc), paving the
way for Word Art in Serenity.
This commit is contained in:
MacDue 2023-11-04 21:41:24 +00:00 committed by Alexander Kalenik
parent b4cabde4a4
commit 50d33f79fa
14 changed files with 186 additions and 88 deletions

View file

@ -14,6 +14,7 @@
#include <AK/Try.h>
#include <LibCore/MappedFile.h>
#include <LibCore/Resource.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/Font/OpenType/Cmap.h>
#include <LibGfx/Font/OpenType/Font.h>
#include <LibGfx/Font/OpenType/Glyf.h>
@ -708,14 +709,25 @@ float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, flo
return 0.0f;
}
RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const
Font::AscenderAndDescender Font::resolve_ascender_and_descender() const
{
if (auto bitmap = color_bitmap(glyph_id)) {
return bitmap;
}
i16 ascender = 0;
i16 descender = 0;
if (m_os2.has_value() && m_os2->use_typographic_metrics()) {
ascender = m_os2->typographic_ascender();
descender = m_os2->typographic_descender();
} else {
ascender = m_hhea.ascender();
descender = m_hhea.descender();
}
return { ascender, descender };
}
Optional<Glyf::Glyph> Font::extract_and_append_glyph_path_to(Gfx::Path& path, u32 glyph_id, i16 ascender, i16 descender, float x_scale, float y_scale) const
{
if (!m_loca.has_value() || !m_glyf.has_value()) {
return nullptr;
return {};
}
if (glyph_id >= glyph_count()) {
@ -727,30 +739,50 @@ RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_s
// If a glyph has no outline, then loca[n] = loca [n+1].
if (glyph_offset0 == glyph_offset1)
return nullptr;
return {};
auto glyph = m_glyf->glyph(glyph_offset0);
if (!glyph.has_value())
return nullptr;
return {};
i16 ascender = 0;
i16 descender = 0;
if (m_os2.has_value() && m_os2->use_typographic_metrics()) {
ascender = m_os2->typographic_ascender();
descender = m_os2->typographic_descender();
} else {
ascender = m_hhea.ascender();
descender = m_hhea.descender();
}
return glyph->rasterize(ascender, descender, x_scale, y_scale, subpixel_offset, [&](u16 glyph_id) {
bool success = glyph->append_path(path, ascender, descender, x_scale, y_scale, [&](u16 glyph_id) {
if (glyph_id >= glyph_count()) {
glyph_id = 0;
}
auto glyph_offset = m_loca->get_glyph_offset(glyph_id);
return m_glyf->glyph(glyph_offset);
});
if (success)
return glyph;
return {};
}
bool Font::append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const
{
auto ascender_and_descender = resolve_ascender_and_descender();
return extract_and_append_glyph_path_to(path, glyph_id, ascender_and_descender.ascender, ascender_and_descender.descender, x_scale, y_scale).has_value();
}
RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const
{
if (auto bitmap = color_bitmap(glyph_id))
return bitmap;
auto ascender_and_descender = resolve_ascender_and_descender();
Gfx::Path path;
path.move_to(subpixel_offset.to_float_point());
auto glyph = extract_and_append_glyph_path_to(path, glyph_id, ascender_and_descender.ascender, ascender_and_descender.descender, x_scale, y_scale);
if (!glyph.has_value())
return {};
u32 width = (u32)(ceilf((glyph->xmax() - glyph->xmin()) * x_scale)) + 2;
u32 height = (u32)(ceilf((ascender_and_descender.ascender - ascender_and_descender.descender) * y_scale)) + 2;
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors();
Gfx::Painter painter { bitmap };
Gfx::AntiAliasingPainter aa_painter(painter);
aa_painter.fill_path(path, Gfx::Color::White);
return bitmap;
}
u32 Font::glyph_count() const

View file

@ -31,6 +31,7 @@ public:
virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) 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, Gfx::GlyphSubpixelOffset) const override;
virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id, float x_scale, float y_scale) 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;
@ -47,6 +48,15 @@ public:
Optional<ReadonlyBytes> glyph_program(u32 glyph_id) const;
private:
struct AscenderAndDescender {
i16 ascender;
i16 descender;
};
AscenderAndDescender resolve_ascender_and_descender() const;
Optional<Glyf::Glyph> extract_and_append_glyph_path_to(Gfx::Path&, u32 glyph_id, i16 ascender, i16 descender, float x_scale, float y_scale) const;
RefPtr<Gfx::Bitmap> color_bitmap(u32 glyph_id) const;
struct EmbeddedBitmapWithFormat17 {

View file

@ -245,7 +245,7 @@ ReadonlyBytes Glyf::Glyph::program() const
return m_slice.slice(instructions_start + 2, num_instructions);
}
void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform const& transform) const
void Glyf::Glyph::append_path_impl(Gfx::Path& path, Gfx::AffineTransform const& transform) const
{
// Get offset for flags, x, and y.
u16 num_points = be_u16(m_slice.offset((m_num_contours - 1) * 2)) + 1;
@ -256,7 +256,6 @@ void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform con
get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset);
// Prepare to render glyph.
Gfx::Path path;
PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, transform);
u32 current_point_index = 0;
@ -297,32 +296,24 @@ void Glyf::Glyph::rasterize_impl(Gfx::Painter& painter, Gfx::AffineTransform con
}
}
}
constexpr auto base_color = Color::White;
Gfx::AntiAliasingPainter aa_painter { painter };
aa_painter.fill_path(path, base_color);
}
RefPtr<Gfx::Bitmap> Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const
bool Glyf::Glyph::append_simple_path(Gfx::Path& path, i16 font_ascender, i16 font_descender, float x_scale, float y_scale) const
{
if (m_xmin > m_xmax) [[unlikely]] {
dbgln("OpenType: Glyph has invalid xMin ({}) > xMax ({})", m_xmin, m_xmax);
return nullptr;
return false;
}
if (font_descender > font_ascender) [[unlikely]] {
dbgln("OpenType: Glyph has invalid ascender ({}) > descender ({})", font_ascender, font_descender);
return nullptr;
return false;
}
u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 2;
u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 2;
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors();
auto affine = Gfx::AffineTransform()
.translate(subpixel_offset.to_float_point())
.translate(path.last_point())
.scale(x_scale, -y_scale)
.translate(-m_xmin, -font_ascender);
Gfx::Painter painter { bitmap };
rasterize_impl(painter, affine);
return bitmap;
append_path_impl(path, affine);
return true;
}
Optional<Glyf::Glyph> Glyf::glyph(u32 offset) const

View file

@ -14,6 +14,7 @@
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibGfx/Painter.h>
#include <LibGfx/Size.h>
#include <math.h>
namespace OpenType {
@ -71,17 +72,22 @@ public:
m_type = Type::Simple;
}
}
template<typename GlyphCb>
RefPtr<Gfx::Bitmap> rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset, GlyphCb glyph_callback) const
bool append_path(Gfx::Path& path, i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
{
switch (m_type) {
case Type::Simple:
return rasterize_simple(font_ascender, font_descender, x_scale, y_scale, subpixel_offset);
return append_simple_path(path, font_ascender, font_descender, x_scale, y_scale);
case Type::Composite:
return rasterize_composite(font_ascender, font_descender, x_scale, y_scale, subpixel_offset, glyph_callback);
return append_composite_path(path, font_ascender, x_scale, y_scale, glyph_callback);
}
VERIFY_NOT_REACHED();
}
i16 xmax() const { return m_xmax; }
i16 xmin() const { return m_xmin; }
int ascender() const { return m_ymax; }
int descender() const { return m_ymin; }
@ -112,11 +118,11 @@ public:
u32 m_offset { 0 };
};
void rasterize_impl(Gfx::Painter&, Gfx::AffineTransform const&) const;
RefPtr<Gfx::Bitmap> rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const;
void append_path_impl(Gfx::Path&, Gfx::AffineTransform const&) const;
bool append_simple_path(Gfx::Path&, i16 ascender, i16 descender, float x_scale, float y_scale) const;
template<typename GlyphCb>
void rasterize_composite_loop(Gfx::Painter& painter, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const
void resolve_composite_path_loop(Gfx::Path& path, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const
{
ComponentIterator component_iterator(m_slice);
@ -133,28 +139,22 @@ public:
continue;
if (glyph->m_type == Type::Simple) {
glyph->rasterize_impl(painter, affine_here);
glyph->append_path_impl(path, affine_here);
} else {
glyph->rasterize_composite_loop(painter, transform, glyph_callback);
glyph->resolve_composite_path_loop(path, transform, glyph_callback);
}
}
}
template<typename GlyphCb>
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
bool append_composite_path(Gfx::Path& path, i16 font_ascender, float x_scale, float y_scale, 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;
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors();
auto affine = Gfx::AffineTransform()
.translate(subpixel_offset.to_float_point())
.translate(path.last_point())
.scale(x_scale, -y_scale)
.translate(-m_xmin, -font_ascender);
Gfx::Painter painter { bitmap };
rasterize_composite_loop(painter, affine, glyph_callback);
return bitmap;
resolve_composite_path_loop(path, affine, glyph_callback);
return true;
}
Type m_type { Type::Composite };

View file

@ -83,6 +83,11 @@ RefPtr<Gfx::Bitmap> ScaledFont::rasterize_glyph(u32 glyph_id, GlyphSubpixelOffse
return glyph_bitmap;
}
bool ScaledFont::append_glyph_path_to(Gfx::Path& path, u32 glyph_id) const
{
return m_font->append_glyph_path_to(path, glyph_id, m_x_scale, m_y_scale);
}
Gfx::Glyph ScaledFont::glyph(u32 code_point) const
{
return glyph(code_point, GlyphSubpixelOffset { 0, 0 });

View file

@ -31,6 +31,7 @@ public:
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, m_point_width, m_point_height); }
RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const;
bool append_glyph_path_to(Gfx::Path&, u32 glyph_id) const;
// ^Gfx::Font
virtual NonnullRefPtr<Font> clone() const override { return MUST(try_clone()); } // FIXME: clone() should not need to be implemented

View file

@ -10,6 +10,7 @@
#include <AK/RefCounted.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Path.h>
namespace Gfx {
@ -39,6 +40,8 @@ public:
virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) 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, GlyphSubpixelOffset) const = 0;
virtual bool append_glyph_path_to(Gfx::Path&, u32 glyph_id, float x_scale, float y_scale) 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

@ -28,6 +28,8 @@ public:
virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override { return m_input_font->glyph_metrics(glyph_id, x_scale, y_scale, point_width, point_height); }
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, Gfx::GlyphSubpixelOffset subpixel_offset) const override { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset); }
virtual bool append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->append_glyph_path_to(path, glyph_id, x_scale, y_scale); }
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); }

View file

@ -32,6 +32,7 @@ public:
{
return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset);
}
virtual bool append_glyph_path_to(Gfx::Path& path, u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->append_glyph_path_to(path, glyph_id, x_scale, y_scale); }
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); }