Files
pikasTech-HWLAB/skills/arm2d-skill/python/ttf2c.py
T
Codex Agent 712db9c597 feat: arm2d-skill directory seed for CaseRun agent workspace
Add B1 directory-type seed mechanism to AGENT_WORKSPACE_SEED_FILES:
- Extended agentWorkspaceFilesForRun with collectDirectorySeedFiles helper
- arm2d-skill (SKILL.md + references/ + python/) auto-injected to .agents/skills/arm2d-skill/
- Enables Code Agent to follow ARM-2D constraints and call asset scripts in case

Skill source: https://github.com/notLabyet/HWLabOA
2026-06-07 14:40:48 +08:00

586 lines
21 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# *************************************************************************************************
# Arm 2D project
# @file ttf2c.py
# @brief TrueTypeFont to C-array converter
#
# *************************************************************************************************
#
# * Copyright (C) 2025 ARM Limited or its affiliates. All rights reserved.
# *
# * SPDX-License-Identifier: Apache-2.0
# *
# * Licensed under the Apache License, Version 2.0 (the License); you may
# * not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * www.apache.org/licenses/LICENSE-2.0
# *
# * Unless required by applicable law or agreed to in writing, software
# * distributed under the License is distributed on an AS IS BASIS, WITHOUT
# * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# * See the License for the specific language governing permissions and
# * limitations under the License.
# */
import os
import sys
import argparse
import freetype
import numpy as np
import math
import binascii
import tempfile
c_head_string="""
/* generated with ttf2c.py (v2.4.1) */
#include "arm_2d_helper.h"
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunknown-warning-option"
# pragma clang diagnostic ignored "-Wreserved-identifier"
# pragma clang diagnostic ignored "-Wmissing-variable-declarations"
# pragma clang diagnostic ignored "-Wcast-qual"
# pragma clang diagnostic ignored "-Wmissing-declarations"
# pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end"
# pragma clang diagnostic ignored "-Wimplicit-int-conversion"
# pragma clang diagnostic ignored "-Wdeclaration-after-statement"
# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
# pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
# pragma clang diagnostic ignored "-Wpadded"
# pragma clang diagnostic ignored "-Wunused-parameter"
# pragma clang diagnostic ignored "-Wunused-variable"
# pragma clang diagnostic ignored "-Wsign-conversion"
#elif defined(__IS_COMPILER_GCC__)
# pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
# pragma GCC diagnostic ignored "-Wunused-variable"
#elif defined(__IS_COMPILER_ARM_COMPILER_5__)
# pragma diag_suppress=1296,1,64,177
#endif
typedef struct {
uint16_t hwIndex;
arm_2d_size_t tCharSize;
int16_t iAdvance;
int16_t iBearingX;
int16_t iBearingY;
uint8_t chCodeLength;
uint8_t chUTF8[4];
} const __ttf_char_descriptor_t;
"""
c_body_string="""
ARM_SECTION(\"arm2d.tile.c_tileUTF8{0}A{5}Mask\")
static const arm_2d_tile_t c_tileUTF8{0}A{5}Mask = {{
.tRegion = {{
.tSize = {{
.iWidth = {1},
.iHeight = {4},
}},
}},
.tInfo = {{
.bIsRoot = true,
.bHasEnforcedColour = true,
.tColourInfo = {{
.chScheme = ARM_2D_COLOUR_{5}BIT,
}},
}},
.pchBuffer = (uint8_t *)c_bmpUTF8{0}A{5}Font,
}};
#define __UTF8_FONT_SIZE_{5}__
static
IMPL_FONT_GET_CHAR_DESCRIPTOR(__utf8_a{5}_font_get_char_descriptor)
{{
assert(NULL != ptFont);
assert(NULL != ptDescriptor);
assert(NULL != pchCharCode);
arm_2d_user_font_t *ptThis = (arm_2d_user_font_t *)ptFont;
ARM_2D_UNUSED(ptThis);
memset(ptDescriptor, 0, sizeof(arm_2d_char_descriptor_t));
ptDescriptor->tileChar.ptParent = (arm_2d_tile_t *)&ptFont->tileFont;
ptDescriptor->tileChar.tInfo.bDerivedResource = true;
/* use the white space as the default char */
__ttf_char_descriptor_t *ptUTF8Char =
(__ttf_char_descriptor_t *)&c_tUTF8{0}LookUpTableA{5}[dimof(c_tUTF8{0}LookUpTableA{5})-1];
arm_foreach(__ttf_char_descriptor_t, c_tUTF8{0}LookUpTableA{5}, ptChar) {{
if (0 == strncmp( (char *)pchCharCode,
(char *)ptChar->chUTF8,
ptChar->chCodeLength)) {{
/* found the UTF8 char */
ptUTF8Char = ptChar;
break;
}}
}}
ptDescriptor->chCodeLength = ptUTF8Char->chCodeLength;
ptDescriptor->tileChar.tRegion.tSize = ptUTF8Char->tCharSize;
ptDescriptor->tileChar.tRegion.tLocation.iY = (int16_t)ptUTF8Char->hwIndex;
ptDescriptor->iAdvance = ptUTF8Char->iAdvance;
ptDescriptor->iBearingX= ptUTF8Char->iBearingX;
ptDescriptor->iBearingY= ptUTF8Char->iBearingY;
return ptDescriptor;
}}
ARM_SECTION(\"arm2d.asset.FONT.ARM_2D_FONT_{0}_A{5}\")
const
struct {{
implement(arm_2d_user_font_t);
arm_2d_char_idx_t tUTF8Table;
}} ARM_2D_FONT_{0}_A{5} = {{
.use_as__arm_2d_user_font_t = {{
.use_as__arm_2d_font_t = {{
.tileFont = impl_child_tile(
c_tileUTF8{0}A{5}Mask,
0, /* x offset */
0, /* y offset */
{1}, /* width */
{4} /* height */
),
.tCharSize = {{
.iWidth = {1},
.iHeight = {2},
}},
.nCount = {3}, //!< Character count
.fnGetCharDescriptor = &__utf8_a{5}_font_get_char_descriptor,
.fnDrawChar = &__arm_2d_lcd_text_default_a{5}_font_draw_char,
}},
.hwCount = 1,
.hwDefaultCharIndex = 1, /* tBlank */
}},
.tUTF8Table = {{
.hwCount = {3},
.hwOffset = 0,
}},
}};
#undef __UTF8_FONT_SIZE_{5}__
"""
c_body_string_ascii="""
ARM_SECTION(\"arm2d.tile.c_tileUTF8{0}A{5}Mask\")
static const arm_2d_tile_t c_tileUTF8{0}A{5}Mask = {{
.tRegion = {{
.tSize = {{
.iWidth = {1},
.iHeight = {4},
}},
}},
.tInfo = {{
.bIsRoot = true,
.bHasEnforcedColour = true,
.tColourInfo = {{
.chScheme = ARM_2D_COLOUR_{5}BIT,
}},
}},
.pchBuffer = (uint8_t *)c_bmpUTF8{0}A{5}Font,
}};
#define __UTF8_FONT_SIZE_{5}__
static
IMPL_FONT_GET_CHAR_DESCRIPTOR(__utf8_a{5}_font_get_char_descriptor)
{{
assert(NULL != ptFont);
assert(NULL != ptDescriptor);
assert(NULL != pchCharCode);
arm_2d_user_font_t *ptThis = (arm_2d_user_font_t *)ptFont;
ARM_2D_UNUSED(ptThis);
memset(ptDescriptor, 0, sizeof(arm_2d_char_descriptor_t));
ptDescriptor->tileChar.ptParent = (arm_2d_tile_t *)&ptFont->tileFont;
ptDescriptor->tileChar.tInfo.bDerivedResource = true;
/* use the white space as the default char */
__ttf_char_descriptor_t *ptUTF8Char = NULL;
if (pchCharCode[0] > 0x20 && pchCharCode[0] <= 0x7e) {{
ptUTF8Char = (__ttf_char_descriptor_t *)
&c_tUTF8{0}LookUpTableA{5}[
pchCharCode[0] - c_tUTF8{0}LookUpTableA{5}[0].chUTF8[0]];
}} else {{
/* use the white space as the default char */
ptUTF8Char = (__ttf_char_descriptor_t *)
&c_tUTF8{0}LookUpTableA{5}[dimof(c_tUTF8{0}LookUpTableA{5})-1];
}}
ptDescriptor->chCodeLength = ptUTF8Char->chCodeLength;
ptDescriptor->tileChar.tRegion.tSize = ptUTF8Char->tCharSize;
ptDescriptor->tileChar.tRegion.tLocation.iY = (int16_t)ptUTF8Char->hwIndex;
ptDescriptor->iAdvance = ptUTF8Char->iAdvance;
ptDescriptor->iBearingX= ptUTF8Char->iBearingX;
ptDescriptor->iBearingY= ptUTF8Char->iBearingY;
return ptDescriptor;
}}
ARM_SECTION(\"arm2d.asset.FONT.ARM_2D_FONT_{0}_A{5}\")
const
struct {{
implement(arm_2d_user_font_t);
arm_2d_char_idx_t tUTF8Table;
}} ARM_2D_FONT_{0}_A{5} = {{
.use_as__arm_2d_user_font_t = {{
.use_as__arm_2d_font_t = {{
.tileFont = impl_child_tile(
c_tileUTF8{0}A{5}Mask,
0, /* x offset */
0, /* y offset */
{1}, /* width */
{4} /* height */
),
.tCharSize = {{
.iWidth = {1},
.iHeight = {2},
}},
.nCount = {3}, //!< Character count
.fnGetCharDescriptor = &__utf8_a{5}_font_get_char_descriptor,
.fnDrawChar = &__arm_2d_lcd_text_default_a{5}_font_draw_char,
}},
.hwCount = 1,
.hwDefaultCharIndex = 1, /* tBlank */
}},
.tUTF8Table = {{
.hwCount = {3},
.hwOffset = 0,
}},
}};
#undef __UTF8_FONT_SIZE_{5}__
"""
c_tail_string="""
#if defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__IS_COMPILER_ARM_COMPILER_5__)
# pragma diag_warning=1296
#endif
"""
def generate_glyphs_data(input_file, text, pixel_size, font_bit_size, font_index):
face = freetype.Face(input_file, index=font_index)
face.set_pixel_sizes(0, pixel_size)
glyphs_data = []
current_index = 0
width_max = 0
height_max = 0
for char in set(text):
# Get the glyph bitmap
face.load_char(char)
bitmap = face.glyph.bitmap
utf8_encoding = char.encode('utf-8')
if len(bitmap.buffer) == 0:
continue
if list(utf8_encoding) == [0xef, 0xbb, 0xbf]:
continue
width_max = max(bitmap.width, width_max)
height_max = max(bitmap.rows, height_max)
width_max += 2
height_max += 2
for char in sorted(set(text)):
face.load_char(char)
bitmap = face.glyph.bitmap
utf8_encoding = char.encode('utf-8')
if len(bitmap.buffer) == 0:
continue
if list(utf8_encoding) == [0xef, 0xbb, 0xbf]:
continue
# update advance, bearing x and bearing y as we added the 1px boarder around each glyph.
advance_width = math.ceil(face.glyph.advance.x / 64.0) + 1
bearing_x = face.glyph.bitmap_left + 1
bearing_y = face.glyph.bitmap_top + 1
width = bitmap.width
height = bitmap.rows
pitch = bitmap.pitch
if bitmap.pixel_mode == freetype.FT_PIXEL_MODE_MONO:
buffer = np.frombuffer(bytes(bitmap.buffer), dtype=np.uint8)
bits_per_row = pitch * 8
bitmap_array = np.unpackbits(buffer).reshape((height, bits_per_row))[:, :width]
bitmap_array = bitmap_array.astype(np.uint8) * 255
elif bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY2:
buffer = np.frombuffer(bytes(bitmap.buffer), dtype=np.uint8)
bits_per_row = pitch * 4
bitmap_array = np.unpackbits(buffer).reshape((height, bits_per_row))[:, :width * 2]
bitmap_array = (bitmap_array.reshape((height, width * 2)) * 85).astype(np.uint8)
elif bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY4:
buffer = np.frombuffer(bytes(bitmap.buffer), dtype=np.uint8)
bits_per_row = pitch * 2
bitmap_array = np.unpackbits(buffer).reshape((height, bits_per_row))[:, :width * 4]
bitmap_array = (bitmap_array.reshape((height, width * 2)) * 17).astype(np.uint8)
elif bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY:
bitmap_array = np.array(bitmap.buffer, dtype=np.uint8).reshape((height, width))
else:
raise ValueError(f"Unsupported pixel mode: {bitmap.pixel_mode}")
continue
if width < width_max:
if (width_max - width) > 1:
padding = ((0, 0), (1, width_max - width - 1))
else:
padding = ((0, 0), (0, width_max - width))
bitmap_array = np.pad(bitmap_array, padding, 'constant')
padding = ((1, 1), (0, 0))
bitmap_array = np.pad(bitmap_array, padding, 'constant')
height += 2
char_index_advance = len(bitmap_array.flatten());
if font_bit_size == 4:
def RevBitQuadPerByte(byteArr):
return ((byteArr & 0x0f) << 4) | ((byteArr & 0xf0) >> 4)
# temporary array with 2x reduced width & pad
(r, c) = np.shape(bitmap_array)
tmp = np.empty((0, int((c+1)/2)), dtype=np.uint8)
for cur in bitmap_array:
bitsArr = np.unpackbits(cur.astype(np.uint8))
# generate indexes for MSB bit quadruplet every byte
idx = np.arange(0, np.size(bitsArr), 8)
idx = np.reshape(np.column_stack(
(np.column_stack((idx+0, idx+1)), np.column_stack((idx+2, idx+3)))),
(1,-1)),
# extraction + endianness conversion appended in temp array
# packbits is taking care of padding
tmp = np.vstack([tmp, RevBitQuadPerByte(np.packbits(bitsArr[idx]))])
bitmap_array = tmp
elif font_bit_size == 2:
def RevBitPairPerByte(byteArr):
return ((byteArr & 0x03) << 6) | ((byteArr & 0xc0) >> 6) | ((byteArr & 0x30) >> 2 ) | ((byteArr & 0x0c) << 2)
# temporary array with 4x reduced width & pad
(r, c) = np.shape(bitmap_array)
tmp = np.empty((0, int((c+3)/4)), dtype=np.uint8)
for cur in bitmap_array:
bitsArr = np.unpackbits(cur.astype(np.uint8))
# generate indexes for MSB bit pair every byte
idx = np.arange(0, np.size(bitsArr), 8)
idx = np.reshape(np.column_stack((idx+0, idx+1)), (1,-1))
# extraction + endianness conversion appended in temp array
tmp = np.vstack([tmp, RevBitPairPerByte(np.packbits(bitsArr[idx]))])
bitmap_array = tmp
elif font_bit_size == 1:
def RevBitPerByte(byteArr):
return ((byteArr & 0x01) << 7) | ((byteArr & 0x80) >> 7) | \
((byteArr & 0x02) << 5) | ((byteArr & 0x40) >> 5) | \
((byteArr & 0x04) << 3) | ((byteArr & 0x20) >> 3) | \
((byteArr & 0x08) << 1) | ((byteArr & 0x10) >> 1)
# temporary array with 8x reduced width & pad
(r, c) = np.shape(bitmap_array)
tmp = np.empty((0, int((c+7)/8)), dtype=np.uint8)
for cur in bitmap_array:
bitsArr = np.unpackbits(cur.astype(np.uint8))
# generate indexes for MSB bit every byte
idx = np.arange(0, np.size(bitsArr), 8)
# extraction + endianness conversion
tmp = np.vstack([tmp, RevBitPerByte(np.packbits(bitsArr[idx]))])
bitmap_array = tmp
char_mask_array = bitmap_array.flatten()
glyphs_data.append((char, char_mask_array, width + 2, height, current_index, advance_width, bearing_x, bearing_y, utf8_encoding))
current_index += char_index_advance
return glyphs_data, width_max, height_max
def utf8_to_c_array(utf8_bytes):
return '{' + ', '.join([f'0x{byte:02x}' for byte in utf8_bytes]) + '}'
def write_c_code(glyphs_data, output_file, name, char_max_width, char_max_height, font_bit_size, ascii_mode):
with open(output_file, "a") as f:
print("ARM_SECTION(\"arm2d.asset.FONT.c_bmpUTF8{0}A{1}Font\")\nconst uint8_t c_bmpUTF8{0}A{1}Font[] = {{\n"
.format(name, font_bit_size),
file=f)
for char, data, width, height, index, advance_width, bearing_x, bearing_y, utf8_encoding in glyphs_data:
utf8_c_array = utf8_to_c_array(utf8_encoding)
f.write(f"\n // Glyph for character {utf8_c_array}\n")
hex_str = binascii.hexlify(data).decode()
for i in range(0, len(hex_str), char_max_width*2):
line = hex_str[i:i+char_max_width*2]
spaced_line = ' '.join(f"0x{line[j:j+2]}," for j in range(0, len(line), 2))
f.write(" ");
f.write(spaced_line)
f.write("\n");
f.write(" // a white space\n ");
f.write("0x00, " * (char_max_width * char_max_height))
f.write("\n};\n\n")
print("ARM_SECTION(\"arm2d.asset.FONT.c_tUTF8{0}LookUpTableA{1}\")\n const __ttf_char_descriptor_t c_tUTF8{0}LookUpTableA{1}[{2}] = {{\n"
.format(name, font_bit_size, len(glyphs_data) + 1), file=f)
last_index = 0;
last_advance = 0;
last_height = 0;
for char, data, width, height, index, advance_width, bearing_x, bearing_y, utf8_encoding in glyphs_data:
utf8_c_array = utf8_to_c_array(utf8_encoding)
last_index = index
last_advance = advance_width
last_height = height;
f.write(f" {{ {round(index / char_max_width)}, {{ {width}, {height}, }}, {advance_width}, {bearing_x}, {bearing_y}, {len(utf8_encoding)}, {utf8_c_array} }},\n")
last_index += char_max_width * last_height
f.write(f" {{ {round(last_index / char_max_width)}, {{ {char_max_width}, {char_max_height}, }}, {round(char_max_width / 2)}, {0}, {char_max_height}, 1, {{0x20}} }},\n")
last_index += char_max_width * char_max_height
f.write("};\n")
if ascii_mode == True:
print(c_body_string_ascii.format( name,
char_max_width,
char_max_height,
len(glyphs_data),
round(last_index / char_max_width)+1,
font_bit_size), file=f)
else:
print(c_body_string.format( name,
char_max_width,
char_max_height,
len(glyphs_data),
round(last_index / char_max_width)+1,
font_bit_size), file=f)
def main():
parser = argparse.ArgumentParser(description='TrueTypeFont to C array converter (v2.4.1)')
parser.add_argument("-i", "--input", type=str, help="Path to the TTF file", required=True)
parser.add_argument("--index", type=int, help="The Font Index in a TTC file", required=False, default=0)
parser.add_argument("-t", "--text", type=str, help="Path to the text file", required=False)
parser.add_argument("-n", "--name", type=str, help="The customized UTF8 font name", required=False, default="UTF8")
parser.add_argument("-o", "--output", type=str, help="Path to the output C file", required=True)
parser.add_argument("-p", "--pixelsize",type=int, help="Font size in pixels", required=False, default=32)
parser.add_argument("-s", "--fontbitsize",type=int, help="font bit size (1,2,4,8)", required=False, default=0)
if len(sys.argv)==1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
if args.fontbitsize not in [1, 2, 4, 8, 0]:
print(f'Invalid alpha size={args.fontbitsize}')
sys.exit(1)
ascii_mode = False
if args.text == None or args.text == "" :
ascii_mode = True
fd, args.text = tempfile.mkstemp()
with os.fdopen(fd, 'w') as temptextfile:
print("!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", file=temptextfile, end='')
with open(args.output, "w") as outputfile:
print(c_head_string, file=outputfile)
if args.fontbitsize in [1, 2, 4, 8]:
with open(args.text, 'r', encoding='utf-8') as f:
text = f.read()
glyphs_data, char_max_width, char_max_height = generate_glyphs_data(args.input, text, args.pixelsize, args.fontbitsize, args.index)
write_c_code(glyphs_data, args.output, args.name, char_max_width, char_max_height, args.fontbitsize, ascii_mode)
else:
with open(args.text, 'r', encoding='utf-8') as f:
text = f.read()
glyphs_data, char_max_width, char_max_height = generate_glyphs_data(args.input, text, args.pixelsize, 1, args.index)
write_c_code(glyphs_data, args.output, args.name, char_max_width, char_max_height, 1, ascii_mode)
with open(args.text, 'r', encoding='utf-8') as f:
text = f.read()
glyphs_data, char_max_width, char_max_height = generate_glyphs_data(args.input, text, args.pixelsize, 2, args.index)
write_c_code(glyphs_data, args.output, args.name, char_max_width, char_max_height, 2, ascii_mode)
with open(args.text, 'r', encoding='utf-8') as f:
text = f.read()
glyphs_data, char_max_width, char_max_height = generate_glyphs_data(args.input, text, args.pixelsize, 4, args.index)
write_c_code(glyphs_data, args.output, args.name, char_max_width, char_max_height, 4, ascii_mode)
with open(args.text, 'r', encoding='utf-8') as f:
text = f.read()
glyphs_data, char_max_width, char_max_height = generate_glyphs_data(args.input, text, args.pixelsize, 8, args.index)
write_c_code(glyphs_data, args.output, args.name, char_max_width, char_max_height, 8, ascii_mode)
with open(args.output, "a") as outputfile:
print(c_tail_string, file=outputfile)
if __name__ == '__main__':
main()