Browse Source

Move font code into GameLib

Macoy Madson 3 months ago
  1. 2
  2. 213
  3. 116


@ -1 +1 @@
Subproject commit 90abbc4fe00e31e59a2155236e20ddf01984c3d3
Subproject commit 86c249c715b3e2ebcdebb9b382bdd022847d79cc


@ -1,213 +0,0 @@
;; FontAtlas.cake: Cache FreeType-rendered fonts into a font atlas and kerning table
;; TODO: Support UTF-8 by using non char-sized keys
;; GameLib
"FreeType.cake" "Dictionary.cake")
(c-import "<stdio.h>"
&with-decls "<stdint.h>")
(defstruct glyph-entry
key char
x uint16_t
y uint16_t
width uint16_t
height uint16_t
to-origin-left int16_t
to-origin-top int16_t
advance-x int16_t)
(defstruct kerning-entry
key uint16_t ;; first character << 8, second character
x int16_t
;; Unused
y int16_t)
(defstruct font-atlas
glyph-lookup-table (addr glyph-entry) ;; dictionary
kerning-lookup-table (addr kerning-entry) ;; dictionary
pixel-buffer (addr (unsigned char)) ;; malloc'd
width uint16_t
height uint16_t
;; Default line spacing (baseline-to-baseline
font-height uint16_t)
(defun free-font-atlas (font-atlas-to-free (addr font-atlas))
(dict-free (path font-atlas-to-free > glyph-lookup-table))
(dict-free (path font-atlas-to-free > kerning-lookup-table))
(free (path font-atlas-to-free > pixel-buffer)))
(defun font-atlas-make-character-pair (character-a char
character-b char
&return uint16_t)
(return (bit-or (bit-shift-<< character-a 8) character-b)))
;; Returns 0 for success or anything else for failure
(defun build-font-atlas (font-face (addr (const (unsigned char)))
font-face-size (unsigned int)
device-dpi (unsigned int)
character-height-points (unsigned char)
enable-subpixel-antialiasing bool
font-atlas-out (addr font-atlas) &return int)
(var freetype-library FT_Library)
(var result int
(FT_Init_FreeType (addr freetype-library)))
(freetype-check-result-or-return result "initializing FreeType")
(defer (FT_Done_FreeType freetype-library))
(var typeface FT_Face)
(var face-index int 0)
(set result (FT_New_Memory_Face freetype-library font-face font-face-size
face-index (addr typeface)))
(unless (= result FT_Err_Ok)
(fprintf stderr "error: encountered error %d while loading font\n" result)
(return 1))
(defer (FT_Done_Face typeface))
(set result (FT_Set_Char_Size
typeface ;; handle to face object
0 ;; char_width in 1/64th of points
(* character-height-points 64) ;; char_height in 1/64th of points
device-dpi ;; horizontal device resolution
device-dpi)) ;; vertical device resolution
(freetype-check-result-or-return result "setting character size")
(defstruct ascii-glyph-range
start-character char
end-character-inclusive char)
;; This is a bit silly
(var ranges-to-render (array ascii-glyph-range)
(array (array 'a' 'z') (array 'A' 'Z') (array '0' '9') (array ' ' '/') (array ':' '@')
(array '[' '`') (array '{' '~')))
(var atlas-width uint16_t 1024)
(var atlas-height uint16_t 1024)
(var num-components-per-pixel char 4)
(var pixel-buffer-size (unsigned int) (* atlas-width atlas-height num-components-per-pixel))
(set (path font-atlas-out > pixel-buffer)
(type-cast (malloc pixel-buffer-size)
(addr (unsigned char))))
(memset (path font-atlas-out > pixel-buffer) 0 pixel-buffer-size)
(set (path font-atlas-out > width) atlas-width)
(set (path font-atlas-out > height) atlas-height)
(set (path font-atlas-out > font-height) (bit-shift->> (path typeface > size > metrics . height) 6))
(var atlas-write-x uint16_t 0)
(var atlas-write-y uint16_t 0)
(var atlas-tallest-character-this-row uint16_t 0)
(each-in-array ranges-to-render range-index
(var range (addr ascii-glyph-range) (addr (at range-index ranges-to-render)))
(each-in-range (- (+ 1 (path range > end-character-inclusive)) (path range > start-character))
(var character char (+ character-offset-from-start (path range > start-character)))
(var glyph-index int (FT_Get_Char_Index typeface character))
(unless glyph-index
(fprintf stderr "error: could not find glyph '%c' in font\n" character)
(return 1))
(set result (FT_Load_Glyph typeface glyph-index FT_LOAD_DEFAULT))
(freetype-check-result-or-return result "loading glyph")
;; One slot for the whole face
(set result (FT_Render_Glyph (path typeface > glyph)
(? enable-subpixel-antialiasing
(freetype-check-result-or-return result "rendering glyph")
(var num-components-per-freetype-pixel (unsigned char) (? enable-subpixel-antialiasing 3 1))
(var num-rows int (path typeface > glyph > bitmap . rows))
(var num-columns int (/ (path typeface > glyph > bitmap . width)
(when (> num-rows atlas-tallest-character-this-row)
(set atlas-tallest-character-this-row num-rows))
(when (> (+ num-columns atlas-write-x) atlas-width)
(set atlas-write-x 0)
(set atlas-write-y (+ atlas-write-y atlas-tallest-character-this-row))
(set atlas-tallest-character-this-row num-rows)
(when (> (+ atlas-write-y atlas-tallest-character-this-row) atlas-height)
(fprintf stderr "error: ran out of space in atlas size %dx%d\n" atlas-width atlas-height)
(return 1)))
(var new-glyph-entry glyph-entry (array 0))
(set (field new-glyph-entry key) character)
(set (field new-glyph-entry x) atlas-write-x)
(set (field new-glyph-entry y) atlas-write-y)
(set (field new-glyph-entry width) num-columns)
(set (field new-glyph-entry height) num-rows)
(set (field new-glyph-entry to-origin-left) (path typeface > glyph > bitmap_left))
(set (field new-glyph-entry to-origin-top) (path typeface > glyph > bitmap_top))
(set (field new-glyph-entry advance-x) (bit-shift->> (path typeface > glyph > advance . x) 6))
(dict-set-struct (path font-atlas-out > glyph-lookup-table) new-glyph-entry)
;; TODO: This can become a memcpy if we can get FreeType to render to our same format
(each-in-range num-rows row
(each-in-range num-columns column
(var current-pixel (addr (unsigned char))
(at (+ (* num-components-per-freetype-pixel column)
(* row (path typeface > glyph > bitmap . pitch)))
(path typeface > glyph > bitmap . buffer))))
(var current-pixel-out (addr (unsigned char))
(at (* num-components-per-pixel
(+ (+ column atlas-write-x) (* (+ atlas-write-y row) atlas-width)))
(path font-atlas-out > pixel-buffer))))
(var pixel-set bool false)
(each-in-range num-components-per-freetype-pixel i
(when (> (at i current-pixel) 0)
(set pixel-set true)
(when pixel-set
(if (> num-components-per-freetype-pixel 1)
(each-in-range 3 component
(set (at component current-pixel-out) (at component current-pixel))))
(each-in-range 3 component
(set (at component current-pixel-out) (deref current-pixel)))))
(set (at 3 current-pixel-out) 255))))
;; Wrapping happens after we know what the next glyph's dimensions are
(set atlas-write-x (+ atlas-write-x num-columns))))
;; Build kerning lookup table
(each-in-array ranges-to-render range-index-a
(var range-a (addr ascii-glyph-range) (addr (at range-index-a ranges-to-render)))
(each-in-range (- (+ 1 (path range-a > end-character-inclusive)) (path range-a > start-character))
(var character-a char (+ character-offset-from-start (path range-a > start-character)))
(var glyph-index-a int (FT_Get_Char_Index typeface character-a))
(unless glyph-index-a
(each-in-array ranges-to-render range-index-b
(var range-b (addr ascii-glyph-range) (addr (at range-index-b ranges-to-render)))
(each-in-range (- (+ 1 (path range-b > end-character-inclusive)) (path range-b > start-character))
(var character-b char (+ character-offset-from-start (path range-b > start-character)))
;; If this is slow, it might make sense to cache it in the outer loop
(var glyph-index-b int (FT_Get_Char_Index typeface character-b))
(unless glyph-index-b
(var kerning FT_Vector)
(set result (FT_Get_Kerning typeface glyph-index-a glyph-index-b
FT_KERNING_DEFAULT (addr kerning)))
(freetype-check-result-or-return result "Getting kerning value")
(when (or (field kerning x) (field kerning y))
(var new-kerning-entry kerning-entry (array 0))
(var character-pair uint16_t (font-atlas-make-character-pair
character-a character-b))
(set (field new-kerning-entry key) character-pair)
(set (field new-kerning-entry x) (bit-shift->> (field kerning x) 6))
(set (field new-kerning-entry y) (bit-shift->> (field kerning y) 6))
(dict-set-struct (path font-atlas-out > kerning-lookup-table)
(return 0))


@ -5,18 +5,16 @@
(add-cakelisp-search-directory "src")
;; Cakelisp
;; GameLib
"Introspection.cake" "SDL.cake" "DynamicArray.cake" "Dictionary.cake" "DataBundle.cake"
"Math.cake" "SDLFontAtlas.cake" "FreeType.cake")
(c-import "<stdio.h>"
&with-decls "<stdint.h>")
(var s-draw-atlases bool false)
(var s-enable-kerning bool true) ;; F2
(var s-enable-debug-overlay bool false) ;; F1
(bundle-file s-start-ubuntu-regular-font s-end-ubuntu-regular-font
@ -36,7 +34,6 @@
(define-keybind s-toggle-kerning-keybind (array SDL_SCANCODE_F2))
(define-keybind s-toggle-debug-overlay-keybind (array SDL_SCANCODE_F1))
(define-keybind s-toggle-fullscreen-keybind (array SDL_SCANCODE_F11))
@ -777,115 +774,6 @@
(unless (>= (deref value-a) (deref value-b))
(set operation-index (find-end-index operations-to-run operation-index))))))))
;; Text rendering
(defun-local render-string (renderer (addr SDL_Renderer) font (addr font-atlas) font-texture (addr SDL_Texture)
x int y int str (addr (const char)))
(var write-x int x)
(var write-y int y)
(var tab-width int 100)
(scope ;; Get tab width based on space advance
(var space-key char ' ')
(var glyph (addr glyph-entry) (dict-ptr-at (path font > glyph-lookup-table) space-key))
(when glyph
(set tab-width (* 4 (path glyph > advance-x)))))
(var line-height int (path font > font-height))
(each-char-in-string-const str current-char
((= (deref current-char) '\r')
((= (deref current-char) '\n')
(set write-y (+ line-height write-y))
(set write-x x)
((= (deref current-char) '\t')
(set write-x (+ write-x tab-width))
(var search-key char (deref current-char))
(var glyph (addr glyph-entry) (dict-ptr-at (path font > glyph-lookup-table) search-key))
(unless glyph ;; fallback
(set search-key '?')
(set glyph (dict-ptr-at (path font > glyph-lookup-table) search-key)))
(unless glyph ;; even the fallback is missing!
(var source-rectangle SDL_Rect
(array (path glyph > x)
(path glyph > y)
(path glyph > width)
(path glyph > height)))
(var destination-rectangle SDL_Rect
(+ write-x (path glyph > to-origin-left))
(- write-y (path glyph > to-origin-top))
(path glyph > width)
(path glyph > height)))
(SDL_RenderCopy renderer font-texture
(addr source-rectangle) (addr destination-rectangle))
(set write-x (+ write-x (path glyph > advance-x)))
(when (and s-enable-kerning (+ 1 current-char))
(var character-pair uint16_t (font-atlas-make-character-pair
(deref current-char) (at 1 current-char)))
(var kerning (addr kerning-entry)
(dict-ptr-at (path font > kerning-lookup-table) character-pair))
(when kerning
(set write-x (+ write-x (path kerning > x)))))))
(defun-local make-font-atlas-and-texture (renderer (addr SDL_Renderer)
font-atlas-out (addr font-atlas) font-texture-out (addr (addr SDL_Texture))
font-data (addr (const (unsigned char))) font-data-size (unsigned int)
device-dpi (unsigned int)
font-size-points (unsigned char)
&return bool)
(var enable-subpixel-antialiasing bool true)
(unless (= 0 (build-font-atlas font-data font-data-size
(return false))
(var font-surface (addr SDL_Surface)
(path font-atlas-out > pixel-buffer)
(path font-atlas-out > width)
(path font-atlas-out > height)
32 ;; bit depth of each pixel (RGBA)
(* (path font-atlas-out > width) 4) ;; pitch (width of a row in bytes)
;; Because of how SDL reads pixels in, this does seem to be endian-dependent
;; I don't fully understand why
(defer (SDL_FreeSurface font-surface)) ;; We don't need this after making the texture
(set (deref font-texture-out) (SDL_CreateTextureFromSurface renderer font-surface))
(unless (deref font-texture-out)
(return false)))
(return true))
(defun-local sdl-texture-from-bmp-data (renderer (addr SDL_Renderer)
data-start (addr (unsigned char))
data-end (addr (unsigned char))
&return (addr SDL_Texture))
(var surface (addr SDL_Surface)
(SDL_LoadBMP_RW (SDL_RWFromMem data-start (- data-end data-start))
;; freesrc (free the RWOps)
(unless surface
(return null))
(defer (SDL_FreeSurface surface))
(var texture (addr SDL_Texture) (SDL_CreateTextureFromSurface renderer surface))
(unless texture
(return null))
(return texture))
;; Main
@ -1143,8 +1031,6 @@
(var slide (addr slide-data) (addr (at current-slide-index (field presentation slides))))
;; Debug keys
(when (keybind-tapped (addr s-toggle-kerning-keybind) (addr s-key-states))
(set s-enable-kerning (not s-enable-kerning)))
(when (keybind-tapped (addr s-toggle-debug-overlay-keybind) (addr s-key-states))
(set s-enable-debug-overlay (not s-enable-debug-overlay)))