mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 04:25:13 +00:00
Utilities: Add ttfdisasm for disassembling OpenType instructions
This utility when given a .tff font provides options for disassembling: - The 'fpgm' table, this a program that's run once when the font is loaded. It's used to define instructions and functions used by used by other programs. - The 'prep' table, this is a general program that's run when ever the font size (or other properties) changes. - And the programs associated with any individual glyph. The disassembly is printed in a format that matches the examples from: https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions I'm mainly adding this because I think it's neat to be able to look at these things, and think it'll be helpful for debugging an interpreter. With this you can see that all of the LiberationSerif-XXX.tff fonts in Serenity have these programs ready to go.
This commit is contained in:
parent
768dc4cda1
commit
1cf45ee930
Notes:
sideshowbarker
2024-07-17 01:48:56 +09:00
Author: https://github.com/MacDue Commit: https://github.com/SerenityOS/serenity/commit/1cf45ee930 Pull-request: https://github.com/SerenityOS/serenity/pull/16923 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/ldm5180
3 changed files with 201 additions and 0 deletions
|
@ -488,6 +488,9 @@ if (BUILD_LAGOM)
|
|||
add_executable(icc ../../Userland/Utilities/icc.cpp)
|
||||
target_link_libraries(icc LibCore LibGfx LibMain)
|
||||
|
||||
add_executable(ttfdisasm ../../Userland/Utilities/ttfdisasm.cpp)
|
||||
target_link_libraries(ttfdisasm LibGfx LibMain)
|
||||
|
||||
add_executable(js ../../Userland/Utilities/js.cpp)
|
||||
target_link_libraries(js LibCrypto LibJS LibLine LibLocale LibMain LibTextCodec Threads::Threads)
|
||||
if (EMSCRIPTEN)
|
||||
|
|
|
@ -117,6 +117,7 @@ target_link_libraries(shot PRIVATE LibGfx LibGUI LibIPC)
|
|||
target_link_libraries(sql PRIVATE LibLine LibSQL LibIPC)
|
||||
target_link_libraries(su PRIVATE LibCrypt)
|
||||
target_link_libraries(syscall PRIVATE LibSystem)
|
||||
target_link_libraries(ttfdisasm PRIVATE LibGfx)
|
||||
target_link_libraries(tar PRIVATE LibArchive LibCompress)
|
||||
target_link_libraries(telws PRIVATE LibProtocol LibLine)
|
||||
target_link_libraries(test-fuzz PRIVATE LibGemini LibGfx LibHTTP LibIPC LibJS LibMarkdown LibRegex LibShell)
|
||||
|
|
197
Userland/Utilities/ttfdisasm.cpp
Normal file
197
Userland/Utilities/ttfdisasm.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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/Font.h>
|
||||
#include <LibGfx/Font/OpenType/Hinting/Opcodes.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 font = TRY(OpenType::Font::try_load_from_file(font_path));
|
||||
|
||||
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.argv[0]);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue