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.
This commit is contained in:
1 parent
5040149db8
commit
9bfdaecc6b
7 files changed
+273
-57
No files matched your search
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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 应当退出
|
||||
|
||||
@@ -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
|
||||
|
||||
+104
-54
@@ -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):
|
||||
|
||||
Binary file not shown.
@@ -191,6 +191,14 @@ def flush(win_id: int):
|
||||
if s.used == 0: return
|
||||
_send(uint32(desk.DESK_REQ_FLUSH), win_id, 0, 0, 0, 0, None)
|
||||
|
||||
def text(win_id: int, x: int, y: int, color: uint32, s: const str) -> 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
|
||||
|
||||
Reference in new issue
Block a user