generated from macoy/gamelib-project-template
3 changed files with 2 additions and 329 deletions
@ -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 |
|||
(import |
|||
;; 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)) |
|||
character-offset-from-start |
|||
(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 |
|||
FT_RENDER_MODE_LCD |
|||
FT_RENDER_MODE_NORMAL))) |
|||
(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) |
|||
num-components-per-freetype-pixel)) |
|||
|
|||
(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)) |
|||
(addr |
|||
(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)) |
|||
(addr |
|||
(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) |
|||
(break))) |
|||
(when pixel-set |
|||
(if (> num-components-per-freetype-pixel 1) |
|||
(scope |
|||
(each-in-range 3 component |
|||
(set (at component current-pixel-out) (at component current-pixel)))) |
|||
(scope |
|||
(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)) |
|||
character-offset-from-start |
|||
(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 |
|||
(continue)) |
|||
|
|||
(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)) |
|||
character-offset-from-start |
|||
(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 |
|||
(continue)) |
|||
|
|||
(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) |
|||
new-kerning-entry)))))) |
|||
|
|||
(return 0)) |
Loading…
Reference in new issue