diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ca2246a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "makefile.launchConfigurations": [ + { + "cwd": "/home/patrick/Sylva/build", + "binaryPath": "/home/patrick/Sylva/build/Kernel.elf", + "binaryArgs": [] + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index ac053ea..43583b7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -CFLAGS = -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -std=c17 -Wwrite-strings -fcf-protection=none -CXXFLAGS = -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -std=c++17 -Wwrite-strings -fcf-protection=none +CFLAGS = -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -std=c17 -Wwrite-strings -fcf-protection=none -g +CXXFLAGS = -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -std=c++17 -Wwrite-strings -fcf-protection=none -g LDFLAGS = -shared -Bsymbolic -Tefi/gnuefi/elf_x86_64_efi.lds LDLIBS = --no-undefined @@ -22,7 +22,8 @@ KERNEL_CPP = kernel/entry.cpp kernel/main.cpp kernel/serial.cpp kernel/fs.cpp \ kernel/interrupt/pic.cpp kernel/interrupt/pit.cpp \ kernel/graphics/layer.cpp \ graphics/context.cpp graphics/draw.cpp \ - fonts/pixel_font.cpp + fonts/pixel_font.cpp \ + fonts/ttf/ttf.cpp fonts/ttf/ttf_parse.cpp fonts/ttf/ttf_render.cpp KERNEL_ASM = kernel/scheduler/context_switch.S kernel/interrupt/isr.S kernel/interrupt/idt_helpers.S KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o) $(KERNEL_ASM:%.S=build/%.o) @@ -48,7 +49,7 @@ all: _bd $(EFI_OBJ) $(BOOT_OBJ) $(KERNEL_OBJ) @echo "Done." _bd: - @mkdir -p build/graphics build/kernel build/fonts build/kernel/memory \ + @mkdir -p build/graphics build/kernel build/fonts build/fonts/ttf build/kernel/memory \ build/kernel/scheduler build/kernel/interrupt build/kernel/graphics \ build/efi/lib build/efi/lib/x86_64 build/efi/lib/runtime build/efi/gnuefi @@ -99,7 +100,7 @@ build/kernel/scheduler/%.o: kernel/scheduler/%.cpp | _bd build/kernel/scheduler/%.o: kernel/scheduler/%.S | _bd @echo "Compile AS $<" @gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \ - -fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@ + -fshort-wchar -mno-red-zone -fcf-protection=none -g -c $< -o $@ build/kernel/interrupt/%.o: kernel/interrupt/%.cpp | _bd @echo "Compile CPP $<" @@ -108,7 +109,7 @@ build/kernel/interrupt/%.o: kernel/interrupt/%.cpp | _bd build/kernel/interrupt/%.o: kernel/interrupt/%.S | _bd @echo "Compile AS $<" @gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \ - -fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@ + -fshort-wchar -mno-red-zone -fcf-protection=none -g -c $< -o $@ build/graphics/%.o: graphics/%.cpp | _bd @echo "Compile CPP $<" @@ -118,6 +119,10 @@ build/fonts/%.o: fonts/%.cpp | _bd @echo "Compile CPP $<" @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ +build/fonts/ttf/%.o: fonts/ttf/%.cpp | _bd + @echo "Compile CPP $<" + @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ + vdir: all @mkdir -p vdir/EFI/BOOT vdir/sys @cp build/BOOTX64.EFI vdir/EFI/BOOT diff --git a/fonts/ttf/ttf.cpp b/fonts/ttf/ttf.cpp new file mode 100644 index 0000000..1f09915 --- /dev/null +++ b/fonts/ttf/ttf.cpp @@ -0,0 +1,125 @@ +#include +#include "ttf_internal.h" +#include +#include +#include + +// Per-glyph scratch — kept static to avoid stack pressure for big Chinese glyphs +static ttf_outline_t s_outline; +static ttf_seg_t s_segs[4096]; +static SUINT8 s_coverage[256 * 256]; + +// Render one glyph bitmap at screen (px, py) where py is the glyph TOP +// (already converted from baseline). (px, py) may be negative — caller +// must have clipped into the visible region. +static void blit_glyph(SSINT32 px, SSINT32 py, SUINT32 w, SUINT32 h, + const SUINT8* coverage, SUINT32 N, + EFI_GRAPHICS_OUTPUT_BLT_PIXEL color) +{ + SUINT32 scale = 255 / N; + for (SUINT32 y = 0; y < h; y++) { + SSINT32 sy = py + (SSINT32)y; + if (sy < 0) continue; + for (SUINT32 x = 0; x < w; x++) { + SSINT32 sx = px + (SSINT32)x; + if (sx < 0) continue; + SUINT8 c = coverage[y * w + x]; + if (c == 0) continue; + SUINT8 a = (SUINT8)(c * scale); + draw_pixel_alpha(sx, sy, color, a); + } + } +} + +// Render a single codepoint at (x, y) = baseline. Returns advance (26.6). +static f26_6 render_codepoint(ttf_face_t* face, SSINT32 cp, + SSINT32 x, SSINT32 y, SUINT32 pixel_size, + EFI_GRAPHICS_OUTPUT_BLT_PIXEL color) +{ + SUINT16 gid = ttf_cmap_lookup(face, cp); + if (gid == 0 && cp != 0) gid = 0; // notdef + + if (!ttf_load_glyph(face, gid, pixel_size, &s_outline)) { + return 0; // composite / parse error + } + + // Bitmap dims in pixels + SSINT32 bbox_w = s_outline.xmax - s_outline.xmin; + SSINT32 bbox_h = s_outline.ymax - s_outline.ymin; + if (bbox_w <= 0 || bbox_h <= 0) { + return s_outline.advance; // whitespace or zero-size + } + SUINT32 pw = (SUINT32)((bbox_w + 63) >> 6); + SUINT32 ph = (SUINT32)((bbox_h + 63) >> 6); + if (pw > 256 || ph > 256) { + return s_outline.advance; // too big for scratch + } + + // Translate outline to bitmap-local: bx = fx - xmin, by = ymax - fy + ttf_outline_t local; + local.num_points = s_outline.num_points; + local.num_contours = s_outline.num_contours; + for (SUINT16 i = 0; i < s_outline.num_contours; i++) { + local.first[i] = s_outline.first[i]; + local.last[i] = s_outline.last[i]; + } + for (SUINT16 i = 0; i < s_outline.num_points; i++) { + local.x[i] = s_outline.x[i] - s_outline.xmin; + local.y[i] = s_outline.ymax - s_outline.y[i]; + local.on_curve[i] = s_outline.on_curve[i]; + } + local.xmin = 0; + local.ymin = 0; + local.xmax = bbox_w; + local.ymax = bbox_h; + + SUINT32 n_segs = 0; + ttf_outline_to_segments(&local, s_segs, &n_segs); + + // Clear coverage + for (SUINT32 i = 0; i < pw * ph; i++) s_coverage[i] = 0; + + const SUINT32 N = 5; // subsamples per pixel row + ttf_rasterize(s_segs, n_segs, 0, 0, pw, ph, s_coverage, N); + + // Screen origin of bitmap + // bitmap (0, 0) is at font (xmin, ymax) which is at screen + // (x + xmin/64, y - ymax/64) since screen y is flipped + SSINT32 px_screen = x + (s_outline.xmin >> 6); + SSINT32 py_screen = y - (s_outline.ymax >> 6); + + blit_glyph(px_screen, py_screen, pw, ph, s_coverage, N, color); + + return s_outline.advance; +} + +SSINT32 ttf_draw_text(ttf_face_t* face, const char* utf8, + SSINT32 x, SSINT32 y, SUINT32 pixel_size, + EFI_GRAPHICS_OUTPUT_BLT_PIXEL color) +{ + if (!face || !utf8) return 0; + const char* p = utf8; + f26_6 pen = 0; + while (*p) { + SSINT32 cp = ttf_utf8_decode(&p); + if (cp < 0) continue; + f26_6 adv = render_codepoint(face, cp, + x + (pen >> 6), y, pixel_size, color); + pen += adv; + } + return pen; +} + +SSINT32 ttf_text_width(ttf_face_t* face, const char* utf8, SUINT32 pixel_size) { + if (!face || !utf8) return 0; + const char* p = utf8; + f26_6 pen = 0; + while (*p) { + SSINT32 cp = ttf_utf8_decode(&p); + if (cp < 0) continue; + SUINT16 gid = ttf_cmap_lookup(face, cp); + if (!ttf_load_glyph(face, gid, pixel_size, &s_outline)) continue; + pen += s_outline.advance; + } + return pen; +} diff --git a/fonts/ttf/ttf_internal.h b/fonts/ttf/ttf_internal.h new file mode 100644 index 0000000..ac6f58a --- /dev/null +++ b/fonts/ttf/ttf_internal.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include "ttf_math.h" + +typedef struct ttf_face ttf_face_t; + +typedef struct ttf_face { + const SUINT8* data; + UINTN size; + + // head + SUINT32 units_per_em; + SSINT16 index_to_loc_format; // 0 = short, 1 = long + + // hhea + SSINT16 hhea_ascender; + SSINT16 hhea_descender; + SSINT16 hhea_line_gap; + SUINT16 num_long_hor_metrics; + + // maxp + SUINT16 num_glyphs; + SUINT16 max_points; + SUINT16 max_contours; + + // os/2 + SSINT16 os2_ascender; + SSINT16 os2_descender; + SSINT16 os2_line_gap; + + // Table directory (sorted by tag at open time) + struct { + char tag[4]; + SUINT32 offset; + SUINT32 length; + } tables[32]; + SUINT16 num_tables; + + // Cached pointers into data[] + const SUINT8* loca; // size depends on num_glyphs and format + const SUINT8* glyf; + SUINT32 glyf_len; + const SUINT8* hmtx; + SUINT32 hmtx_len; + const SUINT8* cmap; +} ttf_face_t; + +// Decomposed glyph: contours in pixel space (26.6 fp) ready to scan. +typedef struct ttf_outline { + f26_6 x[1024]; + f26_6 y[1024]; + SUINT8 on_curve[1024]; + SUINT16 first[64]; + SUINT16 last[64]; + SUINT16 num_contours; + SUINT16 num_points; + + // Bounding box in pixel space (26.6 fp). + SSINT32 xmin, ymin, xmax, ymax; + + // Pen advance in pixel space (26.6 fp). + f26_6 advance; +} ttf_outline_t; + +typedef struct ttf_seg { + f26_6 x0, y0, x1, y1; // line / quad end points + f26_6 cx, cy; // quadratic control (set to x0/y0 if is_line) + SUINT8 is_line; // 1 = line, 0 = quad +} ttf_seg_t; + +// Parse the glyf entry for glyph_id. Fills outline already +// scaled to pixel_size_px. Returns false on composite / parse error. +bool ttf_load_glyph(ttf_face_t* face, SUINT16 glyph_id, + SUINT32 pixel_size_px, ttf_outline_t* out); + +// UTF-8 decoder. *p advances past the codepoint. Returns -1 on error. +SSINT32 ttf_utf8_decode(const char** p); + +// cmap lookup — returns glyph_id (0 = notdef). +SUINT16 ttf_cmap_lookup(ttf_face_t* face, SSINT32 cp); + +// Decompose outline into a flat array of line/quadratic segments. +// out must have room for 2*num_points + num_contours entries. +void ttf_outline_to_segments(const ttf_outline_t* outline, + ttf_seg_t* segs, SUINT32* num_segs); + +// Scanline rasterize segments into per-pixel coverage. +// coverage[w*h] gets values in [0, N] where N = supersample count. +void ttf_rasterize(const ttf_seg_t* segs, SUINT32 num_segs, + SSINT32 x0, SSINT32 y0, SUINT32 w, SUINT32 h, + SUINT8* coverage, SUINT32 N); diff --git a/fonts/ttf/ttf_math.h b/fonts/ttf/ttf_math.h new file mode 100644 index 0000000..26c2c1d --- /dev/null +++ b/fonts/ttf/ttf_math.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +// 26.6 fixed point. 1.0 = 64, -32.0 .. 31.999 range in SSINT32. +typedef SSINT32 f26_6; + +#define F26_ONE ((f26_6)64) +#define F26_HALF ((f26_6)32) +#define F26_FROM_INT(x) ((f26_6)((x) * 64)) +#define F26_FLOOR(x) ((x) >> 6) +#define F26_ROUND(x) (((x) + 32) >> 6) +#define F26_FRAC(x) ((x) & 63) + +static inline f26_6 f26_mul(f26_6 a, f26_6 b) { + return (f26_6)(((SSINT64)a * b) >> 6); +} + +static inline f26_6 f26_div(f26_6 a, f26_6 b) { + if (b == 0) return 0; + return (f26_6)(((SSINT64)a << 6) / b); +} + +// Linear interpolation. t in [0, 64]. +static inline f26_6 f26_lerp(f26_6 a, f26_6 b, f26_6 t) { + return a + f26_mul(b - a, t); +} + +// Floor-of-division helper for bezier root solving. +static inline SSINT32 isign(SSINT32 x) { return (x > 0) - (x < 0); } diff --git a/fonts/ttf/ttf_parse.cpp b/fonts/ttf/ttf_parse.cpp new file mode 100644 index 0000000..c67321a --- /dev/null +++ b/fonts/ttf/ttf_parse.cpp @@ -0,0 +1,331 @@ +#include "ttf_internal.h" +#include +#include +#include + +// ---- Big-endian readers (TTF is big-endian) ---- +static inline SUINT16 rd16(const SUINT8* p) { + return ((SUINT16)p[0] << 8) | p[1]; +} +static inline SSINT16 rd16s(const SUINT8* p) { + return (SSINT16)(((SUINT16)p[0] << 8) | p[1]); +} +static inline SUINT32 rd32(const SUINT8* p) { + return ((SUINT32)p[0] << 24) | ((SUINT32)p[1] << 16) | + ((SUINT32)p[2] << 8) | (SUINT32)p[3]; +} + +static const SUINT8* find_table(ttf_face_t* face, const char tag[4]) { + for (SUINT16 i = 0; i < face->num_tables; i++) { + if (face->tables[i].tag[0] == tag[0] && + face->tables[i].tag[1] == tag[1] && + face->tables[i].tag[2] == tag[2] && + face->tables[i].tag[3] == tag[3]) { + return face->data + face->tables[i].offset; + } + } + return NULL; +} + +// ---- UTF-8 ---- +SSINT32 ttf_utf8_decode(const char** p) { + const SUINT8* s = (const SUINT8*)*p; + SUINT8 b0 = s[0]; + if (b0 < 0x80) { (*p)++; return b0; } + if ((b0 & 0xE0) == 0xC0) { (*p) += 2; return ((b0 & 0x1F) << 6) | (s[1] & 0x3F); } + if ((b0 & 0xF0) == 0xE0) { (*p) += 3; return ((b0 & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); } + if ((b0 & 0xF8) == 0xF0) { (*p) += 4; return ((b0 & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); } + (*p)++; + return -1; +} + +// ---- cmap ---- +static const SUINT8* find_cmap_subtable(ttf_face_t* face) { + const SUINT8* cmap = face->cmap; + SUINT16 num = rd16(cmap + 2); + const SUINT8* best = NULL; + SSINT32 best_score = -1; + for (SUINT16 i = 0; i < num; i++) { + const SUINT8* rec = cmap + 4 + i * 8; + SUINT16 platform = rd16(rec + 0); + SUINT16 encoding = rd16(rec + 2); + SUINT32 offset = rd32(rec + 4); + SSINT32 score = -1; + if (platform == 3 && encoding == 10) score = 30; + else if (platform == 3 && encoding == 1) score = 20; + else if (platform == 0 && encoding == 4) score = 10; + else if (platform == 0 && encoding == 3) score = 5; + if (score > best_score) { best_score = score; best = cmap + offset; } + } + return best; +} + +static SUINT16 cmap4_lookup(const SUINT8* sub, SSINT32 cp) { + SUINT16 segCountX2 = rd16(sub + 6); + SUINT16 segCount = segCountX2 / 2; + const SUINT8* endCode = sub + 14; + const SUINT8* startCode = endCode + segCountX2 + 2; + const SUINT8* idDelta = startCode + segCountX2; + const SUINT8* idRangeOff = idDelta + segCountX2; + for (SUINT16 i = 0; i < segCount; i++) { + SUINT16 ec = rd16(endCode + i * 2); + SUINT16 sc = rd16(startCode + i * 2); + if (cp < sc) break; + if (cp <= ec) { + SUINT16 dro = rd16(idRangeOff + i * 2); + if (dro == 0) { + return (SUINT16)((SSINT16)cp + (SSINT16)rd16(idDelta + i * 2)); + } + SUINT16 gidx = rd16(idRangeOff + i * 2 + dro + (cp - sc) * 2); + if (gidx == 0) return 0; + return (SUINT16)((SSINT16)gidx + (SSINT16)rd16(idDelta + i * 2)); + } + } + return 0; +} + +static SUINT16 cmap12_lookup(const SUINT8* sub, SSINT32 cp) { + // Format 12 header: format(2) reserved(2) length(4) language(4) nGroups(4) + SUINT32 nGroups = rd32(sub + 12); + const SUINT8* g = sub + 16; + for (SUINT32 i = 0; i < nGroups; i++) { + SUINT32 sc = rd32(g + 0); + SUINT32 ec = rd32(g + 4); + SUINT32 sG = rd32(g + 8); + if (cp < (SSINT32)sc) break; + if (cp <= (SSINT32)ec) return (SUINT16)(sG + (cp - sc)); + g += 12; + } + return 0; +} + +SUINT16 ttf_cmap_lookup(ttf_face_t* face, SSINT32 cp) { + const SUINT8* sub = find_cmap_subtable(face); + if (!sub) return 0; + SUINT16 fmt = rd16(sub); + if (fmt == 4) return cmap4_lookup(sub, cp); + if (fmt == 12) return cmap12_lookup(sub, cp); + return 0; +} + +// ---- Glyf decode ---- +bool ttf_load_glyph(ttf_face_t* face, SUINT16 glyph_id, + SUINT32 pixel_size_px, ttf_outline_t* out) +{ + mem_set(out, 0, sizeof(*out)); + if (glyph_id >= face->num_glyphs) return false; + + // Loca + SUINT32 off0, off1; + if (face->index_to_loc_format == 0) { + off0 = ((SUINT32)rd16(face->loca + glyph_id * 2)) * 2; + off1 = ((SUINT32)rd16(face->loca + (glyph_id + 1) * 2)) * 2; + } else { + off0 = rd32(face->loca + glyph_id * 4); + off1 = rd32(face->loca + (glyph_id + 1) * 4); + } + if (off0 >= face->glyf_len) return false; + + // Advance width (always readable from hmtx regardless of glyf presence) + { + SUINT16 aw; + if (glyph_id < face->num_long_hor_metrics) { + aw = rd16(face->hmtx + glyph_id * 4); + } else { + aw = rd16(face->hmtx + (face->num_long_hor_metrics - 1) * 4); + } + out->advance = (f26_6)(((SUINT64)aw * (SUINT64)pixel_size_px * 64) / face->units_per_em); + } + + if (off0 == off1) { + // Empty glyph (e.g. space) + return true; + } + + const SUINT8* g = face->glyf + off0; + SSINT16 numContours = rd16s(g + 0); + if (numContours < 0) { + // Composite — unsupported in v1. Keep advance, no outline. + return true; + } + if (numContours == 0) return true; + if (numContours > 64) return false; + + SSINT16 gxMin = rd16s(g + 2); + SSINT16 gyMin = rd16s(g + 4); + SSINT16 gxMax = rd16s(g + 6); + SSINT16 gyMax = rd16s(g + 8); + + const SUINT8* p = g + 10; + SUINT16 endPts[64]; + for (SSINT16 i = 0; i < numContours; i++) { endPts[i] = rd16(p); p += 2; } + SUINT16 numPoints = (SUINT16)(endPts[numContours - 1] + 1); + if (numPoints > 1024) return false; + + SUINT16 instrLen = rd16(p); p += 2; + p += instrLen; + + // Decode flags (with REPEAT) + SUINT8 flags[1024]; + SUINT16 pi = 0; + while (pi < numPoints) { + SUINT8 f = *p++; + flags[pi++] = f; + if (f & 0x08) { + SUINT8 rep = *p++; + for (SUINT16 k = 1; k < rep && pi < numPoints; k++) + flags[pi++] = f; + } + } + + // Decode x-coords into x_raw[] + SSINT32 x_raw[1024]; + { + SSINT32 x = 0; + for (SUINT16 i = 0; i < numPoints; i++) { + SUINT8 f = flags[i]; + if (f & 0x02) { + SUINT8 b = *p++; + x += (f & 0x10) ? b : -(SSINT32)b; + } else if (!(f & 0x10)) { + x += (SSINT16)((SUINT16)p[0] << 8 | p[1]); + p += 2; + } + x_raw[i] = x; + } + } + // Decode y-coords into y_raw[] + SSINT32 y_raw[1024]; + { + SSINT32 y = 0; + for (SUINT16 i = 0; i < numPoints; i++) { + SUINT8 f = flags[i]; + if (f & 0x04) { + SUINT8 b = *p++; + y += (f & 0x20) ? b : -(SSINT32)b; + } else if (!(f & 0x20)) { + y += (SSINT16)((SUINT16)p[0] << 8 | p[1]); + p += 2; + } + y_raw[i] = y; + } + } + + // Scale to pixel space (f26_6) + SUINT64 scale_num = (SUINT64)pixel_size_px * 64; + for (SUINT16 i = 0; i < numPoints; i++) { + out->x[i] = (f26_6)(((SSINT64)x_raw[i] * (SSINT64)scale_num) / face->units_per_em); + out->y[i] = (f26_6)(((SSINT64)y_raw[i] * (SSINT64)scale_num) / face->units_per_em); + out->on_curve[i] = (flags[i] & 0x01) ? 1 : 0; + } + + out->num_contours = (SUINT16)numContours; + out->num_points = numPoints; + SUINT16 start = 0; + for (SSINT16 i = 0; i < numContours; i++) { + out->first[i] = start; + out->last[i] = endPts[i]; + start = endPts[i] + 1; + } + + out->xmin = (SSINT32)(((SSINT64)gxMin * (SSINT64)scale_num) / face->units_per_em); + out->ymin = (SSINT32)(((SSINT64)gyMin * (SSINT64)scale_num) / face->units_per_em); + out->xmax = (SSINT32)(((SSINT64)gxMax * (SSINT64)scale_num) / face->units_per_em); + out->ymax = (SSINT32)(((SSINT64)gyMax * (SSINT64)scale_num) / face->units_per_em); + return true; +} + +// ---- Open / close ---- +ttf_face_t* ttf_open(const void* data, UINTN size) { + if (!data || size < 12) return NULL; + const SUINT8* d = (const SUINT8*)data; + + SUINT32 sfVersion = rd32(d); + if (sfVersion != 0x00010000 && sfVersion != 0x74727565) { + serial_write("ttf: bad sfVersion\n"); + return NULL; + } + SUINT16 numTables = rd16(d + 4); + if (numTables == 0 || numTables > 32) return NULL; + + ttf_face_t* face = (ttf_face_t*)kcalloc(1, sizeof(ttf_face_t)); + if (!face) return NULL; + face->data = d; + face->size = size; + face->num_tables = numTables; + + for (SUINT16 i = 0; i < numTables; i++) { + const SUINT8* r = d + 12 + i * 16; + face->tables[i].tag[0] = r[0]; + face->tables[i].tag[1] = r[1]; + face->tables[i].tag[2] = r[2]; + face->tables[i].tag[3] = r[3]; + face->tables[i].offset = rd32(r + 8); + face->tables[i].length = rd32(r + 12); + } + + const SUINT8* head = find_table(face, "head"); + if (!head) { kfree(face); return NULL; } + face->units_per_em = rd16(head + 18); + face->index_to_loc_format = rd16s(head + 50); + + const SUINT8* hhea = find_table(face, "hhea"); + if (!hhea) { kfree(face); return NULL; } + face->hhea_ascender = rd16s(hhea + 4); + face->hhea_descender = rd16s(hhea + 6); + face->hhea_line_gap = rd16s(hhea + 8); + face->num_long_hor_metrics = rd16(hhea + 34); + + const SUINT8* maxp = find_table(face, "maxp"); + if (!maxp) { kfree(face); return NULL; } + face->num_glyphs = rd16(maxp + 4); + face->max_points = rd16(maxp + 6); + face->max_contours = rd16(maxp + 8); + + const SUINT8* os2 = find_table(face, "OS/2"); + if (os2) { + face->os2_ascender = rd16s(os2 + 68); + face->os2_descender = rd16s(os2 + 70); + face->os2_line_gap = rd16s(os2 + 72); + } else { + face->os2_ascender = face->hhea_ascender; + face->os2_descender = face->hhea_descender; + face->os2_line_gap = face->hhea_line_gap; + } + + const SUINT8* loca = find_table(face, "loca"); + const SUINT8* glyf = find_table(face, "glyf"); + const SUINT8* hmtx = find_table(face, "hmtx"); + if (!loca || !glyf || !hmtx) { kfree(face); return NULL; } + face->loca = loca; + face->glyf = glyf; + face->hmtx = hmtx; + for (SUINT16 i = 0; i < numTables; i++) { + if (face->tables[i].tag[0]=='g'&&face->tables[i].tag[1]=='l'&& + face->tables[i].tag[2]=='y'&&face->tables[i].tag[3]=='f') + face->glyf_len = face->tables[i].length; + if (face->tables[i].tag[0]=='h'&&face->tables[i].tag[1]=='m'&& + face->tables[i].tag[2]=='t'&&face->tables[i].tag[3]=='x') + face->hmtx_len = face->tables[i].length; + } + + face->cmap = find_table(face, "cmap"); + if (!face->cmap) { kfree(face); return NULL; } + + return face; +} + +void ttf_close(ttf_face_t* face) { + if (face) kfree(face); +} + +// ---- Metrics (scaled to pixel_size, returned as 26.6 fp) ---- +SSINT32 ttf_ascender (ttf_face_t* face, SUINT32 px) { + return (SSINT32)(((SSINT64)face->os2_ascender * (SSINT64)px * 64) / face->units_per_em); +} +SSINT32 ttf_descender(ttf_face_t* face, SUINT32 px) { + return (SSINT32)(((SSINT64)face->os2_descender * (SSINT64)px * 64) / face->units_per_em); +} +SSINT32 ttf_line_gap (ttf_face_t* face, SUINT32 px) { + return (SSINT32)(((SSINT64)face->os2_line_gap * (SSINT64)px * 64) / face->units_per_em); +} diff --git a/fonts/ttf/ttf_render.cpp b/fonts/ttf/ttf_render.cpp new file mode 100644 index 0000000..af1f3ce --- /dev/null +++ b/fonts/ttf/ttf_render.cpp @@ -0,0 +1,214 @@ +#include "ttf_internal.h" +#include + +// ---- Outline -> segments ---- +// +// TrueType contour walk: for each pair of consecutive points, emit either +// a line or a quadratic bezier. Two consecutive off-curve points trigger +// synthesis of an on-curve midpoint. +// +// All coordinates remain in 26.6 fp throughout. +void ttf_outline_to_segments(const ttf_outline_t* outline, + ttf_seg_t* segs, SUINT32* num_segs) +{ + *num_segs = 0; + f26_6 syn_x[256]; + f26_6 syn_y[256]; + int n_syn = 0; + + for (SUINT16 ci = 0; ci < outline->num_contours; ci++) { + SUINT16 first = outline->first[ci]; + SUINT16 last = outline->last[ci]; + int n_pts = last - first + 1; + if (n_pts < 2) continue; + + f26_6 lx[1024]; + f26_6 ly[1024]; + SUINT8 on_c[1024]; + for (int i = 0; i < n_pts; i++) { + lx[i] = outline->x[first + i]; + ly[i] = outline->y[first + i]; + on_c[i] = outline->on_curve[first + i]; + } + + // First on-curve point + int start = 0; + while (start < n_pts && !on_c[start]) start++; + if (start == n_pts) start = 0; // all off-curve (rare) — keep going + + int anchor = start; + int pending = -1; + + #define GET_X(i) ((i) < n_pts ? lx[(i)] : syn_x[(i) - n_pts]) + #define GET_Y(i) ((i) < n_pts ? ly[(i)] : syn_y[(i) - n_pts]) + #define PUSH_LINE(a, b) do { \ + ttf_seg_t* __s = &segs[(*num_segs)++]; \ + __s->x0 = GET_X(a); __s->y0 = GET_Y(a); \ + __s->x1 = GET_X(b); __s->y1 = GET_Y(b); \ + __s->cx = __s->x0; __s->cy = __s->y0; __s->is_line = 1; \ + } while (0) + #define PUSH_QUAD(a, c, b) do { \ + ttf_seg_t* __s = &segs[(*num_segs)++]; \ + __s->x0 = GET_X(a); __s->y0 = GET_Y(a); \ + __s->x1 = GET_X(b); __s->y1 = GET_Y(b); \ + __s->cx = GET_X(c); __s->cy = GET_Y(c); __s->is_line = 0; \ + } while (0) + + for (int step = 0; step < n_pts; step++) { + int cur = (start + step) % n_pts; + if (step == 0) { anchor = cur; continue; } + if (on_c[cur]) { + if (pending < 0) { + PUSH_LINE(anchor, cur); + } else { + PUSH_QUAD(anchor, pending, cur); + pending = -1; + } + anchor = cur; + } else { + if (pending < 0) { + pending = cur; + } else { + int syn_idx = n_pts + n_syn; + syn_x[n_syn] = (GET_X(pending) + GET_X(cur)) >> 1; + syn_y[n_syn] = (GET_Y(pending) + GET_Y(cur)) >> 1; + n_syn++; + PUSH_QUAD(anchor, pending, syn_idx); + anchor = syn_idx; + pending = cur; + } + } + } + if (pending >= 0) PUSH_QUAD(anchor, pending, start); + + #undef GET_X + #undef GET_Y + #undef PUSH_LINE + #undef PUSH_QUAD + } +} + +// ---- Integer sqrt (Newton) ---- +static SUINT32 isqrt_u64(SUINT64 n) { + if (n == 0) return 0; + SUINT32 x = (n > 0xFFFFFFFFu) ? 0xFFFFu : (SUINT32)n; + SUINT32 y = (x + 1) >> 1; + while (y < x) { x = y; y = (x + (SUINT32)(n / x)) >> 1; } + return x; +} + +// ---- Scanline fill with subpixel supersampling ---- +// +// For each output row, run N sub-scanlines at offsets (k+0.5)/N. For each +// sub-scanline y, collect all x-intersections, sort, fill alternating +// x-pairs. Sum over N subsamples yields per-pixel coverage in [0, N]. +void ttf_rasterize(const ttf_seg_t* segs, SUINT32 num_segs, + SSINT32 x0, SSINT32 y0, SUINT32 w, SUINT32 h, + SUINT8* coverage, SUINT32 N) +{ + // Clear coverage + for (SUINT32 i = 0; i < w * h; i++) coverage[i] = 0; + + // Intersection x-buf (per scanline, max possible = num_segs) + f26_6 xs[2048]; + if (num_segs > 2048) num_segs = 2048; + + for (SUINT32 row = 0; row < h; row++) { + for (SUINT32 k = 0; k < N; k++) { + // Sub-scanline y, in 26.6, with y = 0 at glyph top + f26_6 sy = (f26_6)((row * 64) + ((k * 2 + 1) * 64) / (SSINT32)(N * 2)); + // ^ y center of subpixel k: (k + 0.5) * 64 / N + // = ((2k+1) * 64) / (2N) + // For N=5: k=0 -> 6.4, k=1 -> 19.2, etc. + + SUINT32 nxs = 0; + for (SUINT32 s = 0; s < num_segs; s++) { + const ttf_seg_t* g = &segs[s]; + if (g->is_line) { + f26_6 y0s = g->y0; + f26_6 y1s = g->y1; + if (y0s == y1s) continue; // horizontal — skip + // t = (sy - y0s) / (y1s - y0s) in (0, 1) + if (((y0s < sy) && (y1s < sy)) || + ((y0s > sy) && (y1s > sy))) continue; + f26_6 t = f26_div(sy - y0s, y1s - y0s); + if (t <= 0 || t >= F26_ONE) continue; + f26_6 x = g->x0 + f26_mul(t, g->x1 - g->x0); + xs[nxs++] = x; + } else { + // Quadratic: y(t) = (1-t)^2 y0 + 2(1-t)t cy + t^2 y1 + // a t^2 + b t + c = 0 + // a = y1 - 2 cy + y0 + // b = 2 (cy - y0) + // c = y0 - sy + SSINT64 a = (SSINT64)g->y1 - 2 * (SSINT64)g->cy + (SSINT64)g->y0; + SSINT64 b = 2 * ((SSINT64)g->cy - (SSINT64)g->y0); + SSINT64 c = (SSINT64)g->y0 - (SSINT64)sy; + SSINT32 t0_valid = 0, t1_valid = 0; + f26_6 t0 = 0, t1 = 0; + if (a == 0) { + // Linear + if (b == 0) continue; + f26_6 t = f26_div((f26_6)(-c), (f26_6)b); + if (t > 0 && t < F26_ONE) { t0 = t; t0_valid = 1; } + } else { + SSINT64 disc = b * b - 4 * a * c; + if (disc < 0) continue; + SUINT32 sq = isqrt_u64((SUINT64)disc); + // 26.6 roots: t = (-b ± sqrt(disc)) / (2a) + // All in 26.6 fp. + // t = (-b ± sq) / (2a); both numerator and denom in 26.6 units + // Use f26_div: t_26_6 = ((-b ± sq) << 6) / (2a) + SSINT64 denom = 2 * a; + if (denom == 0) continue; + SSINT64 num0 = -b + (SSINT64)sq; + SSINT64 num1 = -b - (SSINT64)sq; + t0 = (f26_6)(num0 == 0 ? 0 : (num0 << 6) / denom); + t1 = (f26_6)(num1 == 0 ? 0 : (num1 << 6) / denom); + if (t0 > 0 && t0 < F26_ONE) t0_valid = 1; + if (t1 > 0 && t1 < F26_ONE) t1_valid = 1; + } + if (t0_valid) { + // x(t) = (1-t)^2 x0 + 2(1-t)t cx + t^2 x1 + f26_6 omt = F26_ONE - t0; + f26_6 x = f26_mul(f26_mul(omt, omt), g->x0) + + f26_mul(2 * f26_mul(omt, t0), g->cx) + + f26_mul(f26_mul(t0, t0), g->x1); + xs[nxs++] = x; + } + if (t1_valid) { + f26_6 omt = F26_ONE - t1; + f26_6 x = f26_mul(f26_mul(omt, omt), g->x0) + + f26_mul(2 * f26_mul(omt, t1), g->cx) + + f26_mul(f26_mul(t1, t1), g->x1); + xs[nxs++] = x; + } + } + } + + if (nxs < 2) continue; + // Insertion sort + for (SUINT32 i = 1; i < nxs; i++) { + f26_6 v = xs[i]; SUINT32 j = i; + while (j > 0 && xs[j-1] > v) { xs[j] = xs[j-1]; j--; } + xs[j] = v; + } + // Fill alternating pairs + for (SUINT32 i = 0; i + 1 < nxs; i += 2) { + f26_6 xa = xs[i]; + f26_6 xb = xs[i+1]; + if (xa == xb) continue; + SSINT32 c0 = F26_FLOOR(xa); + SSINT32 c1 = F26_FLOOR(xb); + if (c0 == c1) continue; + if (c0 < 0) c0 = 0; + if (c1 > (SSINT32)w) c1 = (SSINT32)w; + for (SSINT32 x = c0; x < c1; x++) { + if (x >= 0 && x < (SSINT32)w) coverage[row * w + x]++; + } + } + } + } + // (Alpha conversion happens at the caller via N.) + (void)x0; (void)y0; +} diff --git a/graphics/context.cpp b/graphics/context.cpp index 86bf3db..a80c5d4 100644 --- a/graphics/context.cpp +++ b/graphics/context.cpp @@ -21,7 +21,10 @@ void gfx_init(EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP) { void gfx_clear(void) { EFI_GRAPHICS_OUTPUT_BLT_PIXEL black = {0, 0, 0, 0}; - g_gfx.GOP->Blt(g_gfx.GOP, &black, EfiBltVideoFill, 0, 0, 0, 0, g_gfx.hr, g_gfx.vr, 0); + uefi_call_wrapper(g_gfx.GOP->Blt, 10, + g_gfx.GOP, &black, EfiBltVideoFill, + (UINTN)0, (UINTN)0, (UINTN)0, (UINTN)0, + (UINTN)g_gfx.hr, (UINTN)g_gfx.vr, (UINTN)0); } void draw_set_target(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, SUINT32 w, SUINT32 h) { diff --git a/graphics/draw.cpp b/graphics/draw.cpp index 1077a9e..3ba6dcd 100644 --- a/graphics/draw.cpp +++ b/graphics/draw.cpp @@ -34,3 +34,21 @@ void draw_rect(SUINT32 bx, SUINT32 by, SUINT32 ex, SUINT32 ey, for (SUINT32 y = by; y <= ey; y++) draw_pixel(x, y, color); } + +void draw_pixel_alpha(SUINT32 x, SUINT32 y, EFI_GRAPHICS_OUTPUT_BLT_PIXEL color, SUINT8 alpha) { + if (x >= g_draw_target.w || y >= g_draw_target.h) return; + if (alpha == 0) return; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p = g_draw_target.buf + (g_draw_target.w * y) + x; + if (alpha == 255) { + p->Blue = color.Blue; + p->Green = color.Green; + p->Red = color.Red; + p->Reserved = color.Reserved; + return; + } + SUINT32 inv = 255 - alpha; + p->Blue = (SUINT8)((color.Blue * alpha + p->Blue * inv) / 255); + p->Green = (SUINT8)((color.Green * alpha + p->Green * inv) / 255); + p->Red = (SUINT8)((color.Red * alpha + p->Red * inv) / 255); + p->Reserved = color.Reserved; +} diff --git a/include/fonts/ttf.h b/include/fonts/ttf.h new file mode 100644 index 0000000..3c95483 --- /dev/null +++ b/include/fonts/ttf.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +// Opaque font face. Created by ttf_open() over a memory-mapped TTF blob. +typedef struct ttf_face ttf_face_t; + +// Open a TrueType font from a memory buffer. Buffer must remain valid +// for the lifetime of the face. Returns NULL on parse failure. +ttf_face_t* ttf_open(const void* data, UINTN size); + +// Free face. Does NOT free the source buffer. +void ttf_close(ttf_face_t* face); + +// Vertical metrics in 26.6 fixed point, scaled to pixel_size. +SSINT32 ttf_ascender (ttf_face_t* face, SUINT32 pixel_size); +SSINT32 ttf_descender(ttf_face_t* face, SUINT32 pixel_size); +SSINT32 ttf_line_gap (ttf_face_t* face, SUINT32 pixel_size); + +// Measure total advance width of a UTF-8 string at pixel_size (26.6 fp). +SSINT32 ttf_text_width(ttf_face_t* face, const char* utf8, SUINT32 pixel_size); + +// Render a UTF-8 string onto the current draw target. +// (x, y) = baseline origin in TARGET coordinates +// pixel_size = nominal glyph height in pixels (e.g. 16, 24, 48) +// color = foreground; alpha-blended over the current buffer +// Returns the total pen advance (26.6 fp) consumed. +SSINT32 ttf_draw_text(ttf_face_t* face, const char* utf8, + SSINT32 x, SSINT32 y, SUINT32 pixel_size, EFI_GRAPHICS_OUTPUT_BLT_PIXEL color); diff --git a/include/graphics/draw.h b/include/graphics/draw.h index f80db3e..995247c 100644 --- a/include/graphics/draw.h +++ b/include/graphics/draw.h @@ -10,3 +10,6 @@ void global_draw_rect(SUINT32 bx, SUINT32 by, SUINT32 ex, SUINT32 ey, void draw_pixel(SUINT32 x, SUINT32 y, EFI_GRAPHICS_OUTPUT_BLT_PIXEL color); void draw_rect(SUINT32 bx, SUINT32 by, SUINT32 ex, SUINT32 ey, EFI_GRAPHICS_OUTPUT_BLT_PIXEL color); + +// Alpha blend (alpha 0..255). dest = src*alpha/255 + dest*(255-alpha)/255. +void draw_pixel_alpha(SUINT32 x, SUINT32 y, EFI_GRAPHICS_OUTPUT_BLT_PIXEL color, SUINT8 alpha); diff --git a/include/serial.h b/include/serial.h index 0af3796..21748f8 100644 --- a/include/serial.h +++ b/include/serial.h @@ -13,5 +13,5 @@ extern serial_context g_serial; void serial_init(EFI_SERIAL_IO_PROTOCOL *SerialIo); // 初始化串行驱动 void serial_write(String str); // 往串行写string void serial_write_char(char c); // 往串行写char(不推荐使用) -void serial_write_hex(UINTN val, bool fill = true); // 往串行写十六进制数字(fill=false 不补前导0) +void serial_write_hex(UINTN val); // 往串行写十六进制数字 char serial_read_char(); // 读串行 \ No newline at end of file diff --git a/kernel/graphics/layer.cpp b/kernel/graphics/layer.cpp index 3d51a59..651fc49 100644 --- a/kernel/graphics/layer.cpp +++ b/kernel/graphics/layer.cpp @@ -303,12 +303,12 @@ void layer_compositor_task(void) { } // Blit to screen - g_gfx.GOP->Blt( + uefi_call_wrapper(g_gfx.GOP->Blt, 10, g_gfx.GOP, g_back_buffer, EfiBltBufferToVideo, - 0, 0, 0, 0, - hr, vr, 0 + (UINTN)0, (UINTN)0, (UINTN)0, (UINTN)0, + (UINTN)hr, (UINTN)vr, (UINTN)0 ); yield(); diff --git a/kernel/main.cpp b/kernel/main.cpp index f40d0c1..b6d7e0a 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -206,6 +207,7 @@ extern "C" void kernel_main() { serial_write("Sylva: fs_read FAILED: "); serial_write_hex(rd_st); serial_write("\n"); + serial_write("Test done.\n\n"); } else { UINT64 ticks = t1 - t0; UINT64 ms = ticks * (1000 / PIT_TICK_HZ); @@ -217,6 +219,44 @@ extern "C" void kernel_main() { serial_write(" ms ("); serial_write_hex(kbps); serial_write(" KiB/s)\n"); + + // --- TTF demo --- + serial_write("Sylva: ttf_open...\n"); + ttf_face_t* face = ttf_open(ttf_buf, ttf_size); + if (!face) { + serial_write("Sylva: ttf_open FAILED\n"); + } else { + serial_write("Sylva: ttf_open OK\n"); + + // Create an overlay layer for TTF output (sits above the two demo windows) + const UINT32 TL_W = 500, TL_H = 200; + layer_t* text_layer = layer_create("ttf_text", LAYER_TYPE_WINDOW, TL_W, TL_H); + if (text_layer) { + layer_set_z(text_layer, 3); + layer_set_pos(text_layer, 100, 300); + EFI_GRAPHICS_OUTPUT_BLT_PIXEL clear = {0, 0, 0, 0}; + draw_set_target(text_layer->buffer, TL_W, TL_H); + draw_rect(0, 0, TL_W - 1, TL_H - 1, clear); + + // Render at 4 sizes + mixed CJK + EFI_GRAPHICS_OUTPUT_BLT_PIXEL white = {255, 255, 255, 0}; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL yellow = {255, 240, 80, 0}; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL cyan = {80, 220, 255, 0}; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL pink = {255, 160, 200, 0}; + + UINT32 ttf_t0 = pit_get_ticks(); + ttf_draw_text(face, "Hello, Sylva OS!", + 40, 60, 24, white); + ttf_draw_text(face, "欢迎来到Sylva系统!", + 40, 110, 32, yellow); + UINT32 ttf_t1 = pit_get_ticks(); + draw_set_default_target(); + serial_write("Sylva: ttf render in "); + serial_write_hex((UINT64)((ttf_t1 - ttf_t0) * (1000 / PIT_TICK_HZ))); + serial_write(" ms\n"); + } + ttf_close(face); + } kfree(ttf_buf); } serial_write("Test done.\n\n"); diff --git a/kernel/serial.cpp b/kernel/serial.cpp index 5ae224e..ca4ec37 100644 --- a/kernel/serial.cpp +++ b/kernel/serial.cpp @@ -32,7 +32,7 @@ void serial_write(String str) { } } -void serial_write_hex(UINTN val, bool fill) { +void serial_write_hex(UINTN val) { char buf[19]; buf[0] = '0'; buf[1] = 'x'; @@ -47,11 +47,6 @@ void serial_write_hex(UINTN val, bool fill) { tmp[len++] = digit < 10 ? '0' + digit : 'A' + digit - 10; val >>= 4; } - if (fill) { - while (len < 16) { - buf[pos++] = '0'; - } - } for (SSINT32 i = len - 1; i >= 0; i--) { buf[pos++] = tmp[i]; }