MeowOS for HiBy R3 Pro II — 公共发布版本(历史已重置)
• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
This commit is contained in:
commit
245bee2946
136 files changed
+32730
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* meow_font_demo.c — MeowOS 字体子系统主机/真机验证 demo。
|
||||
*
|
||||
* 用法:
|
||||
* host(主机侧验证):meow_font_demo --sim demo.raw
|
||||
* 渲染中文串到内存 480x720 RGB565 缓冲并写入 demo.raw,主机再用 raw2png.py 转 PNG。
|
||||
* 真机:meow_font_demo --to-fb (写 /dev/fb0,默认走非显示后备缓冲并暂存,谨慎用 --sim)
|
||||
*
|
||||
* 通过 meow_font.h 的公开 API 绘制,验证字形/字号/advance/描边;把 width_text 打印到 stdout。
|
||||
*/
|
||||
#include "meow_font.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define FB_W 480
|
||||
#define FB_H 720
|
||||
|
||||
static uint16_t *g_fb;
|
||||
|
||||
static inline uint16_t rgb565(int r, int g, int b) {
|
||||
return (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
|
||||
}
|
||||
|
||||
static void fill_bg(uint16_t c) {
|
||||
for (size_t i = 0; i < (size_t)FB_W * FB_H; i++) g_fb[i] = c;
|
||||
}
|
||||
|
||||
static void fill_rect(int x, int y, int w, int h, uint16_t c) {
|
||||
for (int yy = y; yy < y + h; yy++) {
|
||||
for (int xx = x; xx < x + w; xx++) {
|
||||
if (xx >= 0 && xx < FB_W && yy >= 0 && yy < FB_H) g_fb[(size_t)yy * FB_W + xx] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 设备侧写文件 */
|
||||
static int write_raw(const char *path) {
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) { fprintf(stderr, "cannot open %s\n", path); return -1; }
|
||||
size_t n = fwrite(g_fb, 2, (size_t)FB_W * FB_H, f);
|
||||
fclose(f);
|
||||
return (n == (size_t)FB_W * FB_H) ? 0 : -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int to_fb = 0;
|
||||
const char *rawpath = "font/demo/meow_demo.raw";
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--to-fb")) to_fb = 1;
|
||||
else if (!strcmp(argv[i], "--sim")) rawpath = (i + 1 < argc) ? argv[++i] : rawpath;
|
||||
else { fprintf(stderr, "usage: %s [--sim out.raw | --to-fb]\n", argv[0]); return 2; }
|
||||
}
|
||||
|
||||
g_fb = (uint16_t *)calloc((size_t)FB_W * FB_H, 2);
|
||||
if (!g_fb) return 3;
|
||||
|
||||
meow_font_set_fb(FB_W, FB_H, FB_W * 2);
|
||||
|
||||
const char *png = "font/atlas/font.png";
|
||||
const char *idx = "font/atlas/font.idx";
|
||||
int rc = meow_font_load(png, idx);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "meow_font_load failed rc=%d\n", rc);
|
||||
return 4;
|
||||
}
|
||||
printf("loaded %s + %s : has_size(28)=%d has_size(20)=%d\n",
|
||||
png, idx, meow_font_has_size(28), meow_font_has_size(20));
|
||||
|
||||
/* ---- 度量核对 ---- */
|
||||
printf("width_text('MeowOS 主屏',28)=%d line_h28=%d asc28=%d\n",
|
||||
meow_font_width_text("MeowOS 主屏", 28), meow_font_line_height(28), meow_font_ascent(28));
|
||||
printf("width_text('返回播放器需重启',20)=%d line_h20=%d\n",
|
||||
meow_font_width_text("返回播放器需重启", 20), meow_font_line_height(20));
|
||||
|
||||
/* ---- 布局 --- */
|
||||
uint16_t cbg = rgb565(0x0c, 0x0e, 0x12);
|
||||
uint16_t ctit = rgb565(0xff, 0xb0, 0x30); /* 主题橙 */
|
||||
uint16_t ctext = rgb565(0xf2, 0xf2, 0xf2); /* 近白 */
|
||||
uint16_t c_dim = rgb565(0x7a, 0x82, 0x8c); /* 灰 */
|
||||
uint16_t cselbg = rgb565(0x1e, 0x2a, 0x3a); /* 选中背景 */
|
||||
|
||||
fill_bg(cbg);
|
||||
|
||||
/* 顶栏:标题 + 状态(版本) */
|
||||
meow_font_drawtext(g_fb, 16, 10, "MeowOS 主屏", ctit, 28);
|
||||
meow_font_drawtext(g_fb, 400, 16, "0.6.9", c_dim, 20);
|
||||
|
||||
/* 分隔线 */
|
||||
fill_rect(16, 56, FB_W - 32, 1, rgb565(0x2a, 0x30, 0x38));
|
||||
|
||||
/* 应用/功能列表(正文 20px) */
|
||||
struct item { const char *s; int sel; } items[] = {
|
||||
{"本地音乐", 0}, {"在线电台", 0}, {"返回播放器需重启", 1},
|
||||
{"设置", 0}, {"关闭背光", 0}, {"关机", 0},
|
||||
};
|
||||
int y = 78;
|
||||
for (unsigned i = 0; i < sizeof(items) / sizeof(items[0]); i++) {
|
||||
int lh = meow_font_line_height(20);
|
||||
if (items[i].sel) {
|
||||
fill_rect(12, y - 4, FB_W - 24, lh + 6, cselbg);
|
||||
meow_font_drawtext_outline(g_fb, 24, y, items[i].s, ctext, ctit, 1, 20);
|
||||
} else {
|
||||
meow_font_drawtext(g_fb, 24, y, items[i].s, items[i].sel ? ctit : ctext, 20);
|
||||
}
|
||||
y += lh + 16;
|
||||
}
|
||||
|
||||
/* 底栏提示 */
|
||||
meow_font_drawtext(g_fb, 16, FB_H - 46, "按住电源键 3 秒返回", c_dim, 20);
|
||||
|
||||
if (to_fb) {
|
||||
fprintf(stderr, "--to-fb 暂未在本机关联(真机用);先用 --sim 验证\n");
|
||||
}
|
||||
if (write_raw(rawpath) != 0) {
|
||||
fprintf(stderr, "write_raw failed\n");
|
||||
return 5;
|
||||
}
|
||||
printf("wrote %s\n", rawpath);
|
||||
meow_font_unload();
|
||||
free(g_fb);
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""raw2png.py — 将 meow_font_demo 产出的 480x720 RGB565(raw, little-endian uint16) 转成 PNG。
|
||||
|
||||
用法: raw2png.py <in.raw> <out.png>
|
||||
"""
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
W, H = 480, 720
|
||||
|
||||
|
||||
def main():
|
||||
raw = open(sys.argv[1], "rb").read()
|
||||
assert len(raw) == W * H * 2, f"expect {W * H * 2}, got {len(raw)}"
|
||||
out = sys.argv[2]
|
||||
img = Image.new("RGBA", (W, H), (0, 0, 0, 255))
|
||||
px = img.load()
|
||||
for y in range(H):
|
||||
base = y * W * 2
|
||||
for x in range(W):
|
||||
o = base + x * 2
|
||||
v = raw[o] | (raw[o + 1] << 8) # LE uint16
|
||||
r = ((v >> 11) & 31) * 255 // 31
|
||||
g = ((v >> 5) & 63) * 255 // 63
|
||||
b = (v & 31) * 255 // 31
|
||||
px[x, y] = (r, g, b, 255)
|
||||
img.save(out)
|
||||
print(f"saved {out} ({W}x{H})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
gen_font.py — MeowOS 预渲染字库生成器(主机侧)· v2:像素进 PNG、索引进 idx。
|
||||
|
||||
产出(单份存储,设备与主机共用同一套字节):
|
||||
<out>/font.png 灰度 PNG(bit depth 4|8),含全部字号的面,每面一个子矩形
|
||||
<out>/font.idx 二进制索引:头部 + 每面元数据 + 每字符 16B 记录
|
||||
|
||||
设计要点:
|
||||
* 像素只存一次(font.png);idx 只做索引。设备端用 zlib inflate + 自写 PNG 反滤波解码,
|
||||
无需 libpng(目标根文件系统无 libpng,仅有 zlib)。
|
||||
* 位深默认 4(16 级 alpha,仍抗锯齿)以把多张高密度汉字面压到 ≤2MB 预算内;8-bit 会超。
|
||||
--bitdepth 8 可出 8-bit(建议单字号)。
|
||||
* 绘制模型:drawtext(fb,x,y,str,color,size),(x,y)=行首「升线顶」锚点;baseline=y+ascent;
|
||||
每字形 blit 于 (x+pen+dx, y+dy),pen 累加 advance。
|
||||
|
||||
idx 二进制布局(little-endian, version=2):
|
||||
Header(16B):
|
||||
+0 u8[4] magic='MFNI'
|
||||
+4 u16 version=2
|
||||
+6 u8 bit_depth (4|8)
|
||||
+7 u8 face_count
|
||||
+8 u32 png_w
|
||||
+12 u32 png_h
|
||||
FaceHeader ×face_count (20B each):
|
||||
+0 u16 px_size
|
||||
+2 u16 plane_x
|
||||
+4 u16 plane_y (PNG 像素内面左上角)
|
||||
+6 u16 plane_w
|
||||
+8 u16 plane_h
|
||||
+10 i16 ascent
|
||||
+12 i16 descent (负值)
|
||||
+14 u16 glyph_count
|
||||
+16 u32 glyph_offset (文件内本面 glyph 表偏移)
|
||||
Glyph 表(每面按 codepoint 升序,16B each):
|
||||
+0 u32 codepoint
|
||||
+4 u16 x (PNG 像素左上角,已含 offset)
|
||||
+6 u16 y
|
||||
+8 u16 w
|
||||
+10 u16 h
|
||||
+12 u16 advance
|
||||
+14 i8 dx
|
||||
+15 u8 dy
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
MAGIC = b"MFNI"
|
||||
VER = 2
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# PNG 编码(灰度, bit depth 4|8, 全部扫描行 filter=0;规范解码器可读)
|
||||
# --------------------------------------------------------------------------
|
||||
def _png_chunk(ctype, data):
|
||||
c = ctype + data
|
||||
return struct.pack(">I", len(data)) + c + struct.pack(">I", zlib.crc32(c) & 0xFFFFFFFF)
|
||||
|
||||
|
||||
def gray_png_encode(width, height, depth, packed):
|
||||
rowbytes = (width * depth + 7) // 8
|
||||
ihdr = struct.pack(">IIBBBBB", width, height, depth, 0, 0, 0, 0) # gray, compression0 filter0 interlace0
|
||||
raw = bytearray()
|
||||
for y in range(height):
|
||||
raw.append(0) # filter type 0 (None)
|
||||
raw += packed[y * rowbytes:(y + 1) * rowbytes]
|
||||
idat = zlib.compress(bytes(raw), 9)
|
||||
out = b"\x89PNG\r\n\x1a\n"
|
||||
out += _png_chunk(b"IHDR", ihdr)
|
||||
out += _png_chunk(b"IDAT", idat)
|
||||
out += _png_chunk(b"IEND", b"")
|
||||
return out
|
||||
|
||||
|
||||
def pack_plane_depth(plane_l8, width, height, depth):
|
||||
"""把 L8 行主序 plane 转为指定 bit depth 的打包字节。"""
|
||||
if depth == 8:
|
||||
return bytes(plane_l8)
|
||||
assert depth == 4
|
||||
rowbytes = (width + 1) // 2
|
||||
out = bytearray()
|
||||
for y in range(height):
|
||||
base = y * width
|
||||
row = bytearray(rowbytes)
|
||||
for x in range(0, width, 2):
|
||||
a = plane_l8[base + x]
|
||||
b = plane_l8[base + x + 1] if x + 1 < width else 0
|
||||
row[x // 2] = ((a >> 4) << 4) | (b >> 4)
|
||||
out += row
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 字符集
|
||||
# --------------------------------------------------------------------------
|
||||
def build_charset(extra=""):
|
||||
cps = []
|
||||
def add(cp):
|
||||
if cp not in cps:
|
||||
cps.append(cp)
|
||||
for c in range(0x20, 0x7F):
|
||||
add(c)
|
||||
for hi in range(0xB0, 0xD8):
|
||||
for lo in range(0xA1, 0xFF):
|
||||
try:
|
||||
add(ord(bytes([hi, lo]).decode("gb2312")))
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
for hi in range(0xA1, 0xA4):
|
||||
for lo in range(0xA1, 0xFF):
|
||||
try:
|
||||
add(ord(bytes([hi, lo]).decode("gb2312")))
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
for ch in extra:
|
||||
add(ord(ch))
|
||||
return sorted(cps)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 逐字形渲染
|
||||
# --------------------------------------------------------------------------
|
||||
class Glyph:
|
||||
__slots__ = ("cp", "w", "h", "dx", "dy", "adv", "pixels")
|
||||
|
||||
def __init__(self, cp, w, h, dx, dy, adv, pixels):
|
||||
self.cp, self.w, self.h, self.dx = cp, w, h, dx
|
||||
self.dy, self.adv, self.pixels = dy, adv, pixels
|
||||
|
||||
|
||||
def render_one(font, ch, pad):
|
||||
asc, desc = font.getmetrics()
|
||||
# Pillow: ascent & descent 均为正; 字体行高 = asc + |desc|(desc 或为负,用 abs 归一)
|
||||
line_h = asc + abs(desc)
|
||||
Wc = int(font.getlength(ch)) + 2 * pad + 16
|
||||
Hc = line_h + 2 * pad
|
||||
img = Image.new("L", (Wc, Hc), 0)
|
||||
ImageDraw.Draw(img).text((pad, pad), ch, font=font, fill=255)
|
||||
bb = img.getbbox()
|
||||
if bb is None:
|
||||
return Glyph(ord(ch), 0, 0, 0, 0, int(round(font.getlength(ch))), None)
|
||||
l, t, r, b = bb
|
||||
return Glyph(ord(ch), r - l, b - t, l - pad, t - pad,
|
||||
int(round(font.getlength(ch))), img.crop((l, t, r, b)).tobytes())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 打包(decreasing-height 书架式;枚举多个最大宽求最小面积)
|
||||
# --------------------------------------------------------------------------
|
||||
def pack_best(glyphs, gap=1):
|
||||
best = None
|
||||
for max_w in (256, 384, 512, 640, 768, 1024, 1280, 1536, 2048, 3072):
|
||||
items = sorted(enumerate(glyphs), key=lambda t: -t[1].h)
|
||||
placements = {}
|
||||
x = y = 0
|
||||
shelf_h = 0
|
||||
cur_w = 0
|
||||
for idx, g in items:
|
||||
if g.w == 0 or g.h == 0:
|
||||
continue
|
||||
if cur_w + g.w > max_w:
|
||||
y += shelf_h + gap
|
||||
x = 0
|
||||
cur_w = 0
|
||||
shelf_h = 0
|
||||
placements[idx] = (x, y)
|
||||
x += g.w + gap
|
||||
cur_w += g.w + gap
|
||||
if g.h > shelf_h:
|
||||
shelf_h = g.h
|
||||
if not placements:
|
||||
continue
|
||||
aw = max(placements[i][0] + g.w for i, g in enumerate(glyphs) if i in placements)
|
||||
ah = y + shelf_h
|
||||
area = aw * ah
|
||||
if best is None or area < best[0]:
|
||||
best = (area, placements, aw, ah, max_w)
|
||||
if best is None:
|
||||
return {}, 0, 0, 0
|
||||
return best[1], best[2], best[3], best[4]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# idx 写入
|
||||
# --------------------------------------------------------------------------
|
||||
def write_idx(path, bit_depth, faces, glyph_tabs):
|
||||
n = len(faces)
|
||||
png_w = max(f["plane_x"] + f["plane_w"] for f in faces)
|
||||
png_h = max(f["plane_y"] + f["plane_h"] for f in faces)
|
||||
header = bytearray(struct.pack("<4sHBBII", MAGIC, VER, bit_depth, n, png_w, png_h))
|
||||
assert len(header) == 16
|
||||
|
||||
face_off = 16
|
||||
glyph_off = face_off + 20 * n
|
||||
body = bytearray()
|
||||
for i, f in enumerate(faces):
|
||||
body += struct.pack("<HHHHHhhHI", f["px"], f["plane_x"], f["plane_y"],
|
||||
f["plane_w"], f["plane_h"], f["ascent"], f["descent"],
|
||||
len(glyph_tabs[i]), 0)
|
||||
|
||||
cur = glyph_off
|
||||
tables = []
|
||||
offs = []
|
||||
for tab in glyph_tabs:
|
||||
offs.append(cur)
|
||||
tab_sorted = sorted(tab, key=lambda g: g[0])
|
||||
buf = bytearray()
|
||||
for cp, x, y, w, h, adv, dx, dy in tab_sorted:
|
||||
buf += struct.pack("<IHHHHHbB", cp, x, y, w, h, adv, dx, dy)
|
||||
tables.append(buf)
|
||||
cur += len(buf)
|
||||
|
||||
for i, off in enumerate(offs):
|
||||
struct.pack_into("<I", body, i * 20 + 16, off)
|
||||
for buf in tables:
|
||||
body += buf
|
||||
|
||||
with open(path, "wb") as fp:
|
||||
fp.write(bytes(header))
|
||||
fp.write(bytes(body))
|
||||
return len(header) + len(body)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 主流程
|
||||
# --------------------------------------------------------------------------
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="MeowOS 预渲染字库生成器 v2")
|
||||
ap.add_argument("--font", required=True)
|
||||
ap.add_argument("--sizes", default="28,20")
|
||||
ap.add_argument("--out", required=True)
|
||||
ap.add_argument("--name", default="font")
|
||||
ap.add_argument("--bitdepth", type=int, default=4, choices=(4, 8))
|
||||
ap.add_argument("--extra", default="")
|
||||
args = ap.parse_args()
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
sizes = [int(s) for s in args.sizes.split(",") if s.strip()]
|
||||
cps = build_charset(args.extra)
|
||||
print(f"字符集: {len(cps)} 码位")
|
||||
|
||||
all_planes = [] # (aw, ah, plane_l8 bytes, tab)
|
||||
max_w = 0
|
||||
for size in sizes:
|
||||
print(f"\n=== 字号 {size}px ===")
|
||||
font = ImageFont.truetype(args.font, size)
|
||||
asc, desc = font.getmetrics()
|
||||
print(f" metrics ascent={asc} descent={desc}")
|
||||
|
||||
glyphs = []
|
||||
miss = []
|
||||
for cp in cps:
|
||||
ch = chr(cp)
|
||||
g = render_one(font, ch, pad=8)
|
||||
if g.w == 0 and g.h == 0 and cp != 0x20:
|
||||
miss.append(ch)
|
||||
glyphs.append(g)
|
||||
if miss:
|
||||
print(f" [warn] {len(miss)} 码位无字形(保留advance): {''.join(miss[:20])}")
|
||||
|
||||
placements, aw, ah, chosen = pack_best(glyphs)
|
||||
print(f" packing atlas {aw}x{ah} (max_w={chosen}) glyphs={len(glyphs)}")
|
||||
|
||||
plane_l8 = bytearray(aw * ah)
|
||||
tab = []
|
||||
for i, g in enumerate(glyphs):
|
||||
if i in placements:
|
||||
px, py = placements[i]
|
||||
for row in range(g.h):
|
||||
s = row * g.w
|
||||
d = (py + row) * aw + px
|
||||
plane_l8[d:d + g.w] = g.pixels[s:s + g.w]
|
||||
tab.append((g.cp, px, py, g.w, g.h, g.adv, g.dx, g.dy))
|
||||
else:
|
||||
tab.append((g.cp, 0, 0, 0, 0, g.adv, 0, 0))
|
||||
all_planes.append((aw, ah, bytes(plane_l8), tab))
|
||||
max_w = max(max_w, aw)
|
||||
|
||||
# ---- 编排到公共画布(纵向堆叠,公共宽 max_w 偶数化) ----
|
||||
if max_w % 2:
|
||||
max_w += 1
|
||||
total_h = sum(p[1] for p in all_planes)
|
||||
canvas_l8 = bytearray(max_w * total_h)
|
||||
face_meta = []
|
||||
fy = 0
|
||||
for i, (aw, ah, plane, tab) in enumerate(all_planes):
|
||||
for y in range(ah):
|
||||
src = y * aw
|
||||
dst = (fy + y) * max_w
|
||||
canvas_l8[dst:dst + aw] = plane[src:src + aw]
|
||||
face_meta.append({"px": sizes[i], "plane_x": 0, "plane_y": fy,
|
||||
"plane_w": aw, "plane_h": ah,
|
||||
"ascent": int(ImageFont.truetype(args.font, sizes[i]).getmetrics()[0]),
|
||||
"descent": int(abs(ImageFont.truetype(args.font, sizes[i]).getmetrics()[1]))})
|
||||
fy += ah
|
||||
|
||||
# ---- 打包 + PNG ----
|
||||
packed = pack_plane_depth(canvas_l8, max_w, total_h, args.bitdepth)
|
||||
png_bytes = gray_png_encode(max_w, total_h, args.bitdepth, packed)
|
||||
png_path = os.path.join(args.out, f"{args.name}.png")
|
||||
with open(png_path, "wb") as fp:
|
||||
fp.write(png_bytes)
|
||||
|
||||
# ---- idx ----
|
||||
idx_path = os.path.join(args.out, f"{args.name}.idx")
|
||||
idx_size = write_idx(idx_path, args.bitdepth, face_meta, [p[3] for p in all_planes])
|
||||
|
||||
png_size = os.path.getsize(png_path)
|
||||
total = png_size + idx_size
|
||||
print(f"\nfont.png {png_size} B")
|
||||
print(f"font.idx {idx_size} B")
|
||||
print(f"合计 {total} B = {total/1024:.1f} KiB = {total/1048576:.2f} MiB (预算 2MiB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""preview_glyphs.py — 从 atlas/font.png + font.idx 抽取若干字符,拼成带标注的接触表 PNG,
|
||||
用于人工核对图集中字形正确、无漏字。用法: preview_glyphs.py <atlas_dir> <out.png>"""
|
||||
import struct
|
||||
import sys
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def load_idx(path):
|
||||
d = open(path, "rb").read()
|
||||
assert d[:4] == b"MFNI", d[:4]
|
||||
depth = d[6]
|
||||
nf = d[7]
|
||||
faces = []
|
||||
for i in range(nf):
|
||||
fh = d[16 + i * 20:16 + i * 20 + 20]
|
||||
px, px_, py, pw, ph, asc, dsc, gc = struct.unpack("<HHHHHhhH", fh[:16])
|
||||
goff = struct.unpack("<I", fh[16:20])[0]
|
||||
tabs = []
|
||||
for k in range(gc):
|
||||
g = d[goff + k * 16:goff + k * 16 + 16]
|
||||
cp, x, y, w, h, adv, dx, dy = struct.unpack("<IHHHHHbB", g)
|
||||
tabs.append((cp, x, y, w, h, adv, dx, dy))
|
||||
faces.append({"px": px, "px_": px_, "py_": py, "pw": pw, "ph": ph, "asc": asc, "dsc": dsc, "tabs": tabs})
|
||||
return depth, faces
|
||||
|
||||
def main():
|
||||
atlas = sys.argv[1]
|
||||
out = sys.argv[2]
|
||||
depth, faces = load_idx(f"{atlas}/font.idx")
|
||||
im = Image.open(f"{atlas}/font.png").convert("L")
|
||||
pix = im.load()
|
||||
|
||||
# 取样字符
|
||||
sample = "MeowOS 主屏关机设置播放器音乐电台返回背光时间应用关闭确认取消01472589 ABC.,。!?:;「」¥·—…%"
|
||||
cell = 44
|
||||
cols = 12
|
||||
rows = (len(sample) + cols - 1) // cols
|
||||
sheet = Image.new("RGB", (cols * cell, rows * cell), (28, 30, 34))
|
||||
dr = ImageDraw.Draw(sheet)
|
||||
for idx, ch in enumerate(sample):
|
||||
cp = ord(ch)
|
||||
r, c = divmod(idx, cols)
|
||||
# 找 28 面,找不到用 20 面
|
||||
box = None
|
||||
for f in faces:
|
||||
if f["px"] == 28:
|
||||
for t in f["tabs"]:
|
||||
if t[0] == cp and t[3] and t[4]:
|
||||
box = (f, t)
|
||||
break
|
||||
if box is None:
|
||||
for f in faces:
|
||||
if f["px"] == 20:
|
||||
for t in f["tabs"]:
|
||||
if t[0] == cp and t[3] and t[4]:
|
||||
box = (f, t)
|
||||
break
|
||||
oy, ox = r * cell + 2, c * cell + 4
|
||||
if box:
|
||||
f, t = box
|
||||
_, x, y, w, h, adv, dx, dy = t
|
||||
for yy in range(h):
|
||||
for xx in range(w):
|
||||
a = pix[f["px_"] + x + xx, f["py_"] + y + yy] # PIL: [x, y]
|
||||
if a:
|
||||
sheet.putpixel((ox + xx, oy + yy), (255, 255, 255))
|
||||
dr.text((ox, oy + 26), ch, fill=(255, 180, 60))
|
||||
sheet.save(out)
|
||||
print(f"saved {out}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,532 @@
|
||||
/*
|
||||
* meow_font.c — MeowOS 预渲染位图字体运行时(独立库,不绑定启动器)。
|
||||
*
|
||||
* 依赖:
|
||||
* - libz(musl 静态)仅用于 inflate PNG 像素面。目标系统无 libpng,故模块内自带
|
||||
* 最小 PNG 解码(灰度 non-interlaced,bit depth 4|8,filter 0-4)。
|
||||
* - zlib 头文件不在 musl sysroot,故在文件底部声明 ABI 兼容的最小 API(z_stream 布局
|
||||
* 与 libz.a 一致,已用 qemu-mipsel 验证)。
|
||||
*
|
||||
* 文件格式参见 gen/gen_font.py 注释(version=2,magic 'MFNI')。
|
||||
*
|
||||
* 绘制模型:drawtext(fb,x,y,utf8,color,size),(x,y)=行首升线顶部;baseline=y+ascent;
|
||||
* 每字形 blit 于 (x+pen+dx, y+dy);pen 累加 advance。color 为 RGB565(R=bit15..11)。
|
||||
*/
|
||||
#include "meow_font.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---------------- 最小 zlib ABI 声明(与 libz.a 匹配,mipsel 32-bit unsigned long=4B) ------- */
|
||||
typedef unsigned char mzf_Bytef;
|
||||
typedef unsigned int mzf_uInt;
|
||||
typedef unsigned long mzf_uLong;
|
||||
typedef void *mzf_voidpf;
|
||||
typedef unsigned int (*mzf_alloc_func)(unsigned int, unsigned int);
|
||||
typedef void (*mzf_free_func)(unsigned int, unsigned int);
|
||||
|
||||
typedef struct mzf_stream_s {
|
||||
mzf_Bytef *next_in;
|
||||
mzf_uInt avail_in;
|
||||
mzf_uLong total_in;
|
||||
mzf_Bytef *next_out;
|
||||
mzf_uInt avail_out;
|
||||
mzf_uLong total_out;
|
||||
char *msg;
|
||||
void *state;
|
||||
mzf_alloc_func zalloc;
|
||||
mzf_free_func zfree;
|
||||
mzf_voidpf opaque;
|
||||
int data_type;
|
||||
mzf_uLong adler;
|
||||
mzf_uLong reserved;
|
||||
} mzf_stream;
|
||||
|
||||
#define MZF_OK 0
|
||||
#define MZF_STREAM_END 1
|
||||
#define MZF_NO_FLUSH 0
|
||||
#define MZF_FINISH 4
|
||||
|
||||
/* 函数符号名必须与 libz.a 一致(inflate 等);结构名仅 C 侧,ABI 由布局决定。 */
|
||||
extern int inflateInit_(mzf_stream *, const char *, int);
|
||||
extern int inflate(mzf_stream *, int);
|
||||
extern int inflateEnd(mzf_stream *);
|
||||
extern const char *zlibVersion(void);
|
||||
|
||||
/* ---------------- 内部数据结构 ---------------- */
|
||||
typedef struct {
|
||||
uint32_t cp;
|
||||
uint16_t x, y, w, h, advance;
|
||||
int8_t dx;
|
||||
uint8_t dy;
|
||||
} meow_glyph;
|
||||
|
||||
typedef struct {
|
||||
int px; /* 原生字号 */
|
||||
unsigned plane_x, plane_y; /* 在大画布(plane_w x plane_h)内的面左上角 */
|
||||
unsigned plane_w, plane_h;
|
||||
int ascent, descent; /* descent>=0 */
|
||||
unsigned glyph_count;
|
||||
meow_glyph *glyphs; /* 按 cp 升序 */
|
||||
} meow_face;
|
||||
|
||||
typedef struct {
|
||||
unsigned fb_w, fb_h, fb_pitch;
|
||||
size_t plane_rowbytes;
|
||||
unsigned depth; /* 4 或 8 */
|
||||
unsigned plane_w, plane_h; /* 画布尺寸 */
|
||||
unsigned char *plane; /* 打包像素面 */
|
||||
int face_count;
|
||||
meow_face *faces;
|
||||
} meow_state;
|
||||
|
||||
static meow_state g;
|
||||
|
||||
/* PNG 内部为大端;idx(自定格式)为小端。 */
|
||||
static uint32_t be32(const unsigned char *p) {
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
||||
((uint32_t)p[2] << 8) | (uint32_t)p[3];
|
||||
}
|
||||
static uint32_t le32(const unsigned char *p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
/* ---------------- 内存读文件 ---------------- */
|
||||
static unsigned char *read_file(const char *path, size_t *out_len) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return NULL; }
|
||||
unsigned char *buf = (unsigned char *)malloc((size_t)n ? (size_t)n : 1);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
size_t rd = fread(buf, 1, (size_t)n, f);
|
||||
fclose(f);
|
||||
if (rd != (size_t)n) { free(buf); return NULL; }
|
||||
*out_len = (size_t)n;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ---------------- zlib inflate ---------------- */
|
||||
static int inflate_mem(const unsigned char *src, size_t srclen,
|
||||
unsigned char *dst, size_t dstlen, size_t *out) {
|
||||
mzf_stream s;
|
||||
memset(&s, 0, sizeof(s));
|
||||
s.next_in = (mzf_Bytef *)src;
|
||||
s.avail_in = (mzf_uInt)srclen;
|
||||
s.next_out = (mzf_Bytef *)dst;
|
||||
s.avail_out = (mzf_uInt)dstlen;
|
||||
s.zalloc = 0; s.zfree = 0; s.opaque = 0;
|
||||
if (inflateInit_(&s, zlibVersion(), (int)sizeof(s)) != MZF_OK)
|
||||
return -1;
|
||||
int ret = inflate(&s, MZF_FINISH);
|
||||
*out = (size_t)(dstlen - s.avail_out);
|
||||
inflateEnd(&s);
|
||||
return (ret == MZF_STREAM_END) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ---------------- 最小 PNG 解码(灰度 non-interlaced) ---------------- */
|
||||
/* 返回打包像素面(调用方释放),并输出 width/height/depth/plane_rowbytes。失败返回 NULL。 */
|
||||
static unsigned char *png_decode_gray(const unsigned char *png, size_t len,
|
||||
unsigned *out_w, unsigned *out_h,
|
||||
unsigned *out_depth, size_t *out_rowbytes) {
|
||||
if (len < 8 || memcmp(png, "\x89PNG\r\n\x1a\n", 8) != 0)
|
||||
return NULL;
|
||||
size_t i = 8;
|
||||
unsigned w = 0, h = 0, depth = 0, color = 0, interlace = 1, compression = 1, filt = 1;
|
||||
int have_ihdr = 0;
|
||||
size_t idat_len = 0;
|
||||
unsigned char *idat = NULL;
|
||||
while (i + 8 <= len) {
|
||||
uint32_t clen = be32(png + i);
|
||||
const unsigned char *type = png + i + 4;
|
||||
if (i + 8 + (size_t)clen > len) break;
|
||||
if (memcmp(type, "IHDR", 4) == 0) {
|
||||
if (clen < 13) { free(idat); return NULL; }
|
||||
w = be32(png + i + 8);
|
||||
h = be32(png + i + 12);
|
||||
depth = png[i + 16];
|
||||
color = png[i + 17];
|
||||
compression = png[i + 18];
|
||||
filt = png[i + 19];
|
||||
interlace = png[i + 20];
|
||||
have_ihdr = 1;
|
||||
} else if (memcmp(type, "IDAT", 4) == 0) {
|
||||
unsigned char *na = (unsigned char *)realloc(idat, idat_len + clen);
|
||||
if (!na) { free(idat); return NULL; }
|
||||
idat = na;
|
||||
memcpy(idat + idat_len, png + i + 8, clen);
|
||||
idat_len += clen;
|
||||
} else if (memcmp(type, "IEND", 4) == 0) {
|
||||
break;
|
||||
}
|
||||
i += 12 + (size_t)clen;
|
||||
}
|
||||
if (!have_ihdr || !idat ||
|
||||
color != 0 || compression != 0 || filt != 0 || interlace != 0 ||
|
||||
(depth != 4 && depth != 8) || w == 0 || h == 0) {
|
||||
free(idat);
|
||||
return NULL;
|
||||
}
|
||||
size_t rowbytes = ((size_t)w * depth + 7) / 8;
|
||||
size_t scanlen = rowbytes + 1; /* 每扫描行 = 1 filter 字节 + rowbytes 像素 */
|
||||
size_t rawlen = scanlen * h;
|
||||
unsigned char *raw = (unsigned char *)malloc(rawlen);
|
||||
if (!raw) { free(idat); return NULL; }
|
||||
size_t outlen = 0;
|
||||
if (inflate_mem(idat, idat_len, raw, rawlen, &outlen) != 0 || outlen != rawlen) {
|
||||
free(raw); free(idat); return NULL;
|
||||
}
|
||||
free(idat);
|
||||
|
||||
/* 反滤波(bpp=1:灰度 depth 4|8) → out 为每行 rowbytes 的打包像素 */
|
||||
size_t outlen_px = rowbytes * h;
|
||||
unsigned char *out = (unsigned char *)malloc(outlen_px);
|
||||
if (!out) { free(raw); return NULL; }
|
||||
const unsigned bpp = 1;
|
||||
for (size_t y = 0; y < h; y++) {
|
||||
unsigned char ft = raw[y * scanlen];
|
||||
const unsigned char *c = raw + y * scanlen + 1;
|
||||
unsigned char *dst = out + y * rowbytes;
|
||||
unsigned char *prev = (y == 0) ? NULL : out + (y - 1) * rowbytes;
|
||||
for (size_t x = 0; x < rowbytes; x++) {
|
||||
unsigned char left = (x >= bpp) ? dst[x - bpp] : 0;
|
||||
unsigned char up = prev ? prev[x] : 0;
|
||||
unsigned char ul = (prev && x >= bpp) ? prev[x - bpp] : 0;
|
||||
unsigned char v = c[x];
|
||||
switch (ft) {
|
||||
case 0: dst[x] = v; break;
|
||||
case 1: dst[x] = v + left; break;
|
||||
case 2: dst[x] = v + up; break;
|
||||
case 3: dst[x] = v + (((unsigned)left + (unsigned)up) >> 1); break;
|
||||
case 4: {
|
||||
int p = (int)left + (int)up - (int)ul;
|
||||
int pa = p - (int)left; if (pa < 0) pa = -pa;
|
||||
int pb = p - (int)up; if (pb < 0) pb = -pb;
|
||||
int pc = p - (int)ul; if (pc < 0) pc = -pc;
|
||||
unsigned char pred = (pa <= pb && pa <= pc) ? left : (pb <= pc ? up : ul);
|
||||
dst[x] = v + pred;
|
||||
break;
|
||||
}
|
||||
default: free(raw); free(out); return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(raw);
|
||||
*out_w = w; *out_h = h; *out_depth = depth; *out_rowbytes = rowbytes;
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ---------------- idx 解析 ---------------- */
|
||||
static int load_idx(const unsigned char *buf, size_t len) {
|
||||
if (len < 16 || memcmp(buf, "MFNI", 4) != 0) return -1;
|
||||
unsigned depth = buf[6];
|
||||
int nfaces = buf[7];
|
||||
unsigned png_w = le32(buf + 8), png_h = le32(buf + 12);
|
||||
(void)png_w; (void)png_h;
|
||||
if (nfaces < 1 || nfaces > 32) return -1;
|
||||
if (depth != g.depth) return -2; /* 位深须与 PNG 一致 */
|
||||
|
||||
size_t face_off = 16;
|
||||
meow_face *faces = (meow_face *)calloc((size_t)nfaces, sizeof(meow_face));
|
||||
if (!faces) return -3;
|
||||
|
||||
for (int i = 0; i < nfaces; i++) {
|
||||
const unsigned char *fh = buf + face_off + (size_t)i * 20;
|
||||
meow_face *f = &faces[i];
|
||||
f->px = fh[0] | (fh[1] << 8);
|
||||
f->plane_x = fh[2] | (fh[3] << 8);
|
||||
f->plane_y = fh[4] | (fh[5] << 8);
|
||||
f->plane_w = fh[6] | (fh[7] << 8);
|
||||
f->plane_h = fh[8] | (fh[9] << 8);
|
||||
int16_t asc = (int16_t)(fh[10] | (fh[11] << 8));
|
||||
int16_t dsc = (int16_t)(fh[12] | (fh[13] << 8));
|
||||
f->ascent = asc;
|
||||
f->descent = dsc;
|
||||
f->glyph_count = fh[14] | (fh[15] << 8);
|
||||
uint32_t goff = le32(fh + 16);
|
||||
if (f->glyph_count > 0 && goff + (size_t)f->glyph_count * 16 <= len) {
|
||||
f->glyphs = (meow_glyph *)malloc((size_t)f->glyph_count * sizeof(meow_glyph));
|
||||
if (!f->glyphs) { for (int j = 0; j < i; j++) free(faces[j].glyphs); free(faces); return -4; }
|
||||
for (unsigned k = 0; k < f->glyph_count; k++) {
|
||||
const unsigned char *gchr = buf + goff + (size_t)k * 16;
|
||||
meow_glyph *gr = &f->glyphs[k];
|
||||
gr->cp = le32(gchr);
|
||||
gr->x = gchr[4] | (gchr[5] << 8);
|
||||
gr->y = gchr[6] | (gchr[7] << 8);
|
||||
gr->w = gchr[8] | (gchr[9] << 8);
|
||||
gr->h = gchr[10] | (gchr[11] << 8);
|
||||
gr->advance = gchr[12] | (gchr[13] << 8);
|
||||
gr->dx = (int8_t)gchr[14];
|
||||
gr->dy = gchr[15];
|
||||
}
|
||||
} else if (f->glyph_count == 0) {
|
||||
f->glyphs = NULL;
|
||||
} else {
|
||||
for (int j = 0; j < i; j++) free(faces[j].glyphs);
|
||||
free(faces);
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
g.faces = faces;
|
||||
g.face_count = nfaces;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------- 面/字形查找 ---------------- */
|
||||
static meow_face *face_for_size(int size) {
|
||||
if (g.face_count <= 0) return NULL;
|
||||
meow_face *best = NULL;
|
||||
int best_delta = -1;
|
||||
for (int i = 0; i < g.face_count; i++) {
|
||||
int d = g.faces[i].px - size;
|
||||
if (d < 0) d = -d;
|
||||
if (best_delta < 0 || d < best_delta) { best_delta = d; best = &g.faces[i]; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
static meow_glyph *find_glyph(meow_face *f, uint32_t cp) {
|
||||
unsigned lo = 0, hi = f->glyph_count;
|
||||
while (lo < hi) {
|
||||
unsigned mid = (lo + hi) >> 1;
|
||||
uint32_t c = f->glyphs[mid].cp;
|
||||
if (c == cp) return &f->glyphs[mid];
|
||||
if (c < cp) lo = mid + 1; else hi = mid;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---------------- 像素采样(depth 感知) ---------------- */
|
||||
static inline unsigned sample_px(unsigned x, unsigned y) {
|
||||
if (x >= g.plane_w || y >= g.plane_h) return 0;
|
||||
if (g.depth == 8) return g.plane[(size_t)y * g.plane_w + x];
|
||||
unsigned char b = g.plane[((size_t)y * g.plane_w + x) >> 1];
|
||||
unsigned nh = (x & 1) ? (b & 0x0F) : (b >> 4);
|
||||
return nh * 17;
|
||||
}
|
||||
|
||||
/* ---------------- RGB565 alpha 混合 ---------------- */
|
||||
static inline uint16_t blend565(uint16_t bg, uint16_t fg, unsigned a) {
|
||||
unsigned inv = 255 - a;
|
||||
unsigned br = (bg >> 11) & 31, bgc = (bg >> 5) & 63, bb = bg & 31;
|
||||
unsigned fr = (fg >> 11) & 31, fgc = (fg >> 5) & 63, fb = fg & 31;
|
||||
unsigned r = ((fr * a + br * inv) + 127) / 255;
|
||||
unsigned gc = ((fgc * a + bgc * inv) + 127) / 255;
|
||||
unsigned b = ((fb * a + bb * inv) + 127) / 255;
|
||||
return (uint16_t)((r << 11) | (gc << 5) | b);
|
||||
}
|
||||
|
||||
/* ---------------- 字形 blit(含缩放、裁剪、alpha 混合) ---------------- */
|
||||
static void blit_glyph(uint16_t *fb, meow_face *face, const meow_glyph *gr,
|
||||
int pen_x, int y, uint16_t color, unsigned scaleq) {
|
||||
int stride = (int)(g.fb_pitch / 2); /* uint16 元素每行数 */
|
||||
/* 目标尺寸 = 源尺寸 * scaleq / 4096 */
|
||||
unsigned gw, gh;
|
||||
int dx, dy;
|
||||
if (scaleq == 4096) {
|
||||
gw = gr->w; gh = gr->h;
|
||||
dx = gr->dx; dy = gr->dy;
|
||||
} else {
|
||||
gw = (unsigned)(((uint64_t)gr->w * scaleq) >> 12);
|
||||
gh = (unsigned)(((uint64_t)gr->h * scaleq) >> 12);
|
||||
dx = (int)(((int64_t)gr->dx * scaleq) >> 12);
|
||||
dy = (int)(((int64_t)gr->dy * scaleq) >> 12);
|
||||
}
|
||||
if (gw == 0 || gh == 0) return;
|
||||
int px = pen_x + dx;
|
||||
int py = y + dy;
|
||||
unsigned sw = gr->w, sh = gr->h;
|
||||
for (unsigned oy = 0; oy < gh; oy++) {
|
||||
int ay = py + (int)oy;
|
||||
if (ay < 0) continue;
|
||||
if (ay >= (int)g.fb_h) break;
|
||||
int sy = (int)((uint64_t)oy * sh / gh);
|
||||
int base = face->plane_x + gr->x;
|
||||
for (unsigned ox = 0; ox < gw; ox++) {
|
||||
int ax = px + (int)ox;
|
||||
if (ax < 0) continue;
|
||||
if (ax >= (int)g.fb_w) break;
|
||||
int sx = (int)((uint64_t)ox * sw / gw);
|
||||
unsigned a = sample_px((unsigned)(base + sx),
|
||||
(unsigned)(face->plane_y + gr->y + sy));
|
||||
if (!a) continue;
|
||||
size_t idx = (size_t)ay * (size_t)stride + (size_t)ax;
|
||||
fb[idx] = blend565(fb[idx], color, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- UTF-8 解码 ---------------- */
|
||||
static int utf8_next(const unsigned char **pp) {
|
||||
const unsigned char *s = *pp;
|
||||
unsigned char c = s[0];
|
||||
if (c < 0x80) { *pp += 1; return c; }
|
||||
int n;
|
||||
uint32_t cp;
|
||||
if ((c & 0xE0) == 0xC0) { n = 2; cp = c & 0x1F; }
|
||||
else if ((c & 0xF0) == 0xE0) { n = 3; cp = c & 0x0F; }
|
||||
else if ((c & 0xF8) == 0xF0) { n = 4; cp = c & 0x07; }
|
||||
else { *pp += 1; return -1; }
|
||||
for (int i = 1; i < n; i++) {
|
||||
if ((s[i] & 0xC0) != 0x80) { *pp += 1; return -1; }
|
||||
cp = (cp << 6) | (s[i] & 0x3F);
|
||||
}
|
||||
*pp += n;
|
||||
return (int)cp;
|
||||
}
|
||||
|
||||
static uint32_t advance_for_missing(meow_face *f) {
|
||||
/* 缺字:用空格或 人 位 advance;找不到就用 fontSize/2 */
|
||||
meow_glyph *sp = find_glyph(f, 0x20);
|
||||
return sp ? sp->advance : (uint32_t)(f->px / 2);
|
||||
}
|
||||
|
||||
static void draw_tofu(uint16_t *fb, int x, int y, int w, int h, uint16_t color) {
|
||||
int stride = (int)(g.fb_pitch / 2);
|
||||
for (int oy = 0; oy < h; oy++) {
|
||||
int ay = y + oy;
|
||||
if (ay < 0 || ay >= (int)g.fb_h) break;
|
||||
for (int ox = 0; ox < w; ox++) {
|
||||
int ax = x + ox;
|
||||
if (ax < 0 || ax >= (int)g.fb_w) break;
|
||||
if (oy == 0 || oy == h - 1 || ox == 0 || ox == w - 1) {
|
||||
size_t idx = (size_t)ay * (size_t)stride + (size_t)ax;
|
||||
fb[idx] = blend565(fb[idx], color, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 公共 API ---------------- */
|
||||
void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes) {
|
||||
g.fb_w = fb_w; g.fb_h = fb_h; g.fb_pitch = pitch_bytes;
|
||||
}
|
||||
|
||||
void meow_font_unload(void) {
|
||||
if (g.faces) {
|
||||
for (int i = 0; i < g.face_count; i++) free(g.faces[i].glyphs);
|
||||
free(g.faces);
|
||||
}
|
||||
free(g.plane);
|
||||
g.faces = NULL; g.plane = NULL; g.face_count = 0;
|
||||
g.plane_w = g.plane_h = 0;
|
||||
}
|
||||
|
||||
int meow_font_load(const char *png_path, const char *idx_path) {
|
||||
meow_font_unload();
|
||||
size_t plen = 0, ilen = 0;
|
||||
unsigned char *png = read_file(png_path, &plen);
|
||||
if (!png) return -1;
|
||||
unsigned char *idx = read_file(idx_path, &ilen);
|
||||
if (!idx) { free(png); return -1; }
|
||||
|
||||
unsigned w, h, depth;
|
||||
size_t rowbytes;
|
||||
unsigned char *plane = png_decode_gray(png, plen, &w, &h, &depth, &rowbytes);
|
||||
free(png);
|
||||
if (!plane) { free(idx); return -2; }
|
||||
|
||||
g.plane = plane;
|
||||
g.depth = depth;
|
||||
g.plane_w = w;
|
||||
g.plane_h = h;
|
||||
g.plane_rowbytes = rowbytes;
|
||||
|
||||
if (load_idx(idx, ilen) != 0) {
|
||||
free(idx);
|
||||
meow_font_unload();
|
||||
return -3;
|
||||
}
|
||||
free(idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_font_has_size(int size) {
|
||||
for (int i = 0; i < g.face_count; i++)
|
||||
if (g.faces[i].px == size) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_font_ascent(int size) {
|
||||
meow_face *f = face_for_size(size);
|
||||
return f ? f->ascent : 0;
|
||||
}
|
||||
|
||||
int meow_font_line_height(int size) {
|
||||
meow_face *f = face_for_size(size);
|
||||
return f ? (f->ascent + f->descent) : 0;
|
||||
}
|
||||
|
||||
int meow_font_width_text(const char *utf8, int size) {
|
||||
meow_face *f = face_for_size(size);
|
||||
if (!f) return 0;
|
||||
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
|
||||
const unsigned char *p = (const unsigned char *)utf8;
|
||||
int total = 0;
|
||||
while (*p) {
|
||||
int cp = utf8_next(&p);
|
||||
if (cp < 0) continue;
|
||||
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
|
||||
unsigned adv = gr ? gr->advance : advance_for_missing(f);
|
||||
if (scaleq == 4096) total += (int)adv;
|
||||
else total += (int)(((uint64_t)adv * scaleq) >> 12);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
|
||||
uint16_t color, int size) {
|
||||
meow_face *f = face_for_size(size);
|
||||
if (!f || !g.plane) return;
|
||||
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
|
||||
const unsigned char *p = (const unsigned char *)utf8;
|
||||
int pen = 0;
|
||||
while (*p) {
|
||||
int cp = utf8_next(&p);
|
||||
if (cp < 0) continue;
|
||||
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
|
||||
if (gr && gr->w > 0 && gr->h > 0) {
|
||||
blit_glyph(fb, f, gr, x + pen, y, color, scaleq);
|
||||
} else if (!gr) {
|
||||
/* 缺字:画空心方块 */
|
||||
unsigned adv = advance_for_missing(f);
|
||||
int tw, th;
|
||||
if (scaleq == 4096) { tw = f->px / 2; th = f->ascent; }
|
||||
else { tw = (int)(((uint64_t)(f->px / 2) * scaleq) >> 12); th = (int)(((uint64_t)f->ascent * scaleq) >> 12); }
|
||||
draw_tofu(fb, x + pen + 1, y + 1, tw - 2, th - 2, color);
|
||||
pen += scaleq == 4096 ? (int)adv : (int)(((uint64_t)adv * scaleq) >> 12);
|
||||
continue;
|
||||
}
|
||||
int adv = gr ? gr->advance : (int)advance_for_missing(f);
|
||||
pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12);
|
||||
}
|
||||
}
|
||||
|
||||
void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8,
|
||||
uint16_t color, uint16_t outline_color, int ol, int size) {
|
||||
if (ol <= 0) { meow_font_drawtext(fb, x, y, utf8, color, size); return; }
|
||||
static const int odx[8] = {-1,1,-1,1,0,0,-1,1};
|
||||
static const int ody[8] = {-1,-1,1,1,-1,1,0,0};
|
||||
/* 先将每个字形画 8 方向偏移描边,再画原色:逐字符处理以保证 advance 一致性。 */
|
||||
meow_face *f = face_for_size(size);
|
||||
if (!f || !g.plane) return;
|
||||
unsigned scaleq = (size == f->px) ? 4096u : (unsigned)(((uint64_t)size << 12) / (uint64_t)f->px);
|
||||
const unsigned char *p = (const unsigned char *)utf8;
|
||||
int pen = 0;
|
||||
while (*p) {
|
||||
int cp = utf8_next(&p);
|
||||
if (cp < 0) continue;
|
||||
meow_glyph *gr = find_glyph(f, (uint32_t)cp);
|
||||
if (gr && gr->w > 0 && gr->h > 0) {
|
||||
for (int k = 0; k < 8; k++)
|
||||
blit_glyph(fb, f, gr, x + pen + odx[k] * ol, y + ody[k] * ol, outline_color, scaleq);
|
||||
}
|
||||
int adv = gr ? gr->advance : (int)advance_for_missing(f);
|
||||
pen += scaleq == 4096 ? adv : (int)(((uint64_t)adv * scaleq) >> 12);
|
||||
}
|
||||
meow_font_drawtext(fb, x, y, utf8, color, size);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef MEOW_FONT_H
|
||||
#define MEOW_FONT_H
|
||||
/*
|
||||
* meow_font.h — MeowOS 预渲染位图字体运行时(独立、可复用,不绑定启动器)。
|
||||
*
|
||||
* 数据来源:由 gen/gen_font.py 生成的 font.png(灰度 PNG,bit depth 4|8,含各字号面)
|
||||
* + font.idx(索引:头部 + 每面元数据 + 每字符 16B 记录)。
|
||||
*
|
||||
* 设备端加载:模块内建最小 PNG 解码(仅支持灰度非隔行)经 zlib inflate + 反滤波还原像素,
|
||||
* 无需 libpng;交叉链接 musl 静态 libz.a。像素面按 depth=4|8 打包存储,取像素时按 nibble 解包。
|
||||
*
|
||||
* 绘制模型:drawtext(fb, x, y, utf8, color, size);(x,y)=行首「升线(top)」锚点;
|
||||
* baseline = y + ascent;每字形 blit 于 (x + pen + dx, y + dy);pen 累加 advance。
|
||||
* color 为 565 位 RGB(R 在 bit15..11,G bit10..5,B bit4..0),与 launcher 一致。
|
||||
* 无字回退:缺码位画空心方块(tofu)并前进空格宽度。
|
||||
*
|
||||
* 字号:字库含若干原生面(如 28、20)。drawtext 请求 size 时先找精确匹配面;
|
||||
* 若无则就近取一面做最近邻缩放(最优仍用原生字号)。
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 设置 framebuffer 几何:宽/高(像素)与每行字节数(如 480*2)。绘制前调用一次。 */
|
||||
void meow_font_set_fb(unsigned fb_w, unsigned fb_h, unsigned pitch_bytes);
|
||||
|
||||
/* 加载字库(全部字号)。成功返回 0,失败返回负值。可重复调用(先卸载旧的)。 */
|
||||
int meow_font_load(const char *png_path, const char *idx_path);
|
||||
|
||||
/* 释放字库。 */
|
||||
void meow_font_unload(void);
|
||||
|
||||
/* 某字号是否存在(命中原生面返回 1)。 */
|
||||
int meow_font_has_size(int size);
|
||||
|
||||
/* UTF-8 字符串渲染宽度(px)。字面不存在返回 0。 */
|
||||
int meow_font_width_text(const char *utf8, int size);
|
||||
|
||||
/* 行高(px = ascent + descent)。 */
|
||||
int meow_font_line_height(int size);
|
||||
int meow_font_ascent(int size);
|
||||
|
||||
/* 绘制文本:(x,y)=行首升线顶部;color=RGB565。已按 fb_w/fb_h 裁剪。 */
|
||||
void meow_font_drawtext(uint16_t *fb, int x, int y, const char *utf8,
|
||||
uint16_t color, int size);
|
||||
|
||||
/* 描边绘制:先以 outline_color 将每字形向 4/8 邻域平移 ol 绘制一圈,再以 color 填充。 */
|
||||
void meow_font_drawtext_outline(uint16_t *fb, int x, int y, const char *utf8,
|
||||
uint16_t color, uint16_t outline_color, int ol, int size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_FONT_H */
|
||||
Binary file not shown.
Reference in new issue
Block a user