Serenity Operating System
1/*
2 * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Format.h>
8#include <AK/Utf8View.h>
9#include <LibCore/ArgsParser.h>
10#include <LibGfx/Font/OpenType/Font.h>
11#include <LibGfx/Font/OpenType/Hinting/Opcodes.h>
12#include <LibMain/Main.h>
13
14using namespace OpenType::Hinting;
15
16#define YELLOW "\e[33m"
17#define CYAN "\e[36m"
18#define PURPLE "\e[95m"
19#define GREEN "\e[92m"
20#define RESET "\e[0m"
21#define GRAY "\e[90m"
22
23struct InstructionPrinter : InstructionHandler {
24 InstructionPrinter(bool enable_highlighting)
25 : m_enable_highlighting(enable_highlighting)
26 {
27 }
28
29 void before_operation(InstructionStream& stream, Opcode opcode) override
30 {
31 if (opcode == Opcode::FDEF && stream.current_position() > 1 && m_indent_level == 1)
32 outln();
33 switch (opcode) {
34 case Opcode::EIF:
35 case Opcode::ELSE:
36 case Opcode::ENDF:
37 m_indent_level--;
38 break;
39 default:
40 break;
41 }
42 auto digits = int(AK::log10(float(stream.length()))) + 1;
43 if (m_enable_highlighting)
44 out(GRAY);
45 out("{:0{}}:", stream.current_position() - 1, digits);
46 if (m_enable_highlighting)
47 out(RESET);
48 out("{:{}}", ""sv, m_indent_level * 2);
49 }
50
51 void after_operation(InstructionStream&, Opcode opcode) override
52 {
53 switch (opcode) {
54 case Opcode::IF:
55 case Opcode::ELSE:
56 case Opcode::IDEF:
57 case Opcode::FDEF:
58 m_indent_level++;
59 break;
60 default:
61 break;
62 }
63 }
64
65 void print_number(u16 value)
66 {
67 if (m_enable_highlighting)
68 return out(GREEN " {}" RESET, value);
69 return out(" {}", value);
70 }
71
72 void print_bytes(ReadonlyBytes bytes, bool first = true)
73 {
74 for (auto value : bytes) {
75 if (!first)
76 out(",");
77 print_number(value);
78 first = false;
79 }
80 }
81
82 void print_words(ReadonlyBytes bytes, bool first = true)
83 {
84 for (size_t i = 0; i < bytes.size(); i += 2) {
85 if (!first)
86 out(",");
87 print_number(bytes[i] << 8 | bytes[i + 1]);
88 first = false;
89 }
90 }
91
92 void default_handler(Context context) override
93 {
94 auto instruction = context.instruction();
95 auto name = opcode_mnemonic(instruction.opcode());
96 if (m_enable_highlighting)
97 out(YELLOW);
98 out(name);
99 if (m_enable_highlighting)
100 out(CYAN);
101 out("[");
102 if (m_enable_highlighting)
103 out(PURPLE);
104 if (instruction.flag_bits() > 0)
105 out("{:0{}b}", to_underlying(instruction.opcode()) & ((1 << instruction.flag_bits()) - 1), instruction.flag_bits());
106 if (m_enable_highlighting)
107 out(CYAN);
108 out("]");
109 if (m_enable_highlighting)
110 out(RESET);
111 switch (instruction.opcode()) {
112 case Opcode::NPUSHB... Opcode::NPUSHB_MAX:
113 print_number(instruction.values().size());
114 print_bytes(instruction.values(), false);
115 break;
116 case Opcode::NPUSHW... Opcode::NPUSHW_MAX:
117 print_number(instruction.values().size() / 2);
118 print_words(instruction.values(), false);
119 break;
120 case Opcode::PUSHB... Opcode::PUSHB_MAX:
121 print_bytes(instruction.values());
122 break;
123 case Opcode::PUSHW... Opcode::PUSHW_MAX:
124 print_words(instruction.values());
125 break;
126 default:
127 break;
128 }
129 outln();
130 }
131
132private:
133 bool m_enable_highlighting;
134 u32 m_indent_level { 1 };
135};
136
137static bool s_disassembly_attempted = false;
138
139static void print_disassembly(StringView name, Optional<ReadonlyBytes> program, bool enable_highlighting, u32 code_point = 0)
140{
141 s_disassembly_attempted = true;
142 if (!program.has_value()) {
143 out(name, code_point);
144 outln(": not found");
145 return;
146 }
147 out(name, code_point);
148 outln(": ({} bytes)\n", program->size());
149 InstructionPrinter printer { enable_highlighting };
150 InstructionStream stream { printer, *program };
151 while (!stream.at_end())
152 stream.process_next_instruction();
153}
154
155ErrorOr<int> serenity_main(Main::Arguments arguments)
156{
157 Core::ArgsParser args_parser;
158
159 StringView font_path;
160 bool no_color = false;
161 bool dump_font_program = false;
162 bool dump_prep_program = false;
163 StringView text;
164 args_parser.add_positional_argument(font_path, "Path to font", "FILE");
165 args_parser.add_option(dump_font_program, "Disassemble font program (fpgm table)", "disasm-fpgm", 'f');
166 args_parser.add_option(dump_prep_program, "Disassemble CVT program (prep table)", "disasm-prep", 'p');
167 args_parser.add_option(text, "Disassemble glyph programs", "disasm-glyphs", 'g', "text");
168 args_parser.add_option(no_color, "Disable syntax highlighting", "no-color", 'n');
169 args_parser.parse(arguments);
170
171 auto font = TRY(OpenType::Font::try_load_from_file(font_path));
172
173 if (dump_font_program)
174 print_disassembly("Font program"sv, font->font_program(), !no_color);
175 if (dump_prep_program) {
176 if (dump_font_program)
177 outln();
178 print_disassembly("CVT program"sv, font->control_value_program(), !no_color);
179 }
180 if (!text.is_empty()) {
181 Utf8View utf8_view { text };
182 bool first = !(dump_font_program || dump_prep_program);
183 for (u32 code_point : utf8_view) {
184 if (!first)
185 outln();
186 auto glyph_id = font->glyph_id_for_code_point(code_point);
187 print_disassembly("Glyph program for codepoint {}"sv, font->glyph_program(glyph_id), !no_color, code_point);
188 first = false;
189 }
190 }
191
192 if (!s_disassembly_attempted) {
193 args_parser.print_usage(stderr, arguments.strings[0]);
194 return 1;
195 }
196 return 0;
197}