diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index e9c2f4c01c..938bf7c47c 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -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) diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index 3cf14b4a39..f85f84a7c6 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -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) diff --git a/Userland/Utilities/ttfdisasm.cpp b/Userland/Utilities/ttfdisasm.cpp new file mode 100644 index 0000000000..817494ae61 --- /dev/null +++ b/Userland/Utilities/ttfdisasm.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +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 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 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; +}