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:
parent
bd354bc2ae
commit
1e1d2cdedf
4 changed files with 328 additions and 220 deletions
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue