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

LibTTF: Initial work on parsing and rasterizing composite glyphs.

This doesn't handle every case yet.
This commit is contained in:
Srimanta Barua 2020-06-14 11:19:28 +05:30 committed by Andreas Kling
parent bd354bc2ae
commit 1e1d2cdedf
4 changed files with 328 additions and 220 deletions

View file

@ -38,6 +38,15 @@ public:
: m_values { 1, 0, 0, 1, 0, 0 } : m_values { 1, 0, 0, 1, 0, 0 }
{ {
} }
AffineTransform(float a, float b, float c, float d, float e, float f)
{
m_values[0] = a;
m_values[1] = b;
m_values[2] = c;
m_values[3] = d;
m_values[4] = e;
m_values[5] = f;
}
AffineTransform(float a, float b, float c, float d, float e, float f) AffineTransform(float a, float b, float c, float d, float e, float f)
: m_values { a, b, c, d, e, f } : m_values { a, b, c, d, e, f }
@ -72,6 +81,8 @@ public:
AffineTransform& rotate_radians(float); AffineTransform& rotate_radians(float);
AffineTransform& multiply(const AffineTransform&); AffineTransform& multiply(const AffineTransform&);
AffineTransform operator*(const AffineTransform&) const;
private: private:
float m_values[6] { 0 }; float m_values[6] { 0 };
}; };

View file

@ -49,6 +49,11 @@ i16 be_i16(const u8* ptr)
return (((i16) ptr[0]) << 8) | ((i16) ptr[1]); return (((i16) ptr[0]) << 8) | ((i16) ptr[1]);
} }
float be_fword(const u8* ptr)
{
return (float) be_i16(ptr) / (float) (1 << 14);
}
u32 tag_from_str(const char *str) u32 tag_from_str(const char *str)
{ {
return be_u32((const u8*) str); return be_u32((const u8*) str);
@ -122,6 +127,27 @@ u16 Font::Hhea::number_of_h_metrics() const
return be_u16(m_slice.offset_pointer((u32) Offsets::NumberOfHMetrics)); return be_u16(m_slice.offset_pointer((u32) Offsets::NumberOfHMetrics));
} }
Font::GlyphHorizontalMetrics Font::Hmtx::get_glyph_horizontal_metrics(u32 glyph_id) const
{
ASSERT(glyph_id < m_num_glyphs);
if (glyph_id < m_number_of_h_metrics) {
auto offset = glyph_id * (u32) Sizes::LongHorMetric;
u16 advance_width = be_u16(m_slice.offset_pointer(offset));
i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset + 2));
return GlyphHorizontalMetrics {
.advance_width = advance_width,
.left_side_bearing = left_side_bearing,
};
}
auto offset = m_number_of_h_metrics * (u32) Sizes::LongHorMetric + (glyph_id - m_number_of_h_metrics) * (u32) Sizes::LeftSideBearing;
u16 advance_width = be_u16(m_slice.offset_pointer((m_number_of_h_metrics - 1) * (u32) Sizes::LongHorMetric));
i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset));
return GlyphHorizontalMetrics {
.advance_width = advance_width,
.left_side_bearing = left_side_bearing,
};
}
u16 Font::Maxp::num_glyphs() const u16 Font::Maxp::num_glyphs() const
{ {
return be_u16(m_slice.offset_pointer((u32) Offsets::NumGlyphs)); return be_u16(m_slice.offset_pointer((u32) Offsets::NumGlyphs));
@ -287,7 +313,13 @@ RefPtr<Gfx::Bitmap> Font::raster_glyph(u32 glyph_id, float x_scale, float y_scal
} }
auto glyph_offset = m_loca.get_glyph_offset(glyph_id); auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
auto glyph = m_glyf.glyph(glyph_offset); auto glyph = m_glyf.glyph(glyph_offset);
return glyph.raster(x_scale, y_scale); return glyph.raster(x_scale, y_scale, [&](u16 glyph_id) {
if (glyph_id >= m_maxp.num_glyphs()) {
glyph_id = 0;
}
auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
return m_glyf.glyph(glyph_offset);
});
} }
int ScaledFont::width(const StringView& string) const int ScaledFont::width(const StringView& string) const

View file

@ -27,12 +27,15 @@
#pragma once #pragma once
#include <AK/ByteBuffer.h> #include <AK/ByteBuffer.h>
#include <AK/FixedArray.h>
#include <AK/Noncopyable.h> #include <AK/Noncopyable.h>
#include <AK/Optional.h> #include <AK/Optional.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h> #include <LibGfx/Size.h>
#include <math.h>
#define POINTS_PER_INCH 72.0f #define POINTS_PER_INCH 72.0f
#define DEFAULT_DPI 96 #define DEFAULT_DPI 96
@ -64,7 +67,7 @@ class Font : public RefCounted<Font> {
AK_MAKE_NONCOPYABLE(Font); AK_MAKE_NONCOPYABLE(Font);
public: public:
static RefPtr<Font> load_from_file(const StringView& path, unsigned index); static RefPtr<Font> load_from_file(const StringView& path, unsigned index = 0);
private: private:
enum class Offsets { enum class Offsets {
@ -84,6 +87,19 @@ private:
RefPtr<Gfx::Bitmap> raster_glyph(u32 glyph_id, float x_scale, float y_scale) const; RefPtr<Gfx::Bitmap> raster_glyph(u32 glyph_id, float x_scale, float y_scale) const;
u32 glyph_count() const { return m_maxp.num_glyphs(); } u32 glyph_count() const { return m_maxp.num_glyphs(); }
class Rasterizer {
public:
Rasterizer(Gfx::IntSize);
void draw_path(Gfx::Path&);
RefPtr<Gfx::Bitmap> accumulate();
private:
void draw_line(Gfx::FloatPoint, Gfx::FloatPoint);
Gfx::IntSize m_size;
FixedArray<float> m_data;
};
enum class IndexToLocFormat { enum class IndexToLocFormat {
Offset16, Offset16,
Offset32, Offset32,
@ -327,54 +343,87 @@ private:
public: public:
class Glyph { class Glyph {
public: public:
static Glyph simple(const ByteBuffer& slice, u16 num_contours, i16 xmin, i16 ymin, i16 xmax, i16 ymax); Glyph(const ByteBuffer& slice, i16 xmin, i16 ymin, i16 xmax, i16 ymax, i16 num_contours = -1)
static Glyph composite(const ByteBuffer& slice); // FIXME: This is currently just a dummy. Need to add support for composite glyphs. : m_xmin(xmin)
RefPtr<Gfx::Bitmap> raster(float x_scale, float y_scale) const; , m_ymin(ymin)
int ascender() const , m_xmax(xmax)
, m_ymax(ymax)
, m_num_contours(num_contours)
, m_slice(move(slice))
{ {
if (m_type == Type::Simple) { if (m_num_contours >= 0) {
return m_meta.simple.ymax; m_type = Type::Simple;
} }
// FIXME: Support composite outlines.
TODO();
} }
int descender() const template <typename GlyphCb>
RefPtr<Gfx::Bitmap> raster(float x_scale, float y_scale, GlyphCb glyph_callback) const
{ {
if (m_type == Type::Simple) { switch (m_type) {
return m_meta.simple.ymin; case Type::Simple:
return raster_simple(x_scale, y_scale);
case Type::Composite:
return raster_composite(x_scale, y_scale, glyph_callback);
} }
// FIXME: Support composite outlines. ASSERT_NOT_REACHED();
TODO();
} }
int ascender() const { return m_ymax; }
int descender() const { return m_ymin; }
private: private:
enum class Type { enum class Type {
Simple, Simple,
Composite, Composite,
}; };
struct Simple {
u16 num_contours; class ComponentIterator {
i16 xmin; public:
i16 ymin; struct Item {
i16 xmax; u16 glyph_id;
i16 ymax; Gfx::AffineTransform affine;
}; };
struct Composite {
ComponentIterator(const ByteBuffer& slice)
: m_slice(slice)
{
}
Optional<Item> next();
private:
ByteBuffer m_slice;
bool m_has_more { true };
u32 m_offset { 0 };
}; };
Glyph(const ByteBuffer& slice, Type type) void raster_inner(Rasterizer&, Gfx::AffineTransform&) const;
: m_type(type)
, m_slice(move(slice))
{
}
RefPtr<Gfx::Bitmap> raster_simple(float x_scale, float y_scale) const; RefPtr<Gfx::Bitmap> raster_simple(float x_scale, float y_scale) const;
template <typename GlyphCb>
RefPtr<Gfx::Bitmap> raster_composite(float x_scale, float y_scale, GlyphCb glyph_callback) const
{
u32 width = (u32) (ceil((m_xmax - m_xmin) * x_scale)) + 1;
u32 height = (u32) (ceil((m_ymax - m_ymin) * y_scale)) + 1;
Rasterizer rasterizer(Gfx::IntSize(width, height));
auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -m_ymax);
ComponentIterator component_iterator(m_slice);
while (true) {
auto opt_item = component_iterator.next();
if (!opt_item.has_value()) {
break;
}
auto item = opt_item.value();
auto affine_here = affine * item.affine;
auto glyph = glyph_callback(item.glyph_id);
glyph.raster_inner(rasterizer, affine_here);
}
return rasterizer.accumulate();
}
Type m_type; Type m_type { Type::Composite };
i16 m_xmin;
i16 m_ymin;
i16 m_xmax;
i16 m_ymax;
i16 m_num_contours { -1 };
ByteBuffer m_slice; ByteBuffer m_slice;
union {
Simple simple;
Composite composite;
} m_meta;
}; };
Glyf() {} Glyf() {}

View file

@ -25,16 +25,15 @@
*/ */
#include "Font.h" #include "Font.h"
#include <AK/FixedArray.h>
#include <LibGfx/FloatPoint.h> #include <LibGfx/FloatPoint.h>
#include <LibGfx/Path.h> #include <LibGfx/Path.h>
#include <math.h>
namespace TTF { namespace TTF {
extern u16 be_u16(const u8* ptr); extern u16 be_u16(const u8* ptr);
extern u32 be_u32(const u8* ptr); extern u32 be_u32(const u8* ptr);
extern i16 be_i16(const u8* ptr); extern i16 be_i16(const u8* ptr);
extern float be_fword(const u8* ptr);
enum class SimpleGlyfFlags { enum class SimpleGlyfFlags {
// From spec. // From spec.
@ -55,6 +54,21 @@ enum class SimpleGlyfFlags {
YPositiveShortVector = 0x24, YPositiveShortVector = 0x24,
}; };
enum class CompositeGlyfFlags {
Arg1AndArg2AreWords = 0x0001,
ArgsAreXYValues = 0x0002,
RoundXYToGrid = 0x0004,
WeHaveAScale = 0x0008,
MoreComponents = 0x0020,
WeHaveAnXAndYScale = 0x0040,
WeHaveATwoByTwo = 0x0080,
WeHaveInstructions = 0x0100,
UseMyMetrics = 0x0200,
OverlapCompound = 0x0400, // Not relevant - can overlap without this set
ScaledComponentOffset = 0x0800,
UnscaledComponentOffset = 0x1000,
};
class PointIterator { class PointIterator {
public: public:
struct Item { struct Item {
@ -62,16 +76,13 @@ public:
Gfx::FloatPoint point; Gfx::FloatPoint point;
}; };
PointIterator(const ByteBuffer& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, float x_translate, float y_translate, float x_scale, float y_scale) PointIterator(const ByteBuffer& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, Gfx::AffineTransform affine)
: m_slice(slice) : m_slice(slice)
, m_points_remaining(num_points) , m_points_remaining(num_points)
, m_flags_offset(flags_offset) , m_flags_offset(flags_offset)
, m_x_offset(x_offset) , m_x_offset(x_offset)
, m_y_offset(y_offset) , m_y_offset(y_offset)
, m_x_translate(x_translate) , m_affine(affine)
, m_y_translate(y_translate)
, m_x_scale(x_scale)
, m_y_scale(y_scale)
{ {
} }
@ -119,11 +130,8 @@ public:
m_points_remaining--; m_points_remaining--;
Item ret = { Item ret = {
.on_curve = (m_flag & (u8) SimpleGlyfFlags::OnCurve) != 0, .on_curve = (m_flag & (u8) SimpleGlyfFlags::OnCurve) != 0,
.point = m_last_point, .point = m_affine.map(m_last_point),
}; };
ret.point.move_by(m_x_translate, m_y_translate);
ret.point.set_x(ret.point.x() * m_x_scale);
ret.point.set_y(ret.point.y() * m_y_scale);
return ret; return ret;
} }
@ -136,144 +144,181 @@ private:
u32 m_flags_offset; u32 m_flags_offset;
u32 m_x_offset; u32 m_x_offset;
u32 m_y_offset; u32 m_y_offset;
float m_x_translate; Gfx::AffineTransform m_affine;
float m_y_translate;
float m_x_scale;
float m_y_scale;
}; };
class Rasterizer { Optional<Font::Glyf::Glyph::ComponentIterator::Item> Font::Glyf::Glyph::ComponentIterator::next() {
public: if (!m_has_more) {
Rasterizer(Gfx::Size size) return {};
: m_size(size)
, m_data(m_size.width() * m_size.height())
{
for (int i = 0; i < m_size.width() * m_size.height(); i++) {
m_data[i] = 0.0;
}
} }
u16 flags = be_u16(m_slice.offset_pointer(m_offset));
RefPtr<Gfx::Bitmap> draw_path(Gfx::Path& path) m_offset += 2;
{ u16 glyph_id = be_u16(m_slice.offset_pointer(m_offset));
for (auto& line : path.split_lines()) { m_offset += 2;
draw_line(line.from, line.to); i16 arg1 = 0, arg2 = 0;
} if (flags & (u16) CompositeGlyfFlags::Arg1AndArg2AreWords) {
return accumulate(); arg1 = be_i16(m_slice.offset_pointer(m_offset));
m_offset += 2;
arg2 = be_i16(m_slice.offset_pointer(m_offset));
m_offset += 2;
} else {
arg1 = (i8) m_slice[m_offset++];
arg2 = (i8) m_slice[m_offset++];
} }
float a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0;
private: if (flags & (u16) CompositeGlyfFlags::WeHaveATwoByTwo) {
RefPtr<Gfx::Bitmap> accumulate() a = be_fword(m_slice.offset_pointer(m_offset));
{ m_offset += 2;
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, m_size); b = be_fword(m_slice.offset_pointer(m_offset));
Color base_color = Color::from_rgb(0xffffff); m_offset += 2;
for (int y = 0; y < m_size.height(); y++) { c = be_fword(m_slice.offset_pointer(m_offset));
float accumulator = 0.0; m_offset += 2;
for (int x = 0; x < m_size.width(); x++) { d = be_fword(m_slice.offset_pointer(m_offset));
accumulator += m_data[y * m_size.width() + x]; m_offset += 2;
float value = accumulator; } else if (flags & (u16) CompositeGlyfFlags::WeHaveAnXAndYScale) {
if (value < 0.0) { a = be_fword(m_slice.offset_pointer(m_offset));
value = -value; m_offset += 2;
} d = be_fword(m_slice.offset_pointer(m_offset));
if (value > 1.0) { m_offset += 2;
value = 1.0; } else if (flags & (u16) CompositeGlyfFlags::WeHaveAScale) {
} a = be_fword(m_slice.offset_pointer(m_offset));
u8 alpha = value * 255.0; m_offset += 2;
bitmap->set_pixel(x, y, base_color.with_alpha(alpha)); d = a;
}
}
return bitmap;
} }
// FIXME: Handle UseMyMetrics, ScaledComponentOffset, UnscaledComponentOffset, non-ArgsAreXYValues
void draw_line(Gfx::FloatPoint p0, Gfx::FloatPoint p1) if (flags & (u16) CompositeGlyfFlags::ArgsAreXYValues) {
{ e = arg1;
ASSERT(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height()); f = arg2;
ASSERT(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height()); } else {
// If we're on the same Y, there's no need to draw TODO();
if (p0.y() == p1.y()) {
return;
}
float direction = -1.0;
if (p1.y() < p0.y()) {
direction = 1.0;
auto tmp = p0;
p0 = p1;
p1 = tmp;
}
float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y());
u32 y0 = floor(p0.y());
u32 y1 = ceil(p1.y());
float x_cur = p0.x();
for (u32 y = y0; y < y1; y++) {
u32 line_offset = m_size.width() * y;
float dy = min(y + 1.0f, p1.y()) - max((float) y, p0.y());
float directed_dy = dy * direction;
float x_next = x_cur + dy * dxdy;
if (x_next < 0.0) {
x_next = 0.0;
}
float x0 = x_cur;
float x1 = x_next;
if (x1 < x0) {
x1 = x_cur;
x0 = x_next;
}
float x0_floor = floor(x0);
float x1_ceil = ceil(x1);
u32 x0i = x0_floor;
if (x1_ceil <= x0_floor + 1.0) {
// If x0 and x1 are within the same pixel, then area to the right is (1 - (mid(x0, x1) - x0_floor)) * dy
float area = ((x0 + x1) * 0.5) - x0_floor;
m_data[line_offset + x0i] += directed_dy * (1.0 - area);
m_data[line_offset + x0i + 1] += directed_dy * area;
} else {
float dydx = 1.0 / dxdy;
float x0_right = 1.0 - (x0 - x0_floor);
u32 x1_floor_i = floor(x1);
float area_upto_here = 0.5 * x0_right * x0_right * dydx;
m_data[line_offset + x0i] += direction * area_upto_here;
for (u32 x = x0i + 1; x < x1_floor_i; x++) {
x0_right += 1.0;
float total_area_here = 0.5 * x0_right * x0_right * dydx;
m_data[line_offset + x] += direction * (total_area_here - area_upto_here);
area_upto_here = total_area_here;
}
m_data[line_offset + x1_floor_i] += direction * (dy - area_upto_here);
}
x_cur = x_next;
}
} }
if (flags & (u16) CompositeGlyfFlags::UseMyMetrics) {
Gfx::Size m_size; TODO();
FixedArray<float> m_data;
};
Font::GlyphHorizontalMetrics Font::Hmtx::get_glyph_horizontal_metrics(u32 glyph_id) const
{
ASSERT(glyph_id < m_num_glyphs);
if (glyph_id < m_number_of_h_metrics) {
auto offset = glyph_id * (u32) Sizes::LongHorMetric;
u16 advance_width = be_u16(m_slice.offset_pointer(offset));
i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset + 2));
return GlyphHorizontalMetrics {
.advance_width = advance_width,
.left_side_bearing = left_side_bearing,
};
} }
auto offset = m_number_of_h_metrics * (u32) Sizes::LongHorMetric + (glyph_id - m_number_of_h_metrics) * (u32) Sizes::LeftSideBearing; if (flags & (u16) CompositeGlyfFlags::ScaledComponentOffset) {
u16 advance_width = be_u16(m_slice.offset_pointer((m_number_of_h_metrics - 1) * (u32) Sizes::LongHorMetric)); TODO();
i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset)); }
return GlyphHorizontalMetrics { if (flags & (u16) CompositeGlyfFlags::UnscaledComponentOffset) {
.advance_width = advance_width, TODO();
.left_side_bearing = left_side_bearing, }
m_has_more = (flags & (u16) CompositeGlyfFlags::MoreComponents);
return Item {
.glyph_id = glyph_id,
.affine = Gfx::AffineTransform(a, b, c, d, e, f),
}; };
} }
Font::Rasterizer::Rasterizer(Gfx::IntSize size)
: m_size(size)
, m_data(m_size.width() * m_size.height())
{
for (int i = 0; i < m_size.width() * m_size.height(); i++) {
m_data[i] = 0.0;
}
}
void Font::Rasterizer::draw_path(Gfx::Path& path)
{
for (auto& line : path.split_lines()) {
draw_line(line.from, line.to);
}
}
RefPtr<Gfx::Bitmap> Font::Rasterizer::accumulate()
{
auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, m_size);
Color base_color = Color::from_rgb(0xffffff);
for (int y = 0; y < m_size.height(); y++) {
float accumulator = 0.0;
for (int x = 0; x < m_size.width(); x++) {
accumulator += m_data[y * m_size.width() + x];
float value = accumulator;
if (value < 0.0) {
value = -value;
}
if (value > 1.0) {
value = 1.0;
}
u8 alpha = value * 255.0;
bitmap->set_pixel(x, y, base_color.with_alpha(alpha));
}
}
return bitmap;
}
void Font::Rasterizer::draw_line(Gfx::FloatPoint p0, Gfx::FloatPoint p1)
{
// FIXME: Shift x and y according to dy/dx
if (p0.x() < 0.0) { p0.set_x(roundf(p0.x())); }
if (p0.y() < 0.0) { p0.set_y(roundf(p0.y())); }
if (p1.x() < 0.0) { p1.set_x(roundf(p1.x())); }
if (p1.y() < 0.0) { p1.set_y(roundf(p1.y())); }
dbg() << "m_size: " << m_size << " | p0: (" << p0.x() << ", " << p0.y() << ") | p1: (" << p1.x() << ", " << p1.y() << ")";
ASSERT(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height());
ASSERT(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height());
// If we're on the same Y, there's no need to draw
if (p0.y() == p1.y()) {
return;
}
float direction = -1.0;
if (p1.y() < p0.y()) {
direction = 1.0;
auto tmp = p0;
p0 = p1;
p1 = tmp;
}
float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y());
u32 y0 = floor(p0.y());
u32 y1 = ceil(p1.y());
float x_cur = p0.x();
for (u32 y = y0; y < y1; y++) {
u32 line_offset = m_size.width() * y;
float dy = min(y + 1.0f, p1.y()) - max((float) y, p0.y());
float directed_dy = dy * direction;
float x_next = x_cur + dy * dxdy;
if (x_next < 0.0) {
x_next = 0.0;
}
float x0 = x_cur;
float x1 = x_next;
if (x1 < x0) {
x1 = x_cur;
x0 = x_next;
}
float x0_floor = floor(x0);
float x1_ceil = ceil(x1);
u32 x0i = x0_floor;
if (x1_ceil <= x0_floor + 1.0) {
// If x0 and x1 are within the same pixel, then area to the right is (1 - (mid(x0, x1) - x0_floor)) * dy
float area = ((x0 + x1) * 0.5) - x0_floor;
m_data[line_offset + x0i] += directed_dy * (1.0 - area);
m_data[line_offset + x0i + 1] += directed_dy * area;
} else {
float dydx = 1.0 / dxdy;
float x0_right = 1.0 - (x0 - x0_floor);
u32 x1_floor_i = floor(x1);
float area_upto_here = 0.5 * x0_right * x0_right * dydx;
m_data[line_offset + x0i] += direction * area_upto_here;
for (u32 x = x0i + 1; x < x1_floor_i; x++) {
x0_right += 1.0;
float total_area_here = 0.5 * x0_right * x0_right * dydx;
m_data[line_offset + x] += direction * (total_area_here - area_upto_here);
area_upto_here = total_area_here;
}
m_data[line_offset + x1_floor_i] += direction * (dy - area_upto_here);
}
x_cur = x_next;
}
}
u32 Font::Loca::get_glyph_offset(u32 glyph_id) const u32 Font::Loca::get_glyph_offset(u32 glyph_id) const
{ {
ASSERT(glyph_id < m_num_glyphs); ASSERT(glyph_id < m_num_glyphs);
@ -287,39 +332,6 @@ u32 Font::Loca::get_glyph_offset(u32 glyph_id) const
} }
} }
Font::Glyf::Glyph Font::Glyf::Glyph::simple(const ByteBuffer& slice, u16 num_contours, i16 xmin, i16 ymin, i16 xmax, i16 ymax)
{
auto ret = Glyph(slice, Type::Simple);
ret.m_meta.simple = Simple {
.num_contours = num_contours,
.xmin = xmin,
.ymin = ymin,
.xmax = xmax,
.ymax = ymax,
};
return ret;
}
// FIXME: This is currently just a dummy. Need to add support for composite glyphs.
Font::Glyf::Glyph Font::Glyf::Glyph::composite(const ByteBuffer& slice)
{
auto ret = Glyph(slice, Type::Composite);
ret.m_meta.composite = Composite();
return ret;
}
RefPtr<Gfx::Bitmap> Font::Glyf::Glyph::raster(float x_scale, float y_scale) const
{
switch (m_type) {
case Type::Simple:
return raster_simple(x_scale, y_scale);
case Type::Composite:
// FIXME: Add support for composite glyphs
TODO();
}
ASSERT_NOT_REACHED();
}
static void get_ttglyph_offsets(const ByteBuffer& slice, u32 num_points, u32 flags_offset, u32 *x_offset, u32 *y_offset) static void get_ttglyph_offsets(const ByteBuffer& slice, u32 num_points, u32 flags_offset, u32 *x_offset, u32 *y_offset)
{ {
u32 flags_size = 0; u32 flags_size = 0;
@ -351,25 +363,22 @@ static void get_ttglyph_offsets(const ByteBuffer& slice, u32 num_points, u32 fla
*y_offset = *x_offset + x_size; *y_offset = *x_offset + x_size;
} }
RefPtr<Gfx::Bitmap> Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const void Font::Glyf::Glyph::raster_inner(Rasterizer& rasterizer, Gfx::AffineTransform& affine) const
{ {
auto simple = m_meta.simple;
// Get offets for flags, x, and y. // Get offets for flags, x, and y.
u16 num_points = be_u16(m_slice.offset_pointer((simple.num_contours - 1) * 2)) + 1; u16 num_points = be_u16(m_slice.offset_pointer((m_num_contours - 1) * 2)) + 1;
u16 num_instructions = be_u16(m_slice.offset_pointer(simple.num_contours * 2)); u16 num_instructions = be_u16(m_slice.offset_pointer(m_num_contours * 2));
u32 flags_offset = simple.num_contours * 2 + 2 + num_instructions; u32 flags_offset = m_num_contours * 2 + 2 + num_instructions;
u32 x_offset = 0; u32 x_offset = 0;
u32 y_offset = 0; u32 y_offset = 0;
get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset); get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset);
// Prepare to render glyph. // Prepare to render glyph.
u32 width = (u32) (ceil((simple.xmax - simple.xmin) * x_scale)) + 1;
u32 height = (u32) (ceil((simple.ymax - simple.ymin) * y_scale)) + 1;
Gfx::Path path; Gfx::Path path;
PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, -simple.xmin, -simple.ymax, x_scale, -y_scale); PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, affine);
int last_contour_end = -1; int last_contour_end = -1;
u32 contour_index = 0; i32 contour_index = 0;
u32 contour_size = 0; u32 contour_size = 0;
Optional<Gfx::FloatPoint> contour_start = {}; Optional<Gfx::FloatPoint> contour_start = {};
Optional<Gfx::FloatPoint> last_offcurve_point = {}; Optional<Gfx::FloatPoint> last_offcurve_point = {};
@ -377,7 +386,7 @@ RefPtr<Gfx::Bitmap> Font::Glyf::Glyph::raster_simple(float x_scale, float y_scal
// Render glyph // Render glyph
while (true) { while (true) {
if (!contour_start.has_value()) { if (!contour_start.has_value()) {
if (contour_index >= simple.num_contours) { if (contour_index >= m_num_contours) {
break; break;
} }
int current_contour_end = be_u16(m_slice.offset_pointer(contour_index++ * 2)); int current_contour_end = be_u16(m_slice.offset_pointer(contour_index++ * 2));
@ -449,7 +458,17 @@ RefPtr<Gfx::Bitmap> Font::Glyf::Glyph::raster_simple(float x_scale, float y_scal
} }
} }
return Rasterizer(Gfx::Size(width, height)).draw_path(path); rasterizer.draw_path(path);
}
RefPtr<Gfx::Bitmap> Font::Glyf::Glyph::raster_simple(float x_scale, float y_scale) const
{
u32 width = (u32) (ceil((m_xmax - m_xmin) * x_scale)) + 2;
u32 height = (u32) (ceil((m_ymax - m_ymin) * y_scale)) + 2;
Rasterizer rasterizer(Gfx::IntSize(width, height));
auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -m_ymax);
raster_inner(rasterizer, affine);
return rasterizer.accumulate();
} }
Font::Glyf::Glyph Font::Glyf::glyph(u32 offset) const Font::Glyf::Glyph Font::Glyf::glyph(u32 offset) const
@ -461,10 +480,7 @@ Font::Glyf::Glyph Font::Glyf::glyph(u32 offset) const
i16 xmax = be_i16(m_slice.offset_pointer(offset + (u32) Offsets::XMax)); i16 xmax = be_i16(m_slice.offset_pointer(offset + (u32) Offsets::XMax));
i16 ymax = be_i16(m_slice.offset_pointer(offset + (u32) Offsets::YMax)); i16 ymax = be_i16(m_slice.offset_pointer(offset + (u32) Offsets::YMax));
auto slice = ByteBuffer::wrap(m_slice.offset_pointer(offset + (u32) Sizes::GlyphHeader), m_slice.size() - offset - (u32) Sizes::GlyphHeader); auto slice = ByteBuffer::wrap(m_slice.offset_pointer(offset + (u32) Sizes::GlyphHeader), m_slice.size() - offset - (u32) Sizes::GlyphHeader);
if (num_contours < 0) { return Glyph(slice, xmin, ymin, xmax, ymax, num_contours);
return Glyph::composite(slice);
}
return Glyph::simple(slice, num_contours, xmin, ymin, xmax, ymax);
} }
} }