LibGfx: Remove a bunch of no-longer-used OpenType font code

We're not quite ready to remove the old OpenType implementation yet,
but let's carve off some unused stuff.
This commit is contained in:
Andreas Kling 2024-08-09 20:31:29 +02:00 committed by Andreas Kling
parent ca2cd86999
commit aabad3b94e
Notes: github-actions[bot] 2024-08-20 07:38:01 +00:00
15 changed files with 5 additions and 1195 deletions

View file

@ -456,7 +456,6 @@ if (ENABLE_GUI_TARGETS)
lagom_utility(animation SOURCES ../../Userland/Utilities/animation.cpp LIBS LibGfx LibMain)
lagom_utility(icc SOURCES ../../Userland/Utilities/icc.cpp LIBS LibGfx LibMain LibURL)
lagom_utility(image SOURCES ../../Userland/Utilities/image.cpp LIBS LibGfx LibMain)
lagom_utility(ttfdisasm SOURCES ../../Userland/Utilities/ttfdisasm.cpp LIBS LibGfx LibMain)
endif()
lagom_utility(js SOURCES ../../Userland/Utilities/js.cpp LIBS LibCrypto LibJS LibLine LibUnicode LibMain LibTextCodec Threads::Threads)

View file

@ -158,85 +158,6 @@ void DeprecatedPath::elliptical_arc_to(FloatPoint point, FloatSize radii, float
theta_delta);
}
void DeprecatedPath::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, scaled_font, [&](DrawGlyphOrEmoji const& glyph_or_emoji) {
if (glyph_or_emoji.has<DrawGlyph>()) {
auto& glyph = glyph_or_emoji.get<DrawGlyph>();
move_to(glyph.position);
scaled_font.append_glyph_path_to(*this, glyph.glyph_id);
}
},
IncludeLeftBearing::Yes);
}
DeprecatedPath DeprecatedPath::place_text_along(Utf8View text, Font const& font) const
{
if (!is<ScaledFont>(font)) {
// FIXME: This API only accepts Gfx::Font for ease of use.
dbgln("Cannot path-ify bitmap fonts!");
return {};
}
auto lines = split_lines();
auto next_point_for_offset = [&, line_index = 0U, distance_along_path = 0.0f, last_line_length = 0.0f](float offset) mutable -> Optional<FloatPoint> {
while (line_index < lines.size() && offset > distance_along_path) {
last_line_length = lines[line_index++].length();
distance_along_path += last_line_length;
}
if (offset > distance_along_path)
return {};
if (last_line_length > 1) {
// If the last line segment was fairly long, compute the point in the line.
float p = (last_line_length + offset - distance_along_path) / last_line_length;
auto current_line = lines[line_index - 1];
return current_line.a() + (current_line.b() - current_line.a()).scaled(p);
}
if (line_index >= lines.size())
return {};
return lines[line_index].a();
};
auto& scaled_font = static_cast<Gfx::ScaledFont const&>(font);
Gfx::DeprecatedPath result_path;
Gfx::for_each_glyph_position(
{}, text, font, [&](Gfx::DrawGlyphOrEmoji glyph_or_emoji) {
auto* glyph = glyph_or_emoji.get_pointer<Gfx::DrawGlyph>();
if (!glyph)
return;
auto offset = glyph->position.x();
auto width = scaled_font.glyph_metrics(glyph->glyph_id).advance_width;
auto start = next_point_for_offset(offset);
if (!start.has_value())
return;
auto end = next_point_for_offset(offset + width);
if (!end.has_value())
return;
// Find the angle between the start and end points on the path.
auto delta = *end - *start;
auto angle = AK::atan2(delta.y(), delta.x());
Gfx::DeprecatedPath glyph_path;
// Rotate the glyph then move it to start point.
scaled_font.append_glyph_path_to(glyph_path, glyph->glyph_id);
auto transform = Gfx::AffineTransform {}
.translate(*start)
.multiply(Gfx::AffineTransform {}.rotate_radians(angle))
.multiply(Gfx::AffineTransform {}.translate({ 0, -scaled_font.pixel_metrics().ascent }));
glyph_path = glyph_path.copy_transformed(transform);
result_path.append_path(glyph_path);
},
Gfx::IncludeLeftBearing::Yes);
return result_path;
}
void DeprecatedPath::close()
{
// If there's no `moveto` starting this subpath assume the start is (0, 0).

View file

@ -183,8 +183,6 @@ public:
elliptical_arc_to(point, { radius, radius }, 0, large_arc, sweep);
}
void text(Utf8View, Font const&);
FloatPoint last_point()
{
if (!m_points.is_empty())
@ -196,9 +194,6 @@ public:
void close_all_subpaths();
DeprecatedPath stroke_to_fill(float thickness) const;
DeprecatedPath place_text_along(Utf8View text, Font const&) const;
DeprecatedPath copy_transformed(AffineTransform const&) const;
ReadonlySpan<FloatLine> split_lines() const

View file

@ -75,7 +75,6 @@ public:
virtual u16 weight() const = 0;
virtual bool contains_glyph(u32 code_point) const = 0;
virtual bool append_glyph_path_to(Gfx::DeprecatedPath&, u32 glyph_id) const = 0;
virtual u32 glyph_id_for_code_point(u32 code_point) const = 0;
virtual float glyph_left_bearing(u32 code_point) const = 0;
virtual float glyph_width(u32 code_point) const = 0;
@ -97,8 +96,6 @@ public:
Font const& bold_variant() const;
hb_font_t* harfbuzz_font() const;
virtual bool has_color_bitmaps() const = 0;
virtual Typeface const& typeface() const = 0;
private:

View file

@ -4,9 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Font/OpenType/Glyf.h>
#include <LibGfx/Point.h>
@ -14,167 +11,6 @@ namespace OpenType {
extern u16 be_u16(u8 const* ptr);
extern u32 be_u32(u8 const* ptr);
extern i16 be_i16(u8 const* ptr);
extern float be_fword(u8 const* ptr);
enum class SimpleGlyfFlags {
// From spec.
OnCurve = 0x01,
XShortVector = 0x02,
YShortVector = 0x04,
RepeatFlag = 0x08,
XIsSameOrPositiveXShortVector = 0x10,
YIsSameOrPositiveYShortVector = 0x20,
// Combinations
XMask = 0x12,
YMask = 0x24,
XLongVector = 0x00,
YLongVector = 0x00,
XNegativeShortVector = 0x02,
YNegativeShortVector = 0x04,
XPositiveShortVector = 0x12,
YPositiveShortVector = 0x24,
};
class PointIterator {
public:
struct Item {
bool on_curve;
Gfx::FloatPoint point;
};
PointIterator(ReadonlyBytes slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, Gfx::AffineTransform affine)
: m_slice(slice)
, m_points_remaining(num_points)
, m_flags_offset(flags_offset)
, m_x_offset(x_offset)
, m_y_offset(y_offset)
, m_affine(affine)
{
}
Optional<Item> next()
{
if (m_points_remaining == 0) {
return {};
}
if (m_flags_remaining > 0) {
m_flags_remaining--;
} else {
m_flag = m_slice[m_flags_offset++];
if (m_flag & (u8)SimpleGlyfFlags::RepeatFlag) {
m_flags_remaining = m_slice[m_flags_offset++];
}
}
switch (m_flag & (u8)SimpleGlyfFlags::XMask) {
case (u8)SimpleGlyfFlags::XLongVector:
m_last_point.set_x(m_last_point.x() + be_i16(m_slice.offset(m_x_offset)));
m_x_offset += 2;
break;
case (u8)SimpleGlyfFlags::XNegativeShortVector:
m_last_point.set_x(m_last_point.x() - m_slice[m_x_offset++]);
break;
case (u8)SimpleGlyfFlags::XPositiveShortVector:
m_last_point.set_x(m_last_point.x() + m_slice[m_x_offset++]);
break;
default:
break;
}
switch (m_flag & (u8)SimpleGlyfFlags::YMask) {
case (u8)SimpleGlyfFlags::YLongVector:
m_last_point.set_y(m_last_point.y() + be_i16(m_slice.offset(m_y_offset)));
m_y_offset += 2;
break;
case (u8)SimpleGlyfFlags::YNegativeShortVector:
m_last_point.set_y(m_last_point.y() - m_slice[m_y_offset++]);
break;
case (u8)SimpleGlyfFlags::YPositiveShortVector:
m_last_point.set_y(m_last_point.y() + m_slice[m_y_offset++]);
break;
default:
break;
}
m_points_remaining--;
Item ret = {
.on_curve = (m_flag & (u8)SimpleGlyfFlags::OnCurve) != 0,
.point = m_affine.map(m_last_point),
};
return ret;
}
private:
ReadonlyBytes m_slice;
u16 m_points_remaining;
u8 m_flag { 0 };
Gfx::FloatPoint m_last_point = { 0.0f, 0.0f };
u32 m_flags_remaining = { 0 };
u32 m_flags_offset;
u32 m_x_offset;
u32 m_y_offset;
Gfx::AffineTransform m_affine;
};
Optional<Glyf::Glyph::ComponentIterator::Item> Glyf::Glyph::ComponentIterator::next()
{
if (!m_has_more) {
return {};
}
u16 flags = be_u16(m_slice.offset(m_offset));
m_offset += 2;
u16 glyph_id = be_u16(m_slice.offset(m_offset));
m_offset += 2;
i16 arg1 = 0, arg2 = 0;
if (flags & (u16)CompositeFlags::Arg1AndArg2AreWords) {
arg1 = be_i16(m_slice.offset(m_offset));
m_offset += 2;
arg2 = be_i16(m_slice.offset(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;
if (flags & (u16)CompositeFlags::WeHaveATwoByTwo) {
a = be_fword(m_slice.offset(m_offset));
m_offset += 2;
b = be_fword(m_slice.offset(m_offset));
m_offset += 2;
c = be_fword(m_slice.offset(m_offset));
m_offset += 2;
d = be_fword(m_slice.offset(m_offset));
m_offset += 2;
} else if (flags & (u16)CompositeFlags::WeHaveAnXAndYScale) {
a = be_fword(m_slice.offset(m_offset));
m_offset += 2;
d = be_fword(m_slice.offset(m_offset));
m_offset += 2;
} else if (flags & (u16)CompositeFlags::WeHaveAScale) {
a = be_fword(m_slice.offset(m_offset));
m_offset += 2;
d = a;
}
// FIXME: Handle UseMyMetrics, ScaledComponentOffset, UnscaledComponentOffset, non-ArgsAreXYValues
if (flags & (u16)CompositeFlags::ArgsAreXYValues) {
e = arg1;
f = arg2;
} else {
// FIXME: Implement this. There's no TODO() here since many fonts work just fine without this.
}
if (flags & (u16)CompositeFlags::UseMyMetrics) {
// FIXME: Implement this. There's no TODO() here since many fonts work just fine without this.
}
if (flags & (u16)CompositeFlags::ScaledComponentOffset) {
// FIXME: Implement this. There's no TODO() here since many fonts work just fine without this.
}
if (flags & (u16)CompositeFlags::UnscaledComponentOffset) {
// FIXME: Implement this. There's no TODO() here since many fonts work just fine without this.
}
m_has_more = (flags & (u16)CompositeFlags::MoreComponents);
return Item {
.glyph_id = glyph_id,
.affine = Gfx::AffineTransform(a, b, c, d, e, f),
};
}
ErrorOr<Loca> Loca::from_slice(ReadonlyBytes slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
{
@ -205,121 +41,6 @@ u32 Loca::get_glyph_offset(u32 glyph_id) const
}
}
static void get_ttglyph_offsets(ReadonlyBytes slice, u32 num_points, u32 flags_offset, u32* x_offset, u32* y_offset)
{
u32 flags_size = 0;
u32 x_size = 0;
u32 repeat_count;
while (num_points > 0) {
u8 flag = slice[flags_offset + flags_size];
if (flag & (u8)SimpleGlyfFlags::RepeatFlag) {
flags_size++;
repeat_count = slice[flags_offset + flags_size] + 1;
} else {
repeat_count = 1;
}
flags_size++;
switch (flag & (u8)SimpleGlyfFlags::XMask) {
case (u8)SimpleGlyfFlags::XLongVector:
x_size += repeat_count * 2;
break;
case (u8)SimpleGlyfFlags::XNegativeShortVector:
case (u8)SimpleGlyfFlags::XPositiveShortVector:
x_size += repeat_count;
break;
default:
break;
}
num_points -= repeat_count;
}
*x_offset = flags_offset + flags_size;
*y_offset = *x_offset + x_size;
}
ReadonlyBytes Glyf::Glyph::program() const
{
if (m_num_contours == 0)
return {};
auto instructions_start = m_num_contours * 2;
u16 num_instructions = be_u16(m_slice.offset(instructions_start));
return m_slice.slice(instructions_start + 2, num_instructions);
}
void Glyf::Glyph::append_path_impl(Gfx::DeprecatedPath& path, Gfx::AffineTransform const& transform) const
{
if (m_num_contours == 0)
return;
// Get offset for flags, x, and y.
u16 num_points = be_u16(m_slice.offset((m_num_contours - 1) * 2)) + 1;
u16 num_instructions = be_u16(m_slice.offset(m_num_contours * 2));
u32 flags_offset = m_num_contours * 2 + 2 + num_instructions;
u32 x_offset = 0;
u32 y_offset = 0;
get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset);
// Prepare to render glyph.
PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, transform);
u32 current_point_index = 0;
for (u16 contour_index = 0; contour_index < m_num_contours; contour_index++) {
u32 current_contour_last_point_index = be_u16(m_slice.offset(contour_index * 2));
Vector<PointIterator::Item> points;
while (current_point_index <= current_contour_last_point_index) {
points.append(*point_iterator.next());
current_point_index++;
}
if (points.is_empty())
continue;
auto current = points.last();
auto next = points.first();
if (current.on_curve) {
path.move_to(current.point);
} else if (next.on_curve) {
path.move_to(next.point);
} else {
auto implied_point = (current.point + next.point) * 0.5f;
path.move_to(implied_point);
}
for (size_t i = 0; i < points.size(); i++) {
current = next;
next = points[(i + 1) % points.size()];
if (current.on_curve) {
path.line_to(current.point);
} else if (next.on_curve) {
path.quadratic_bezier_curve_to(current.point, next.point);
} else {
auto implied_point = (current.point + next.point) * 0.5f;
path.quadratic_bezier_curve_to(current.point, implied_point);
}
}
}
}
bool Glyf::Glyph::append_simple_path(Gfx::DeprecatedPath& 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 false;
}
if (font_descender > font_ascender) [[unlikely]] {
dbgln("OpenType: Glyph has invalid ascender ({}) > descender ({})", font_ascender, font_descender);
return false;
}
auto affine = Gfx::AffineTransform()
.translate(path.last_point())
.scale(x_scale, -y_scale)
.translate(-m_xmin, -font_ascender);
append_path_impl(path, affine);
return true;
}
Optional<Glyf::Glyph> Glyf::glyph(u32 offset) const
{
if (offset + sizeof(GlyphHeader) > m_slice.size())

View file

@ -9,9 +9,6 @@
#include <AK/Endian.h>
#include <AK/Span.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibGfx/Size.h>
@ -43,21 +40,6 @@ private:
// glyf: Glyph Data
class Glyf {
public:
enum class CompositeFlags {
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 Glyph {
public:
Glyph(ReadonlyBytes slice, i16 xmin, i16 ymin, i16 xmax, i16 ymax, i16 num_contours = -1)
@ -73,90 +55,18 @@ public:
}
}
template<typename GlyphCb>
bool append_path(Gfx::DeprecatedPath& path, i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
{
switch (m_type) {
case Type::Simple:
return append_simple_path(path, font_ascender, font_descender, x_scale, y_scale);
case Type::Composite:
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; }
ReadonlyBytes program() const;
private:
enum class Type {
Simple,
Composite,
};
class ComponentIterator {
public:
struct Item {
u16 glyph_id;
Gfx::AffineTransform affine;
};
ComponentIterator(ReadonlyBytes slice)
: m_slice(slice)
{
}
Optional<Item> next();
private:
ReadonlyBytes m_slice;
bool m_has_more { true };
u32 m_offset { 0 };
};
void append_path_impl(Gfx::DeprecatedPath&, Gfx::AffineTransform const&) const;
bool append_simple_path(Gfx::DeprecatedPath&, i16 ascender, i16 descender, float x_scale, float y_scale) const;
template<typename GlyphCb>
void resolve_composite_path_loop(Gfx::DeprecatedPath& path, Gfx::AffineTransform const& transform, GlyphCb glyph_callback) const
{
ComponentIterator component_iterator(m_slice);
while (true) {
auto opt_item = component_iterator.next();
if (!opt_item.has_value()) {
break;
}
auto item = opt_item.value();
Gfx::AffineTransform affine_here { transform };
affine_here.multiply(item.affine);
auto glyph = glyph_callback(item.glyph_id);
if (!glyph.has_value())
continue;
if (glyph->m_type == Type::Simple) {
glyph->append_path_impl(path, affine_here);
} else {
glyph->resolve_composite_path_loop(path, transform, glyph_callback);
}
}
}
template<typename GlyphCb>
bool append_composite_path(Gfx::DeprecatedPath& path, i16 font_ascender, float x_scale, float y_scale, GlyphCb glyph_callback) const
{
auto affine = Gfx::AffineTransform()
.translate(path.last_point())
.scale(x_scale, -y_scale)
.translate(-m_xmin, -font_ascender);
resolve_composite_path_loop(path, affine, glyph_callback);
return true;
}
Type m_type { Type::Composite };
i16 m_xmin { 0 };
i16 m_ymin { 0 };

View file

@ -443,85 +443,6 @@ Optional<i16> OS2::x_height() const
[](auto*) { return Optional<i16>(); });
}
ErrorOr<CBLC> CBLC::from_slice(ReadonlyBytes slice)
{
if (slice.size() < sizeof(CblcHeader))
return Error::from_string_literal("CBLC table too small");
auto const& header = *bit_cast<CblcHeader const*>(slice.data());
size_t num_sizes = header.num_sizes;
Checked<size_t> size_used_by_bitmap_sizes = num_sizes;
size_used_by_bitmap_sizes *= sizeof(BitmapSize);
if (size_used_by_bitmap_sizes.has_overflow())
return Error::from_string_literal("Integer overflow in CBLC table");
Checked<size_t> total_size = sizeof(CblcHeader);
total_size += size_used_by_bitmap_sizes;
if (total_size.has_overflow())
return Error::from_string_literal("Integer overflow in CBLC table");
if (slice.size() < total_size)
return Error::from_string_literal("CBLC table too small");
ReadonlySpan<BitmapSize> bitmap_sizes { bit_cast<BitmapSize const*>(slice.data() + sizeof(CblcHeader)), num_sizes };
return CBLC { slice, header, bitmap_sizes };
}
Optional<CBLC::BitmapSize const&> CBLC::bitmap_size_for_glyph_id(u32 glyph_id) const
{
for (auto const& bitmap_size : m_bitmap_sizes) {
if (glyph_id >= bitmap_size.start_glyph_index && glyph_id <= bitmap_size.end_glyph_index) {
return bitmap_size;
}
}
return {};
}
Optional<EBLC::IndexSubHeader const&> CBLC::index_subtable_for_glyph_id(u32 glyph_id, u16& first_glyph_index, u16& last_glyph_index) const
{
auto maybe_bitmap_size = bitmap_size_for_glyph_id(glyph_id);
if (!maybe_bitmap_size.has_value()) {
return {};
}
auto const& bitmap_size = maybe_bitmap_size.value();
Checked<size_t> required_size = static_cast<u32>(bitmap_size.index_subtable_array_offset);
required_size += bitmap_size.index_tables_size;
if (m_slice.size() < required_size) {
dbgln("CBLC index subtable array goes out of bounds");
return {};
}
auto index_subtables_slice = m_slice.slice(bitmap_size.index_subtable_array_offset, bitmap_size.index_tables_size);
ReadonlySpan<EBLC::IndexSubTableArray> index_subtable_arrays {
bit_cast<EBLC::IndexSubTableArray const*>(index_subtables_slice.data()), bitmap_size.number_of_index_subtables
};
EBLC::IndexSubTableArray const* index_subtable_array = nullptr;
for (auto const& array : index_subtable_arrays) {
if (glyph_id >= array.first_glyph_index && glyph_id <= array.last_glyph_index)
index_subtable_array = &array;
}
if (!index_subtable_array) {
return {};
}
auto index_subtable_slice = m_slice.slice(bitmap_size.index_subtable_array_offset + index_subtable_array->additional_offset_to_index_subtable);
first_glyph_index = index_subtable_array->first_glyph_index;
last_glyph_index = index_subtable_array->last_glyph_index;
return *bit_cast<EBLC::IndexSubHeader const*>(index_subtable_slice.data());
}
ErrorOr<CBDT> CBDT::from_slice(ReadonlyBytes slice)
{
if (slice.size() < sizeof(CbdtHeader))
return Error::from_string_literal("CBDT table too small");
auto const& header = *bit_cast<CbdtHeader*>(slice.data());
return CBDT { slice, header };
}
ErrorOr<GPOS> GPOS::from_slice(ReadonlyBytes slice)
{
FixedMemoryStream stream { slice };

View file

@ -192,36 +192,6 @@ struct GlyphHorizontalMetrics {
i16 left_side_bearing;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/fpgm
// fpgm: Font Program
struct Fpgm {
public:
explicit Fpgm(ReadonlyBytes slice)
: m_slice(slice)
{
}
ReadonlyBytes program_data() const { return m_slice; }
private:
ReadonlyBytes m_slice;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/prep
// prep: Control Value Program
struct Prep {
public:
explicit Prep(ReadonlyBytes slice)
: m_slice(slice)
{
}
ReadonlyBytes program_data() const { return m_slice; }
private:
ReadonlyBytes m_slice;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx
// hmtx: Horizontal Metrics Table
class Hmtx {
@ -455,150 +425,6 @@ private:
Vector<Subtable> const m_subtables;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
// EBLC — Embedded Bitmap Location Table
class EBLC {
public:
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#sbitlinemetrics-record
struct [[gnu::packed]] SbitLineMetrics {
Int8 ascender {};
Int8 descender {};
Uint8 width_max {};
Int8 caret_slope_numerator {};
Int8 caret_slope_denominator {};
Int8 caret_offset {};
Int8 min_origin_sb {};
Int8 min_advance_sb {};
Int8 max_before_bl {};
Int8 min_after_bl {};
Int8 pad1 {};
Int8 pad2 {};
};
static_assert(AssertSize<SbitLineMetrics, 12>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtablearray
struct [[gnu::packed]] IndexSubTableArray {
Uint16 first_glyph_index;
Uint16 last_glyph_index;
Offset32 additional_offset_to_index_subtable;
};
static_assert(AssertSize<IndexSubTableArray, 8>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubheader
struct [[gnu::packed]] IndexSubHeader {
Uint16 index_format;
Uint16 image_format;
Offset32 image_data_offset;
};
static_assert(AssertSize<IndexSubHeader, 8>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable1-variable-metrics-glyphs-with-4-byte-offsets
// IndexSubTable1: variable-metrics glyphs with 4-byte offsets
struct [[gnu::packed]] IndexSubTable1 {
IndexSubHeader header;
Offset32 sbit_offsets[];
};
static_assert(AssertSize<IndexSubTable1, 8>());
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/cblc
// CBLC — Color Bitmap Location Table
class CBLC {
public:
// https://learn.microsoft.com/en-us/typography/opentype/spec/cblc#bitmapsize-record
struct [[gnu::packed]] BitmapSize {
Offset32 index_subtable_array_offset;
Uint32 index_tables_size;
Uint32 number_of_index_subtables;
Uint32 color_ref;
EBLC::SbitLineMetrics hori;
EBLC::SbitLineMetrics vert;
Uint16 start_glyph_index;
Uint16 end_glyph_index;
Uint8 ppem_x {};
Uint8 ppem_y {};
Uint8 bit_depth {};
Int8 flags {};
};
static_assert(AssertSize<BitmapSize, 48>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/cblc#cblcheader
struct [[gnu::packed]] CblcHeader {
Uint16 major_version;
Uint16 minor_version;
Uint32 num_sizes;
// Stored in a separate span:
// BitmapSize bitmap_sizes[];
};
static_assert(AssertSize<CblcHeader, 8>());
static ErrorOr<CBLC> from_slice(ReadonlyBytes);
Optional<BitmapSize const&> bitmap_size_for_glyph_id(u32 glyph_id) const;
Optional<EBLC::IndexSubHeader const&> index_subtable_for_glyph_id(u32 glyph_id, u16& first_glyph_index, u16& last_glyph_index) const;
private:
explicit CBLC(ReadonlyBytes slice, CblcHeader const& header, ReadonlySpan<BitmapSize> bitmap_sizes)
: m_slice(slice)
, m_header(header)
, m_bitmap_sizes(bitmap_sizes)
{
}
ReadonlyBytes m_slice;
CblcHeader const& m_header;
ReadonlySpan<BitmapSize> m_bitmap_sizes;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt
// EBDT — Embedded Bitmap Data Table
class EBDT {
public:
// https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#smallglyphmetrics
struct [[gnu::packed]] SmallGlyphMetrics {
Uint8 height {};
Uint8 width {};
Int8 bearing_x {};
Int8 bearing_y {};
Uint8 advance {};
};
static_assert(AssertSize<SmallGlyphMetrics, 5>());
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt
// CBDT — Color Bitmap Data Table
class CBDT {
public:
// https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#table-structure
struct [[gnu::packed]] CbdtHeader {
Uint16 major_version;
Uint16 minor_version;
};
static_assert(AssertSize<CbdtHeader, 4>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-17-small-metrics-png-image-data
struct [[gnu::packed]] Format17 {
EBDT::SmallGlyphMetrics glyph_metrics;
Uint32 data_len;
Uint8 data[];
};
static_assert(AssertSize<Format17, 9>());
static ErrorOr<CBDT> from_slice(ReadonlyBytes);
ReadonlyBytes bytes() const { return m_slice; }
CbdtHeader const& header() const { return m_header; }
private:
explicit CBDT(ReadonlyBytes slice, CbdtHeader const& header)
: m_slice(slice)
, m_header(header)
{
}
ReadonlyBytes m_slice;
CbdtHeader const& m_header;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-list-table
struct [[gnu::packed]] FeatureRecord {
Tag feature_tag;

View file

@ -128,7 +128,6 @@ namespace OpenType {
u16 be_u16(u8 const*);
u32 be_u32(u8 const*);
i16 be_i16(u8 const*);
float be_fword(u8 const*);
u16 be_u16(u8 const* ptr)
{
@ -145,11 +144,6 @@ i16 be_i16(u8 const* ptr)
return (((i16)ptr[0]) << 8) | ((i16)ptr[1]);
}
float be_fword(u8 const* ptr)
{
return (float)be_i16(ptr) / (float)(1 << 14);
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_resource(Core::Resource const& resource, unsigned index)
{
auto font_data = Gfx::FontData::create_from_resource(resource);
@ -229,11 +223,7 @@ ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes bu
Optional<ReadonlyBytes> opt_glyf_slice = {};
Optional<ReadonlyBytes> opt_os2_slice = {};
Optional<ReadonlyBytes> opt_kern_slice = {};
Optional<ReadonlyBytes> opt_fpgm_slice = {};
Optional<ReadonlyBytes> opt_prep_slice = {};
Optional<CBLC> cblc;
Optional<CBDT> cbdt;
Optional<GPOS> gpos;
TRY(for_each_table_record(buffer, offset, [&](Tag table_tag, ReadonlyBytes tag_buffer) -> ErrorOr<void> {
@ -258,14 +248,6 @@ ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes bu
opt_os2_slice = tag_buffer;
} else if (table_tag == Tag("kern")) {
opt_kern_slice = tag_buffer;
} else if (table_tag == Tag("fpgm")) {
opt_fpgm_slice = tag_buffer;
} else if (table_tag == Tag("prep")) {
opt_prep_slice = tag_buffer;
} else if (table_tag == Tag("CBLC")) {
cblc = TRY(CBLC::from_slice(tag_buffer));
} else if (table_tag == Tag("CBDT")) {
cbdt = TRY(CBDT::from_slice(tag_buffer));
} else if (table_tag == Tag("GPOS")) {
gpos = TRY(GPOS::from_slice(tag_buffer));
}
@ -329,14 +311,6 @@ ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes bu
if (opt_kern_slice.has_value())
kern = TRY(Kern::from_slice(opt_kern_slice.value()));
Optional<Fpgm> fpgm;
if (opt_fpgm_slice.has_value())
fpgm = Fpgm(opt_fpgm_slice.value());
Optional<Prep> prep;
if (opt_prep_slice.has_value())
prep = Prep(opt_prep_slice.value());
return adopt_ref(*new Typeface(
move(head),
move(name),
@ -348,10 +322,6 @@ ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes bu
move(glyf),
move(os2),
move(kern),
move(fpgm),
move(prep),
move(cblc),
move(cbdt),
move(gpos),
buffer.slice(offset),
options.index));
@ -387,46 +357,8 @@ Gfx::ScaledFontMetrics Typeface::metrics([[maybe_unused]] float x_scale, float y
};
}
Typeface::EmbeddedBitmapData Typeface::embedded_bitmap_data_for_glyph(u32 glyph_id) const
float Typeface::glyph_advance(u32 glyph_id, float x_scale, float, float, float) const
{
if (!has_color_bitmaps())
return Empty {};
u16 first_glyph_index {};
u16 last_glyph_index {};
auto maybe_index_subtable = m_cblc->index_subtable_for_glyph_id(glyph_id, first_glyph_index, last_glyph_index);
if (!maybe_index_subtable.has_value())
return Empty {};
auto const& index_subtable = maybe_index_subtable.value();
auto const& bitmap_size = m_cblc->bitmap_size_for_glyph_id(glyph_id).value();
if (index_subtable.index_format == 1) {
auto const& index_subtable1 = *bit_cast<EBLC::IndexSubTable1 const*>(&index_subtable);
size_t size_of_array = (last_glyph_index - first_glyph_index + 1) + 1;
auto sbit_offsets = ReadonlySpan<Offset32> { index_subtable1.sbit_offsets, size_of_array };
auto sbit_offset = sbit_offsets[glyph_id - first_glyph_index];
size_t glyph_data_offset = sbit_offset + index_subtable.image_data_offset;
if (index_subtable.image_format == 17) {
return EmbeddedBitmapWithFormat17 {
.bitmap_size = bitmap_size,
.format17 = *bit_cast<CBDT::Format17 const*>(m_cbdt->bytes().slice(glyph_data_offset, size_of_array).data()),
};
}
dbgln("FIXME: Implement OpenType embedded bitmap image format {}", index_subtable.image_format);
} else {
dbgln("FIXME: Implement OpenType embedded bitmap index format {}", index_subtable.index_format);
}
return Empty {};
}
float Typeface::glyph_advance(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const
{
if (has_color_bitmaps())
return glyph_metrics(glyph_id, x_scale, y_scale, point_width, point_height).advance_width;
if (!m_hmtx.has_value())
return 0;
@ -437,33 +369,8 @@ float Typeface::glyph_advance(u32 glyph_id, float x_scale, float y_scale, float
return static_cast<float>(horizontal_metrics.advance_width) * x_scale;
}
Gfx::ScaledGlyphMetrics Typeface::glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const
Gfx::ScaledGlyphMetrics Typeface::glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float, float) const
{
auto embedded_bitmap_metrics = embedded_bitmap_data_for_glyph(glyph_id).visit(
[&](EmbeddedBitmapWithFormat17 const& data) -> Optional<Gfx::ScaledGlyphMetrics> {
// FIXME: This is a pretty ugly hack to work out new scale factors based on the relationship between
// the pixels-per-em values and the font point size. It appears that bitmaps are not in the same
// coordinate space as the head table's "units per em" value.
// There's definitely some cleaner way to do this.
float x_scale = (point_width * DEFAULT_DPI) / (POINTS_PER_INCH * data.bitmap_size.ppem_x);
float y_scale = (point_height * DEFAULT_DPI) / (POINTS_PER_INCH * data.bitmap_size.ppem_y);
return Gfx::ScaledGlyphMetrics {
.ascender = static_cast<float>(data.bitmap_size.hori.ascender) * y_scale,
.descender = static_cast<float>(data.bitmap_size.hori.descender) * y_scale,
.advance_width = static_cast<float>(data.format17.glyph_metrics.advance) * x_scale,
.left_side_bearing = static_cast<float>(data.format17.glyph_metrics.bearing_x) * x_scale,
};
},
[&](Empty) -> Optional<Gfx::ScaledGlyphMetrics> {
// Unsupported format or no embedded bitmap for this glyph ID.
return {};
});
if (embedded_bitmap_metrics.has_value()) {
return embedded_bitmap_metrics.release_value();
}
if (!m_loca.has_value() || !m_glyf.has_value() || !m_hmtx.has_value()) {
return Gfx::ScaledGlyphMetrics {};
}
@ -511,61 +418,6 @@ float Typeface::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id,
return 0.0f;
}
Typeface::AscenderAndDescender Typeface::resolve_ascender_and_descender() const
{
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> Typeface::extract_and_append_glyph_path_to(Gfx::DeprecatedPath& 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 {};
}
if (glyph_id >= glyph_count()) {
glyph_id = 0;
}
auto glyph_offset0 = m_loca->get_glyph_offset(glyph_id);
auto glyph_offset1 = m_loca->get_glyph_offset(glyph_id + 1);
// If a glyph has no outline, then loca[n] = loca [n+1].
if (glyph_offset0 == glyph_offset1)
return {};
auto glyph = m_glyf->glyph(glyph_offset0);
if (!glyph.has_value())
return {};
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 Typeface::append_glyph_path_to(Gfx::DeprecatedPath& 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();
}
u32 Typeface::glyph_count() const
{
return m_maxp.num_glyphs();
@ -652,40 +504,6 @@ u8 Typeface::slope() const
return *m_slope;
}
bool Typeface::is_fixed_width() const
{
// FIXME: Read this information from the font file itself.
// FIXME: Although, it appears some application do similar hacks
return glyph_metrics(glyph_id_for_code_point('.'), 1, 1, 1, 1).advance_width == glyph_metrics(glyph_id_for_code_point('X'), 1, 1, 1, 1).advance_width;
}
Optional<ReadonlyBytes> Typeface::font_program() const
{
if (m_fpgm.has_value())
return m_fpgm->program_data();
return {};
}
Optional<ReadonlyBytes> Typeface::control_value_program() const
{
if (m_prep.has_value())
return m_prep->program_data();
return {};
}
Optional<ReadonlyBytes> Typeface::glyph_program(u32 glyph_id) const
{
if (!m_loca.has_value() || !m_glyf.has_value()) {
return {};
}
auto glyph_offset = m_loca->get_glyph_offset(glyph_id);
auto glyph = m_glyf->glyph(glyph_offset);
if (!glyph.has_value())
return {};
return glyph->program();
}
u32 Typeface::glyph_id_for_code_point(u32 code_point) const
{
return glyph_page(code_point / GlyphPage::glyphs_per_page).glyph_ids[code_point % GlyphPage::glyphs_per_page];
@ -719,28 +537,5 @@ void Typeface::populate_glyph_page(GlyphPage& glyph_page, size_t page_index) con
glyph_page.glyph_ids[i] = m_cmap->glyph_id_for_code_point(code_point);
}
}
bool Typeface::has_color_bitmaps() const
{
return m_cblc.has_value() && m_cbdt.has_value();
}
RefPtr<Gfx::Bitmap> Typeface::color_bitmap(u32 glyph_id) const
{
return embedded_bitmap_data_for_glyph(glyph_id).visit(
[&](EmbeddedBitmapWithFormat17 const& data) -> RefPtr<Gfx::Bitmap> {
auto data_slice = ReadonlyBytes { data.format17.data, static_cast<u32>(data.format17.data_len) };
auto decoder = Gfx::PNGImageDecoderPlugin::create(data_slice).release_value_but_fixme_should_propagate_errors();
auto frame = decoder->frame(0);
if (frame.is_error()) {
dbgln("PNG decode failed");
return nullptr;
}
return frame.value().image;
},
[&](Empty) -> RefPtr<Gfx::Bitmap> {
// Unsupported format or no image for this glyph ID.
return nullptr;
});
}
}

View file

@ -58,7 +58,6 @@ 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 glyph_advance(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 bool append_glyph_path_to(Gfx::DeprecatedPath&, 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;
@ -67,12 +66,6 @@ public:
virtual u16 weight() const override;
virtual u16 width() const override;
virtual u8 slope() const override;
virtual bool is_fixed_width() const override;
virtual bool has_color_bitmaps() const override;
Optional<ReadonlyBytes> font_program() const;
Optional<ReadonlyBytes> control_value_program() const;
Optional<ReadonlyBytes> glyph_program(u32 glyph_id) const;
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff
// "OpenType fonts that contain TrueType outlines should use the value of 0x00010000 for the sfntVersion.
@ -92,26 +85,6 @@ protected:
virtual unsigned ttc_index() const override { return m_ttc_index; }
private:
struct AscenderAndDescender {
i16 ascender;
i16 descender;
};
AscenderAndDescender resolve_ascender_and_descender() const;
Optional<Glyf::Glyph> extract_and_append_glyph_path_to(Gfx::DeprecatedPath&, 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 {
CBLC::BitmapSize const& bitmap_size;
CBDT::Format17 const& format17;
};
using EmbeddedBitmapData = Variant<EmbeddedBitmapWithFormat17, Empty>;
EmbeddedBitmapData embedded_bitmap_data_for_glyph(u32 glyph_id) const;
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_offset(ReadonlyBytes, u32 offset, Options options);
Typeface(
@ -125,10 +98,6 @@ private:
Optional<Glyf>&& glyf,
Optional<OS2> os2,
Optional<Kern>&& kern,
Optional<Fpgm> fpgm,
Optional<Prep> prep,
Optional<CBLC> cblc,
Optional<CBDT> cbdt,
Optional<GPOS> gpos,
ReadonlyBytes buffer,
unsigned ttc_index)
@ -144,10 +113,6 @@ private:
, m_cmap(move(cmap))
, m_os2(move(os2))
, m_kern(move(kern))
, m_fpgm(move(fpgm))
, m_prep(move(prep))
, m_cblc(move(cblc))
, m_cbdt(move(cbdt))
, m_gpos(move(gpos))
{
}
@ -167,10 +132,6 @@ private:
NonnullOwnPtr<CharCodeToGlyphIndex> m_cmap;
Optional<OS2> m_os2;
Optional<Kern> m_kern;
Optional<Fpgm> m_fpgm;
Optional<Prep> m_prep;
Optional<CBLC> m_cblc;
Optional<CBDT> m_cbdt;
Optional<GPOS> m_gpos;
// This cache stores information per code point.

View file

@ -63,11 +63,6 @@ ALWAYS_INLINE float ScaledFont::unicode_view_width(T const& view) const
return longest_width;
}
bool ScaledFont::append_glyph_path_to(Gfx::DeprecatedPath& path, u32 glyph_id) const
{
return m_typeface->append_glyph_path_to(path, glyph_id, m_x_scale, m_y_scale);
}
float ScaledFont::glyph_left_bearing(u32 code_point) const
{
auto id = glyph_id_for_code_point(code_point);
@ -83,10 +78,8 @@ float ScaledFont::glyph_width(u32 code_point) const
template<typename CodePointIterator>
static float glyph_or_emoji_width_impl(ScaledFont const& font, CodePointIterator& it)
{
if (!font.has_color_bitmaps()) {
if (auto const* emoji = Emoji::emoji_for_code_point_iterator(it))
return font.pixel_size() * emoji->width() / emoji->height();
}
if (auto const* emoji = Emoji::emoji_for_code_point_iterator(it))
return font.pixel_size() * emoji->width() / emoji->height();
return font.glyph_width(*it);
}

View file

@ -35,7 +35,6 @@ public:
virtual float glyph_or_emoji_width(Utf8CodePointIterator&) const override;
virtual float glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const override;
virtual u32 glyph_id_for_code_point(u32 code_point) const override { return m_typeface->glyph_id_for_code_point(code_point); }
virtual bool append_glyph_path_to(Gfx::DeprecatedPath&, u32 glyph_id) const override;
virtual float preferred_line_height() const override { return metrics().height() + metrics().line_gap; }
virtual int x_height() const override { return m_point_height; } // FIXME: Read from font
virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font
@ -47,8 +46,6 @@ public:
virtual NonnullRefPtr<ScaledFont> scaled_with_size(float point_size) const;
virtual NonnullRefPtr<Font> with_size(float point_size) const override;
virtual bool has_color_bitmaps() const override { return m_typeface->has_color_bitmaps(); }
virtual Typeface const& typeface() const override { return m_typeface; }
SkFont skia_font(float scale) const;

View file

@ -51,7 +51,6 @@ public:
virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const = 0;
virtual float glyph_advance(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 bool append_glyph_path_to(Gfx::DeprecatedPath&, u32 glyph_id, float x_scale, float y_scale) const = 0;
virtual u32 glyph_count() const = 0;
virtual u16 units_per_em() const = 0;
@ -61,8 +60,6 @@ public:
virtual u16 weight() const = 0;
virtual u16 width() const = 0;
virtual u8 slope() const = 0;
virtual bool is_fixed_width() const = 0;
virtual bool has_color_bitmaps() const = 0;
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;

View file

@ -14,33 +14,8 @@
namespace Gfx {
static DrawGlyphOrEmoji construct_glyph_or_emoji(size_t index, FloatPoint const& position, Gfx::Font const& font, Span<hb_glyph_info_t const> glyph_info, Span<hb_glyph_info_t const> input_glyph_info)
static DrawGlyphOrEmoji construct_glyph_or_emoji(size_t index, FloatPoint const& position, Gfx::Font const&, Span<hb_glyph_info_t const> glyph_info, Span<hb_glyph_info_t const>)
{
if (font.has_color_bitmaps()) {
auto cluster_start = glyph_info[index].cluster;
auto cluster_end = [&]() -> u32 {
if (index + 1 < glyph_info.size())
return glyph_info[index + 1].cluster;
return input_glyph_info.last().cluster + 1;
}();
Vector<u32> cluster;
for (size_t j = 0; j < input_glyph_info.size(); ++j) {
auto const& glyph = input_glyph_info[j];
if (glyph.cluster >= cluster_end)
break;
if (glyph.cluster >= cluster_start)
cluster.append(glyph.codepoint);
}
if (auto const* emoji = Emoji::emoji_for_code_points(cluster)) {
return DrawEmoji {
.position = position,
.emoji = emoji,
};
}
}
return DrawGlyph {
.position = position,
.glyph_id = glyph_info[index].codepoint,

View file

@ -1,198 +0,0 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/Utf8View.h>
#include <LibCore/ArgsParser.h>
#include <LibGfx/Font/OpenType/Hinting/Opcodes.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibMain/Main.h>
using namespace OpenType::Hinting;
#define YELLOW "\e[33m"
#define CYAN "\e[36m"
#define PURPLE "\e[95m"
#define GREEN "\e[92m"
#define RESET "\e[0m"
#define GRAY "\e[90m"
struct InstructionPrinter : InstructionHandler {
InstructionPrinter(bool enable_highlighting)
: m_enable_highlighting(enable_highlighting)
{
}
void before_operation(InstructionStream& stream, Opcode opcode) override
{
if (opcode == Opcode::FDEF && stream.current_position() > 1 && m_indent_level == 1)
outln();
switch (opcode) {
case Opcode::EIF:
case Opcode::ELSE:
case Opcode::ENDF:
m_indent_level--;
break;
default:
break;
}
auto digits = int(AK::log10(float(stream.length()))) + 1;
if (m_enable_highlighting)
out(GRAY);
out("{:0{}}:", stream.current_position() - 1, digits);
if (m_enable_highlighting)
out(RESET);
out("{:{}}", ""sv, m_indent_level * 2);
}
void after_operation(InstructionStream&, Opcode opcode) override
{
switch (opcode) {
case Opcode::IF:
case Opcode::ELSE:
case Opcode::IDEF:
case Opcode::FDEF:
m_indent_level++;
break;
default:
break;
}
}
void print_number(u16 value)
{
if (m_enable_highlighting)
return out(GREEN " {}" RESET, value);
return out(" {}", value);
}
void print_bytes(ReadonlyBytes bytes, bool first = true)
{
for (auto value : bytes) {
if (!first)
out(",");
print_number(value);
first = false;
}
}
void print_words(ReadonlyBytes bytes, bool first = true)
{
for (size_t i = 0; i < bytes.size(); i += 2) {
if (!first)
out(",");
print_number(bytes[i] << 8 | bytes[i + 1]);
first = false;
}
}
void default_handler(Context context) override
{
auto instruction = context.instruction();
auto name = opcode_mnemonic(instruction.opcode());
if (m_enable_highlighting)
out(YELLOW);
out(name);
if (m_enable_highlighting)
out(CYAN);
out("[");
if (m_enable_highlighting)
out(PURPLE);
if (instruction.flag_bits() > 0)
out("{:0{}b}", to_underlying(instruction.opcode()) & ((1 << instruction.flag_bits()) - 1), instruction.flag_bits());
if (m_enable_highlighting)
out(CYAN);
out("]");
if (m_enable_highlighting)
out(RESET);
switch (instruction.opcode()) {
case Opcode::NPUSHB... Opcode::NPUSHB_MAX:
print_number(instruction.values().size());
print_bytes(instruction.values(), false);
break;
case Opcode::NPUSHW... Opcode::NPUSHW_MAX:
print_number(instruction.values().size() / 2);
print_words(instruction.values(), false);
break;
case Opcode::PUSHB... Opcode::PUSHB_MAX:
print_bytes(instruction.values());
break;
case Opcode::PUSHW... Opcode::PUSHW_MAX:
print_words(instruction.values());
break;
default:
break;
}
outln();
}
private:
bool m_enable_highlighting;
u32 m_indent_level { 1 };
};
static bool s_disassembly_attempted = false;
static void print_disassembly(StringView name, Optional<ReadonlyBytes> program, bool enable_highlighting, u32 code_point = 0)
{
s_disassembly_attempted = true;
if (!program.has_value()) {
out(name, code_point);
outln(": not found");
return;
}
out(name, code_point);
outln(": ({} bytes)\n", program->size());
InstructionPrinter printer { enable_highlighting };
InstructionStream stream { printer, *program };
while (!stream.at_end())
stream.process_next_instruction();
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::ArgsParser args_parser;
StringView font_path;
bool no_color = false;
bool dump_font_program = false;
bool dump_prep_program = false;
StringView text;
args_parser.add_positional_argument(font_path, "Path to font", "FILE");
args_parser.add_option(dump_font_program, "Disassemble font program (fpgm table)", "disasm-fpgm", 'f');
args_parser.add_option(dump_prep_program, "Disassemble CVT program (prep table)", "disasm-prep", 'p');
args_parser.add_option(text, "Disassemble glyph programs", "disasm-glyphs", 'g', "text");
args_parser.add_option(no_color, "Disable syntax highlighting", "no-color", 'n');
args_parser.parse(arguments);
auto resource = TRY(Core::Resource::load_from_filesystem(font_path));
auto font = TRY(OpenType::Typeface::try_load_from_resource(resource));
if (dump_font_program)
print_disassembly("Font program"sv, font->font_program(), !no_color);
if (dump_prep_program) {
if (dump_font_program)
outln();
print_disassembly("CVT program"sv, font->control_value_program(), !no_color);
}
if (!text.is_empty()) {
Utf8View utf8_view { text };
bool first = !(dump_font_program || dump_prep_program);
for (u32 code_point : utf8_view) {
if (!first)
outln();
auto glyph_id = font->glyph_id_for_code_point(code_point);
print_disassembly("Glyph program for codepoint {}"sv, font->glyph_program(glyph_id), !no_color, code_point);
first = false;
}
}
if (!s_disassembly_attempted) {
args_parser.print_usage(stderr, arguments.strings[0]);
return 1;
}
return 0;
}