@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
FONT_PATH = REPO / "package/assets/fonts/petaluma/PetalumaScript.otf"
|
||||
OUTPUT_PATH = REPO / "src/vamp_style/font.cpp"
|
||||
FONT_SIZE = 32
|
||||
FONT_HEIGHT = 36
|
||||
FONT_MAX_WIDTH = 28
|
||||
FIRST_CODEPOINT = 32
|
||||
LAST_CODEPOINT = 126
|
||||
|
||||
|
||||
def cpp_char_literal(value: str) -> str:
|
||||
if value == "'":
|
||||
return "'\\''"
|
||||
if value == "\\":
|
||||
return "'\\\\'"
|
||||
return f"'{value}'"
|
||||
|
||||
|
||||
def render_glyph(font: ImageFont.FreeTypeFont, value: str, y_offset: int) -> tuple[list[int], int, int]:
|
||||
image = Image.new("L", (FONT_MAX_WIDTH, FONT_HEIGHT), 0)
|
||||
draw = ImageDraw.Draw(image)
|
||||
bbox = font.getbbox(value)
|
||||
x = max(0, -bbox[0])
|
||||
draw.text((x, y_offset), value, font=font, fill=255)
|
||||
|
||||
pixels = [min(15, max(0, round(pixel / 17))) for pixel in image.get_flattened_data()]
|
||||
visible = image.getbbox()
|
||||
width = 0 if visible is None else min(FONT_MAX_WIDTH, visible[2])
|
||||
advance = min(255, max(0, round(font.getlength(value))))
|
||||
return pixels, width, advance
|
||||
|
||||
|
||||
def render_alpha_rows(values: list[int]) -> list[str]:
|
||||
lines = []
|
||||
for row in range(FONT_HEIGHT):
|
||||
start = row * FONT_MAX_WIDTH
|
||||
row_values = values[start : start + FONT_MAX_WIDTH]
|
||||
lines.append(" " + ", ".join(f"0x{value:02x}" for value in row_values) + ",")
|
||||
return lines
|
||||
|
||||
|
||||
def pair_adjustments(font: ImageFont.FreeTypeFont) -> list[tuple[int, int]]:
|
||||
chars = [chr(value) for value in range(FIRST_CODEPOINT, LAST_CODEPOINT + 1)]
|
||||
adjustments = []
|
||||
for left in chars:
|
||||
left_width = font.getlength(left)
|
||||
for right in chars:
|
||||
adjustment = round(font.getlength(left + right) - left_width - font.getlength(right))
|
||||
if adjustment == 0:
|
||||
continue
|
||||
key = (ord(left) << 8) | ord(right)
|
||||
adjustments.append((key, int(adjustment)))
|
||||
return sorted(adjustments)
|
||||
|
||||
|
||||
def generate() -> str:
|
||||
font = ImageFont.truetype(str(FONT_PATH), FONT_SIZE)
|
||||
min_top = min(font.getbbox(chr(value))[1] for value in range(FIRST_CODEPOINT, LAST_CODEPOINT + 1))
|
||||
y_offset = max(0, -min_top)
|
||||
adjustments = pair_adjustments(font)
|
||||
|
||||
lines = [
|
||||
"#include <vamp_style/font.hpp>",
|
||||
"",
|
||||
"#include <algorithm>",
|
||||
"#include <array>",
|
||||
"#include <cstdint>",
|
||||
"#include <string_view>",
|
||||
"",
|
||||
"namespace {",
|
||||
"",
|
||||
"struct pair_adjustment {",
|
||||
" std::uint16_t key{};",
|
||||
" std::int8_t adjustment{};",
|
||||
"};",
|
||||
"",
|
||||
f"constexpr auto pair_adjustments = std::array<pair_adjustment, {len(adjustments)}>{{{{",
|
||||
]
|
||||
for key, adjustment in adjustments:
|
||||
lines.append(f" {{0x{key:04x}, {adjustment}}},")
|
||||
lines.extend(
|
||||
[
|
||||
"}};",
|
||||
"",
|
||||
"auto pair_key(std::char_t left, std::char_t right) -> std::uint16_t {",
|
||||
" auto l = static_cast<std::uint16_t>(static_cast<std::uint8_t>(left));",
|
||||
" auto r = static_cast<std::uint16_t>(static_cast<std::uint8_t>(right));",
|
||||
" return static_cast<std::uint16_t>((l << 8U) | r);",
|
||||
"}",
|
||||
"",
|
||||
"auto empty_glyph() -> vamp_style::font_glyph {",
|
||||
" return {",
|
||||
" .alpha = {},",
|
||||
" .width = 0,",
|
||||
" .advance = 8,",
|
||||
" };",
|
||||
"}",
|
||||
"",
|
||||
"}",
|
||||
"",
|
||||
"auto vamp_style::ui_font_glyph(std::char_t value) -> vamp_style::font_glyph {",
|
||||
" switch (value) {",
|
||||
]
|
||||
)
|
||||
|
||||
for codepoint in range(FIRST_CODEPOINT, LAST_CODEPOINT + 1):
|
||||
value = chr(codepoint)
|
||||
alpha, width, advance = render_glyph(font, value, y_offset)
|
||||
lines.extend(
|
||||
[
|
||||
f" case {cpp_char_literal(value)}:",
|
||||
" return {",
|
||||
" .alpha = {",
|
||||
]
|
||||
)
|
||||
lines.extend(render_alpha_rows(alpha))
|
||||
lines.extend(
|
||||
[
|
||||
" },",
|
||||
f" .width = {width},",
|
||||
f" .advance = {advance},",
|
||||
" };",
|
||||
]
|
||||
)
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
" default:",
|
||||
" return empty_glyph();",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"auto vamp_style::ui_font_pair_adjust(std::char_t left, std::char_t right) -> std::int32_t {",
|
||||
" auto key = pair_key(left, right);",
|
||||
" auto found = std::lower_bound(pair_adjustments.begin(), pair_adjustments.end(), key, [](pair_adjustment const& item, std::uint16_t value) {",
|
||||
" return item.key < value;",
|
||||
" });",
|
||||
" if (found == pair_adjustments.end() || found->key != key) {",
|
||||
" return 0;",
|
||||
" }",
|
||||
" return found->adjustment;",
|
||||
"}",
|
||||
"",
|
||||
"auto vamp_style::ui_text_width(std::string_view text, std::int32_t scale) -> std::int32_t {",
|
||||
" if (text.empty() || scale <= 0) {",
|
||||
" return 0;",
|
||||
" }",
|
||||
" auto width = std::int32_t{};",
|
||||
" auto previous = std::char_t{};",
|
||||
" for (auto ch : text) {",
|
||||
" if (previous != std::char_t{}) {",
|
||||
" width += vamp_style::ui_font_pair_adjust(previous, ch) * scale;",
|
||||
" }",
|
||||
" width += static_cast<std::int32_t>(vamp_style::ui_font_glyph(ch).advance) * scale;",
|
||||
" previous = ch;",
|
||||
" }",
|
||||
" return std::max(std::int32_t{}, width);",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
generated = generate()
|
||||
if args.check:
|
||||
current = OUTPUT_PATH.read_text()
|
||||
if current == generated:
|
||||
print("pico font is up to date")
|
||||
return 0
|
||||
diff = difflib.unified_diff(
|
||||
current.splitlines(),
|
||||
generated.splitlines(),
|
||||
fromfile=str(OUTPUT_PATH),
|
||||
tofile=f"{OUTPUT_PATH} (generated)",
|
||||
lineterm="",
|
||||
)
|
||||
print("\n".join(diff))
|
||||
return 1
|
||||
|
||||
OUTPUT_PATH.write_text(generated)
|
||||
print(f"generated {OUTPUT_PATH}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user