From 9bfdaecc6ba9dd81b0a0ac4e9847d540b5a4b8c1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 27 Oct 2026 22:08:16 +0800 Subject: [PATCH] feat(vpui): support direct and Explorer-managed TTF text vpui.set_text_mode selects: - TEXT_MODE_DIRECT: vpui imports fonttools, loads /FONTS/FONT.TTF and builds the 8x16 atlas in-process. - TEXT_MODE_MANAGED (default): text is queued per frame and drawn by Explorer through the new DESK_REQ_TEXT request; Explorer uses the same fonttools atlas. Remove the fixed VPUI.ATL asset and its loader. --- Apps/Explorer/desk_server.vp | 115 +++++++++++++++++++++++++ build.py | 3 - vpsdk/desk.vp | 1 + vpsdk/fonttools/fonttools.vp | 45 ++++++++++ vpsdk/vpui.vp | 158 +++++++++++++++++++++++------------ vpsdk/vpui_font.atl | Bin 16384 -> 0 bytes vpsdk/window.vp | 8 ++ 7 files changed, 273 insertions(+), 57 deletions(-) delete mode 100644 vpsdk/vpui_font.atl diff --git a/Apps/Explorer/desk_server.vp b/Apps/Explorer/desk_server.vp index 8b958cf..8bdc219 100644 --- a/Apps/Explorer/desk_server.vp +++ b/Apps/Explorer/desk_server.vp @@ -4,6 +4,7 @@ import vpsdk.fs as fs import vpsdk.process as process import vpsdk.syscall as syscall import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表 +import vpsdk.fonttools.fonttools as fonttools from stdint import * import string @@ -60,6 +61,25 @@ define SHADOW_TOP = 4 define SHADOW_BOTTOM = 14 define SHADOW_ALPHA = 100 +_ttf_font: fonttools.Font* = None +_ttf_atlas: uint8* = None +_ttf_state: int = 0 + +def _ensure_ttf(): + global _ttf_font, _ttf_atlas, _ttf_state + if _ttf_state != 0: return + f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") + if f is None: + _ttf_state = -1 + return + a: uint8* = fonttools.build_ascii_atlas(f, vpui.CHAR_W, vpui.CHAR_H) + if a is None: + _ttf_state = -1 + return + _ttf_font = f + _ttf_atlas = a + _ttf_state = 1 + # 光标 16×16 位图模板(取自 desktop.vp 的历史实现):0 = 透明、1 = 黑边、2 = 白心。 # 同一历史文件里的 _CURSOR_IBEAM / _CURSOR_RESIZE_* 待接 WinSlot.cursor 后再启用。 _CURSOR_ARROW: [256]uint8 = [ @@ -724,6 +744,37 @@ class DeskServer: # ---------------- 文本(8x16 点阵,字体表复用 vpui.font)---------------- def draw_char(self, x: int, y: int, ch: char, col: uint32): ci: int = int(uint8(ch)) + _ensure_ttf() + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < vpui.CHAR_H: + ty: int = y + fy + if ty >= 0 and ty < self.fb_h: + base: int = ty * self.fb_w + fx: int = 0 + while fx < vpui.CHAR_W: + a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx]) + if a != 0: + px: int = x + fx + if px >= 0 and px < self.fb_w: + idx: int = base + px + if a >= 255: + self.back[idx] = col + else: + dst: uint32 = self.back[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(col & 0xFF) + sg: int = int((col >> 8) & 0xFF) + sb: int = int((col >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + self.back[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + return if ci >= 128: ci = ci & 127 fy: int = 0 while fy < vpui.CHAR_H: @@ -1648,6 +1699,67 @@ class DeskServer: self.evt.d = d return desk.ring_push(self.shm + desk.desk_evt_ring_off(pid), self.evt) + def draw_text_request(self, win: int, x: int, y: int, color: uint32, text: char*): + s: desk.WinSlot* = self.slot(win) + if s.used == 0: return + _ensure_ttf() + buf: uint32* = uint32*(self.shm + uint64(s.fb_off)) + bw: int = int(s.fb_w) + bh: int = int(s.fb_h) + cx: int = x + i: int = 0 + while text[i] != 0: + ci: int = int(uint8(text[i])) + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < vpui.CHAR_H: + ty: int = y + fy + if ty >= 0 and ty < bh: + base: int = ty * bw + fx: int = 0 + while fx < vpui.CHAR_W: + a: int = int(_ttf_atlas[(ci * vpui.CHAR_H + fy) * vpui.CHAR_W + fx]) + if a != 0: + px: int = cx + fx + if px >= 0 and px < bw: + idx: int = base + px + if a >= 255: + buf[idx] = color + else: + dst: uint32 = buf[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(color & 0xFF) + sg: int = int((color >> 8) & 0xFF) + sb: int = int((color >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + else: + c2: int = ci & 127 + fy = 0 + while fy < vpui.CHAR_H: + ty = y + fy + if ty >= 0 and ty < bh: + row: uint8 = vpui.font[c2][fy] + if row != uint8(0): + base = ty * bw + fx = 0 + while fx < vpui.CHAR_W: + if row & (0x80 >> fx): + px = cx + fx + if px >= 0 and px < bw: + buf[base + px] = color + fx += 1 + fy += 1 + cx += vpui.CHAR_W + i += 1 + self.mark(s.uy, s.h) + # ---------------- 请求处理 ---------------- def handle_request(self, m: desk.DeskMsg): if m.type == uint32(desk.DESK_REQ_CREATE): @@ -1665,6 +1777,9 @@ class DeskServer: s: desk.WinSlot* = self.slot(win) if s.used == 0: return ty: uint32 = m.type + if ty == uint32(desk.DESK_REQ_TEXT): + self.draw_text_request(win, m.a, m.b, uint32(m.c), &m.title[0]) + return if ty == uint32(desk.DESK_REQ_DESTROY): self._close_window(win) return diff --git a/build.py b/build.py index 11b7514..247f79b 100644 --- a/build.py +++ b/build.py @@ -95,9 +95,6 @@ def CopyFonts(): src = "NotoSansSC/NotoSansSC-6.ttf" if os.path.exists(src): shutil.copy(src, "./disk/fonts/font.ttf") - atlas = "vpsdk/vpui_font.atl" - if os.path.exists(atlas): - shutil.copy(atlas, "./disk/fonts/vpui.atl") def BuildLibs(): diff --git a/vpsdk/desk.vp b/vpsdk/desk.vp index 959c757..d5431ad 100644 --- a/vpsdk/desk.vp +++ b/vpsdk/desk.vp @@ -54,6 +54,7 @@ define DESK_REQ_TITLE = 5 # title 在数据区 define DESK_REQ_FLUSH = 6 # 像素已写,请求合成该窗口 define DESK_REQ_ACTIVATE = 7 # 置顶并聚焦 define DESK_REQ_CURSOR = 8 # a=cursor type(app 请求窗口内光标形状) +define DESK_REQ_TEXT = 9 # a=x b=y c=color,title=文字(explorer 托管绘制) # 消息类型:事件(explorer → app) define DESK_EVT_CREATED = 101 # a=win_id(失败为 -1) define DESK_EVT_CLOSE = 102 # 用户点了关闭按钮,app 应当退出 diff --git a/vpsdk/fonttools/fonttools.vp b/vpsdk/fonttools/fonttools.vp index eb0fd74..2608b20 100644 --- a/vpsdk/fonttools/fonttools.vp +++ b/vpsdk/fonttools/fonttools.vp @@ -500,6 +500,51 @@ def draw_text(font: Font*, fb: uint32*, fw: int, fh: int, x: int, baseline: int, i += int(adv) return int(pen) +_ascii_tmp: [128 * 128]uint32 + +def build_ascii_atlas(font: Font*, cw: int, ch: int) -> uint8*: + """Render ASCII 0x20..0x7e into an alpha atlas of cw x ch cells.""" + if font is None or font.loaded == 0: return None + if cw <= 0 or ch <= 0 or cw > 128 or ch > 128: return None + atlas: uint8* = uint8*(stdlib.calloc(1, uint64(128 * cw * ch))) + if atlas is None: return None + ci: int = 32 + while ci < 127: + gid: int = font.glyph_index(uint32(ci)) + if gid != 0: + r: uint64 = font.glyph_range(gid) + if r != 0: + base: uint8* = font.data + uint64(font.glyf_off) + (r >> 32) + xmin: int = _i16(base, 2) + ymin: int = _i16(base, 4) + xmax: int = _i16(base, 6) + ymax: int = _i16(base, 8) + bw: int = xmax - xmin + bh: int = ymax - ymin + if bw > 0 and bh > 0: + a: float = float(cw - 2) / float(bw) + d: float = float(ch - 4) / float(bh) + tx: float = 1.0 - float(xmin) * a + ty: float = -2.0 - float(ymax) * d + string.memset(&_ascii_tmp, 0, uint64(cw * ch * 4)) + global _edge_count, _min_x, _max_x, _min_y, _max_y + _edge_count = 0 + _min_x = 1.0e30 + _max_x = -1.0e30 + _min_y = 1.0e30 + _max_y = -1.0e30 + _add_glyph(font, gid, a, 0.0, 0.0, d, tx, ty, 0.0, 0.0, 1.0, 0) + _fill_edges(&_ascii_tmp[0], cw, ch, uint32(0x00FFFFFF)) + py: int = 0 + while py < ch: + px: int = 0 + while px < cw: + atlas[(ci * ch + py) * cw + px] = uint8(_ascii_tmp[py * cw + px] & 0xFF) + px += 1 + py += 1 + ci += 1 + return atlas + def load(path: const str) -> Font*: fd: int = fs.open(path, fs.O_RDONLY) if fd < 0: return None diff --git a/vpsdk/vpui.vp b/vpsdk/vpui.vp index 557807a..5f8efaa 100644 --- a/vpsdk/vpui.vp +++ b/vpsdk/vpui.vp @@ -2,7 +2,7 @@ import vpsdk.window as window import vpsdk.process as process import vpsdk.devfs as devfs import vpsdk.stdlib as stdlib -import vpsdk.fs as fs +import vpsdk.fonttools.fonttools as fonttools import string define TITLE_H = 32 @@ -10,6 +10,11 @@ define MAX_WIDGETS = 24 define CHAR_W = 8 define CHAR_H = 16 +define TEXT_MODE_DIRECT = 0 +define TEXT_MODE_MANAGED = 1 +define TEXT_QUEUE_MAX = 256 +define TEXT_CHUNK = 28 + define WIDGET_LABEL = 1 define WIDGET_BUTTON = 2 define WIDGET_ENTRY = 3 @@ -248,70 +253,109 @@ font: [256][16]uint8 = [ [0x00,0x00,0x70,0x13,0x24,0x26,0x24,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00], ] -_ttf_font: uint8* = None -_ttf_state: int = 0 +class TextCmd: + x: int + y: int + color: uint32 + s: [32]char -def _ensure_font(): - global _ttf_font, _ttf_state +_text_mode: int = TEXT_MODE_MANAGED +_ttf_font: fonttools.Font* = None +_ttf_atlas: uint8* = None +_ttf_state: int = 0 +_text_queue: [TEXT_QUEUE_MAX]TextCmd +_text_qn: int = 0 + +def set_text_mode(mode: int): + global _text_mode + _text_mode = mode + +def _ensure_ttf(): + global _ttf_font, _ttf_atlas, _ttf_state if _ttf_state != 0: return - fd: int = fs.open("/FONTS/VPUI.ATL", fs.O_RDONLY) - if fd < 0: + f: fonttools.Font* = fonttools.load("/FONTS/FONT.TTF") + if f is None: _ttf_state = -1 return - buf: uint8* = uint8*(stdlib.malloc(uint64(16384))) - if buf is None: - fs.close(fd) + a: uint8* = fonttools.build_ascii_atlas(f, CHAR_W, CHAR_H) + if a is None: _ttf_state = -1 return - got: uint64 = 0 - while got < uint64(16384): - n: int = fs.read(fd, rawptr(buf + got), uint32(16384) - uint32(got)) - if n <= 0: break - got += uint64(n) - fs.close(fd) - if got != uint64(16384): - stdlib.free(buf) - _ttf_state = -1 - return - _ttf_font = buf + _ttf_font = f + _ttf_atlas = a _ttf_state = 1 +def _queue_text(x: int, y: int, s: const str, color: uint32): + global _text_qn + if _text_mode != TEXT_MODE_MANAGED: return + cx: int = x + cy: int = y + i: int = 0 + while s[i] != 0: + if s[i] == 10: + cx = x + cy += CHAR_H + i += 1 + continue + if _text_qn >= TEXT_QUEUE_MAX: return + cmd: TextCmd* = &_text_queue[_text_qn] + cmd.x = cx + cmd.y = cy + cmd.color = color + n: int = 0 + while n < TEXT_CHUNK and s[i] != 0 and s[i] != 10: + cmd.s[n] = s[i] + n += 1 + i += 1 + cmd.s[n] = 0 + _text_qn += 1 + cx += n * CHAR_W + +def _flush_text(win_id: int): + global _text_qn + if _text_qn == 0: return + for i in range(_text_qn): + cmd: TextCmd* = &_text_queue[i] + window.text(win_id, cmd.x, cmd.y, cmd.color, &cmd.s[0]) + _text_qn = 0 + def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: uint32): if not buf: return if bw <= 0 or bh <= 0: return ci: int = int(uint8(ch)) - _ensure_font() - if _ttf_state == 1 and ci < 128: - fy: int = 0 - while fy < CHAR_H: - py: int = y + fy - if py >= 0 and py < bh: - fx: int = 0 - while fx < CHAR_W: - a: int = int(_ttf_font[(ci * 16 + fy) * 8 + fx]) - if a != 0: - px: int = x + fx - if px >= 0 and px < bw: - idx: int = py * bw + px - if a >= 255: - buf[idx] = color - else: - dst: uint32 = buf[idx] - dr: int = int(dst & 0xFF) - dg: int = int((dst >> 8) & 0xFF) - db: int = int((dst >> 16) & 0xFF) - sr: int = int(color & 0xFF) - sg: int = int((color >> 8) & 0xFF) - sb: int = int((color >> 16) & 0xFF) - r: int = (sr * a + dr * (255 - a)) / 255 - g: int = (sg * a + dg * (255 - a)) / 255 - b: int = (sb * a + db * (255 - a)) / 255 - buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) - fx += 1 - fy += 1 - return + if _text_mode == TEXT_MODE_DIRECT: + _ensure_ttf() + if _ttf_state == 1 and ci < 128: + fy: int = 0 + while fy < CHAR_H: + py: int = y + fy + if py >= 0 and py < bh: + fx: int = 0 + while fx < CHAR_W: + a: int = int(_ttf_atlas[(ci * CHAR_H + fy) * CHAR_W + fx]) + if a != 0: + px: int = x + fx + if px >= 0 and px < bw: + idx: int = py * bw + px + if a >= 255: + buf[idx] = color + else: + dst: uint32 = buf[idx] + dr: int = int(dst & 0xFF) + dg: int = int((dst >> 8) & 0xFF) + db: int = int((dst >> 16) & 0xFF) + sr: int = int(color & 0xFF) + sg: int = int((color >> 8) & 0xFF) + sb: int = int((color >> 16) & 0xFF) + r: int = (sr * a + dr * (255 - a)) / 255 + g: int = (sg * a + dg * (255 - a)) / 255 + b: int = (sb * a + db * (255 - a)) / 255 + buf[idx] = uint32(r) | (uint32(g) << 8) | (uint32(b) << 16) + fx += 1 + fy += 1 + return if ci >= 128: ci = 0 - fy = 0 + fy: int = 0 while fy < CHAR_H: if y + fy < 0 or y + fy >= bh: fy += 1 @@ -330,6 +374,9 @@ def _draw_char(buf: uint32*, bw: int, bh: int, x: int, y: int, ch: char, color: def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, color: uint32): if not buf: return if bw <= 0 or bh <= 0: return + if _text_mode == TEXT_MODE_MANAGED: + _queue_text(x, y, s, color) + return cx: int = x cy: int = y i: int = 0 @@ -337,11 +384,11 @@ def _draw_text(buf: uint32*, bw: int, bh: int, x: int, y: int, s: const str, col ch: char = s[i] if ch == 0: break if ch == 10: - cy += 16 + cy += CHAR_H cx = x else: _draw_char(buf, bw, bh, cx, cy, ch, color) - cx += 8 + cx += CHAR_W i += 1 # ============================================================================ @@ -1741,6 +1788,7 @@ virtual class Tk(Wm, Misc): self.bg_color = uint32(0xFF231919) def redraw(self): + global _text_qn # 窗口槽已回收(用户点了关闭):立即返回,不再触碰共享 arena / flush。 if self.win_id < 0: return if window.is_active(self.win_id) == 0: return @@ -1753,6 +1801,7 @@ virtual class Tk(Wm, Misc): if self.bw <= 0 or self.bh <= 0: return # 先刷新尺寸再布局:窗口 resize 后若沿用上一帧的 bw/bh,布局会滞后一帧。 self._do_layout() + _text_qn = 0 # 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。 # 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows # 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、 @@ -1772,6 +1821,7 @@ virtual class Tk(Wm, Misc): w.draw(tgt, self.bw, self.bh) if tgt != self.buf: string.memcpy(self.buf, tgt, uint64(px * 4)) + _flush_text(self.win_id) window.flush(self.win_id) def _do_layout(self): diff --git a/vpsdk/vpui_font.atl b/vpsdk/vpui_font.atl deleted file mode 100644 index 29b17cd29d4b17a7df614c2e1ad0acadde2acff7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 int: + if _connect() != 0: return -1 + for _ in range(10000): + if _send(uint32(desk.DESK_REQ_TEXT), win_id, x, y, int(color), 0, s) == 0: + return 0 + process.yield_cpu() + return -1 + def is_active(win_id: int) -> int: if _connect() != 0: return 0 if win_id < 0 or win_id >= desk.DESK_MAX_WIN: return 0