mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03: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:
parent
b4cabde4a4
commit
50d33f79fa
Notes:
sideshowbarker
2024-07-17 01:27:18 +09:00
Author: https://github.com/MacDue Commit: https://github.com/SerenityOS/serenity/commit/50d33f79fa Pull-request: https://github.com/SerenityOS/serenity/pull/21785 Reviewed-by: https://github.com/kalenikaliaksandr ✅
@ -176,7 +176,7 @@ TEST_CASE(test_character_set_masking)
|
||||
EXPECT(masked_font->glyph_index(0xFFFD).value() == 0x1FD);
|
||||
}
|
||||
|
||||
TEST_CASE(rasterize_glyph_containing_single_off_curve_point)
|
||||
TEST_CASE(resolve_glyph_path_containing_single_off_curve_point)
|
||||
{
|
||||
Vector<u8> glyph_data {
|
||||
0, 5, 0, 205, 255, 51, 7, 51, 6, 225, 0, 3, 0, 6, 0, 9, 0, 12, 0, 15, 0, 31, 64, 13, 13, 2, 15, 5, 7, 2, 8, 5, 10, 3, 0,
|
||||
@ -188,8 +188,9 @@ TEST_CASE(rasterize_glyph_containing_single_off_curve_point)
|
||||
OpenType::Glyf glyf(glyph_data.span());
|
||||
auto glyph = glyf.glyph(118);
|
||||
EXPECT(glyph.has_value());
|
||||
EXPECT_NO_CRASH("rasterizing glyph containing single off-curve point should not crash", [&] {
|
||||
(void)glyph->rasterize(0, 0, 1, 1, {}, [&](u16) -> Optional<OpenType::Glyf::Glyph> { VERIFY_NOT_REACHED(); });
|
||||
EXPECT_NO_CRASH("resolving the path of glyph containing single off-curve point should not crash", [&] {
|
||||
Gfx::Path path;
|
||||
(void)glyph->append_path(path, 0, 0, 1, 1, [&](u16) -> Optional<OpenType::Glyf::Glyph> { VERIFY_NOT_REACHED(); });
|
||||
return Test::Crash::Failure::DidNotCrash;
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 };
|
||||
|
@ -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 });
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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); }
|
||||
|
@ -42,11 +42,6 @@
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
static bool should_paint_as_space(u32 code_point)
|
||||
{
|
||||
return is_ascii_space(code_point) || code_point == 0xa0;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static Color color_for_format(BitmapFormat format, ARGB32 value)
|
||||
{
|
||||
switch (format) {
|
||||
@ -2478,33 +2473,9 @@ void Painter::draw_text_run(IntPoint baseline_start, Utf8View const& string, Fon
|
||||
|
||||
void Painter::draw_text_run(FloatPoint baseline_start, Utf8View const& string, Font const& font, Color color)
|
||||
{
|
||||
float space_width = font.glyph_width(' ') + font.glyph_spacing();
|
||||
|
||||
u32 last_code_point = 0;
|
||||
|
||||
auto point = baseline_start;
|
||||
point.translate_by(0, -font.pixel_metrics().ascent);
|
||||
|
||||
for (auto code_point_iterator = string.begin(); code_point_iterator != string.end(); ++code_point_iterator) {
|
||||
auto code_point = *code_point_iterator;
|
||||
if (should_paint_as_space(code_point)) {
|
||||
point.translate_by(space_width, 0);
|
||||
last_code_point = code_point;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point);
|
||||
if (kerning != 0.0f)
|
||||
point.translate_by(kerning, 0);
|
||||
|
||||
auto it = code_point_iterator; // The callback function will advance the iterator, so create a copy for this lookup.
|
||||
auto glyph_width = font.glyph_or_emoji_width(it) + font.glyph_spacing();
|
||||
|
||||
draw_glyph_or_emoji(point, code_point_iterator, font, color);
|
||||
|
||||
point.translate_by(glyph_width, 0);
|
||||
last_code_point = code_point;
|
||||
}
|
||||
for_each_glyph_position(baseline_start, string, font, [&](GlyphPosition glyph_position) {
|
||||
draw_glyph_or_emoji(glyph_position.position, glyph_position.it, font, color);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_scaled_bitmap_with_transform(IntRect const& dst_rect, Bitmap const& bitmap, FloatRect const& src_rect, AffineTransform const& transform, float opacity, Painter::ScalingMode scaling_mode)
|
||||
|
@ -9,8 +9,11 @@
|
||||
#include <AK/Math.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibGfx/Font/ScaledFont.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibGfx/TextLayout.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
@ -156,6 +159,25 @@ void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, float x_axis_rot
|
||||
theta_1,
|
||||
theta_delta);
|
||||
}
|
||||
|
||||
void Path::text(Utf8View text, Font const& font)
|
||||
{
|
||||
if (!is<ScaledFont>(font)) {
|
||||
// FIXME: This API only accepts Gfx::Font for ease of use.
|
||||
dbgln("Cannot path-ify bitmap fonts!");
|
||||
return;
|
||||
}
|
||||
auto& scaled_font = static_cast<ScaledFont const&>(font);
|
||||
for_each_glyph_position(
|
||||
last_point(), text, font, [&](GlyphPosition glyph_position) {
|
||||
move_to(glyph_position.position);
|
||||
// FIXME: This does not correctly handle multi-codepoint glyphs (i.e. emojis).
|
||||
auto glyph_id = scaled_font.glyph_id_for_code_point(*glyph_position.it);
|
||||
scaled_font.append_glyph_path_to(*this, glyph_id);
|
||||
},
|
||||
IncludeLeftBearing::Yes);
|
||||
}
|
||||
|
||||
FloatPoint Path::last_point()
|
||||
{
|
||||
FloatPoint last_point { 0, 0 };
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Font/Font.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Line.h>
|
||||
#include <LibGfx/Point.h>
|
||||
@ -153,6 +154,8 @@ public:
|
||||
elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep);
|
||||
}
|
||||
|
||||
void text(Utf8View, Font const&);
|
||||
|
||||
FloatPoint last_point();
|
||||
|
||||
void close();
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/DeprecatedString.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Utf32View.h>
|
||||
@ -64,4 +65,59 @@ private:
|
||||
FloatRect m_rect;
|
||||
};
|
||||
|
||||
inline bool should_paint_as_space(u32 code_point)
|
||||
{
|
||||
return is_ascii_space(code_point) || code_point == 0xa0;
|
||||
}
|
||||
|
||||
enum class IncludeLeftBearing {
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
struct GlyphPosition {
|
||||
FloatPoint position;
|
||||
float glyph_width;
|
||||
AK::Utf8CodePointIterator& it;
|
||||
};
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_glyph_position(FloatPoint start, Utf8View text, Font const& font, Callback callback, IncludeLeftBearing include_left_bearing = IncludeLeftBearing::No)
|
||||
{
|
||||
float space_width = font.glyph_width(' ') + font.glyph_spacing();
|
||||
|
||||
u32 last_code_point = 0;
|
||||
|
||||
auto point = start;
|
||||
point.translate_by(0, -font.pixel_metrics().ascent);
|
||||
|
||||
for (auto code_point_iterator = text.begin(); code_point_iterator != text.end(); ++code_point_iterator) {
|
||||
auto code_point = *code_point_iterator;
|
||||
if (should_paint_as_space(code_point)) {
|
||||
point.translate_by(space_width, 0);
|
||||
last_code_point = code_point;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point);
|
||||
if (kerning != 0.0f)
|
||||
point.translate_by(kerning, 0);
|
||||
|
||||
auto it = code_point_iterator; // The callback function will advance the iterator, so create a copy for this lookup.
|
||||
auto glyph_width = font.glyph_or_emoji_width(it) + font.glyph_spacing();
|
||||
|
||||
auto glyph_point = point;
|
||||
if (include_left_bearing == IncludeLeftBearing::Yes)
|
||||
glyph_point += FloatPoint(font.glyph_left_bearing(code_point), 0);
|
||||
|
||||
callback(GlyphPosition {
|
||||
.position = glyph_point,
|
||||
.glyph_width = glyph_width,
|
||||
.it = code_point_iterator });
|
||||
|
||||
point.translate_by(glyph_width, 0);
|
||||
last_code_point = code_point;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user