-
-
Notifications
You must be signed in to change notification settings - Fork 25
Font Support
Jinny You edited this page Mar 23, 2026
·
7 revisions
ThorVG Web adopts default font, enabling essential glyph rendering even in environments where no font is explicitly loaded.
- DM Sans : https://fonts.google.com/specimen/DM+Sans
- LICENSE : https://fonts.google.com/specimen/DM+Sans/license
- Included Glyphs
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 +-=().
A tool for embedding the default font into the WASM binary. Converts a TTF file into a C++ header with LZSS-compressed data.
Show code
import sys
def lzss_encode(data):
if not data:
return []
window_size = 4095
min_match = 3
max_match = 18
output = []
pos = 0
buffer = bytearray(data)
while pos < len(buffer):
flags = 0
flag_pos = len(output)
output.append(0)
for flag_bit in range(8):
if pos >= len(buffer):
break
best_length = 0
best_offset = 0
search_start = max(0, pos - window_size)
for i in range(search_start, pos):
length = 0
while (pos + length < len(buffer) and
length < max_match and
buffer[i + length] == buffer[pos + length]):
length += 1
if length > best_length:
best_length = length
best_offset = pos - i
if best_length >= min_match:
offset_high = (best_offset >> 4) & 0xFF
offset_low = ((best_offset & 0x0F) << 4) | ((best_length - min_match) & 0x0F)
output.append(offset_high)
output.append(offset_low)
pos += best_length
else:
flags |= (1 << flag_bit)
output.append(buffer[pos])
pos += 1
output[flag_pos] = flags
return output
def convert_to_hex(input_file, output_file, array_name="embedded_font"):
with open(input_file, "rb") as f:
data = f.read()
compressed_data = lzss_encode(data)
hex_array = ", ".join(f"0x{b:02X}" for b in compressed_data)
compressed_size = len(compressed_data)
original_size = len(data)
cpp_content = f"""#pragma once
#include <cstddef>
constexpr unsigned char {array_name}_compressed[] = {{
{hex_array}
}};
constexpr size_t {array_name}_compressed_size = {compressed_size};
constexpr size_t {array_name}_original_size = {original_size};
inline void decompress_{array_name}(unsigned char* output) {{
const unsigned char* input = {array_name}_compressed;
size_t input_pos = 0;
size_t output_pos = 0;
while (input_pos < {array_name}_compressed_size && output_pos < {array_name}_original_size) {{
unsigned char flags = input[input_pos++];
for (int bit = 0; bit < 8 && output_pos < {array_name}_original_size; bit++) {{
if (flags & (1 << bit)) {{
output[output_pos++] = input[input_pos++];
}} else {{
unsigned short offset = input[input_pos++] << 4;
unsigned char length_offset = input[input_pos++];
offset |= length_offset >> 4;
unsigned char length = (length_offset & 0x0F) + 3;
size_t copy_pos = output_pos - offset;
for (int i = 0; i < length; i++) {{
output[output_pos++] = output[copy_pos + i];
}}
}}
}}
}}
}}
"""
with open(output_file, "w") as f:
f.write(cpp_content)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: font_to_hex.py <input_font_file> <output_cpp_file>")
sys.exit(1)
input_font = sys.argv[1]
output_cpp = sys.argv[2]
convert_to_hex(input_font, output_cpp)# Extract glyphs (fonttools)
$ pyftsubset DMSans-Regular.ttf \
--text="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 +-=()." \
--output-file=DMSans-subset.ttf
# Convert to code
$ python3 font_to_hex.py <input.ttf> <output.h>
# Replace wasm/common/tvgWasmDefaultFont.h with the generated header data.