Skip to content

Instantly share code, notes, and snippets.

@Bananattack
Created December 29, 2016 03:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Bananattack/0cd6fe79e8d2074f6fe25e4137b8445f to your computer and use it in GitHub Desktop.
Save Bananattack/0cd6fe79e8d2074f6fe25e4137b8445f to your computer and use it in GitHub Desktop.
Game Boy variable width font library. Include and assemble in RGBDS homebrew projects. MIT license.
; Variable Width Font Library
;
; by Andrew G. Crowell (@eggboycolor)
;
; --
;
; Copyright (c) 2016 Andrew G. Crowell
;
; Permission is hereby granted, free of charge, to any person obtaining a copy of
; this software and associated documentation files (the "Software"), to deal in
; the Software without restriction, including without limitation the rights to
; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
; of the Software, and to permit persons to whom the Software is furnished to do
; so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.
;
; --
;
; Requires the following RAM variables:
;
; vwf_buffer_position DB ; holds the current pixel column in the VWF buffer
; vwf_buffer_data DS 32 ; VWF buffer, big enough for 2 tiles, to allow rendering characters that cross horizontal tile boundaries
; vwf_dest_tile_addr_lo DB ; Pointer to the current tile in the tile bitmap data
; vwf_dest_tile_addr_hi DB
; vwf_dest_pixel_y DB ; Vertical position of the tile, used to draw lines that are between vertical tile boundaries
; vwf_dest_line_addr_lo DB ; Pointer to the current line starting offset in tile bitmap data
; vwf_dest_line_addr_hi DB
;
; Requires the following ROM data:
;
; font_bitmap_data ; A tileset bitmap in GB 8x8 format for each of the font glyphs. Organized in ASCII order, starting with the first printable character (space).
; font_glyph_widths ; The pixel widths of each of the font glyphs, one byte per glyph. Width for each glyph should be no greater 8px (one tile).
;
; Usage:
;
; call vwf_reset ; to reset the VWF system for the next page of text
;
; ld hl, tilemap_destination
; call vwf_setup_canvas_direct ; place the VWF tiles on the tilemap at the specified address.
;
; ld hl, text
; call vwf_draw_text_direct ; print the specified text string to the tileset bitmap area reserved for the VWF canvas.
;
; Note:
;
; This doesn't support incremental drawing, only direct access to the GB video memory, which requires the screen to be off.
; That would require knowledge of how the program draws to VRAM during vblank/hblank, so it isn't provided out of the box.
; It would pretty simple to add with some adaptation of the code.
VWF_CANVAS_WIDTH EQU 20 ; The width of the VWF canvas area, in tiles.
VWF_CANVAS_HEIGHT EQU 4 ; The height of the VWF canvas area, in tiles.
VWF_START_TILE_INDEX EQU 1 ; The first tile index to use in the tileset.
VWF_TILESET_BASE_ADDRESS EQU $9000 ; The base address of the background tileset.
; Reset the VWF rendering system.
vwf_reset:
ld a, (VWF_TILESET_BASE_ADDRESS + VWF_START_TILE_INDEX * 16) & $FF
ld [vwf_dest_line_addr_lo], a
ld [vwf_dest_tile_addr_lo], a
ld a, (VWF_TILESET_BASE_ADDRESS + VWF_START_TILE_INDEX * 16) >> 8
ld [vwf_dest_line_addr_hi], a
ld [vwf_dest_tile_addr_hi], a
ld a, 0
ld [vwf_dest_pixel_y], a
call vwf_clear_buffer
ret
; Directly setup the GB tilemap so the VWF canvas area can be displayed to the screen.
; Requires the screen to be disabled during use.
;
; Arguments:
; hl = tilemap start address
vwf_setup_canvas_direct:
ld c, VWF_START_TILE_INDEX
ld d, VWF_CANVAS_HEIGHT
.loop
ld e, VWF_CANVAS_WIDTH
.row_copy
ld a, c
ld [hl+], a
inc c
dec e
jr nz, .row_copy
ld a, l
add a, 32 - VWF_CANVAS_WIDTH
ld l, a
ld a, h
adc a, 0
ld h, a
dec d
jr nz, .loop
ret
; Clear text rendering buffer, and prepare for the next line of text.
;
; Preserves: c
vwf_clear_buffer:
ld hl, vwf_buffer_data
ld b, 32
ld a, 4
ld [vwf_buffer_position], a
ld a, 0
.loop
ld [hl+], a
dec b
jr nz, .loop
ret
; Advance to next line of text.
;
; Preserves: c
vwf_next_line:
call vwf_clear_buffer
ld a, [vwf_dest_pixel_y]
add a, 4
ld [vwf_dest_pixel_y], a
cp a, 8
jr c, .skip
sub a, 8
ld [vwf_dest_pixel_y], a
ld a, [vwf_dest_line_addr_lo]
add a, (VWF_CANVAS_WIDTH * 16) & $FF
ld [vwf_dest_line_addr_lo], a
ld a, [vwf_dest_line_addr_hi]
adc a, (VWF_CANVAS_WIDTH * 16) >> 8
ld [vwf_dest_line_addr_hi], a
.skip
ld a, [vwf_dest_line_addr_lo]
add a, (VWF_CANVAS_WIDTH * 16) & $FF
ld [vwf_dest_line_addr_lo], a
ld [vwf_dest_tile_addr_lo], a
ld a, [vwf_dest_line_addr_hi]
adc a, (VWF_CANVAS_WIDTH * 16) >> 8
ld [vwf_dest_line_addr_hi], a
ld [vwf_dest_tile_addr_hi], a
ret
; Prepare a glyph to be drawn, using the temporary buffer to draw it.
;
; Arguments:
; b = letter
; c = color
vwf_prepare_glyph:
; b = letter - 32
ld a, b
sub a, 32
ld b, a
; d = [font_glyph_widths + (letter - 32)]
ld hl, font_glyph_widths
ld a, l
add a, b
ld l, a
ld a, h
adc a, 0
ld h, a
ld a, [hl]
ld d, a
push de
ld l, b
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
; hl = font_bitmap_data + b
ld a, l
add a, font_bitmap_data & $FF
ld l, a
ld a, h
adc a, font_bitmap_data >> 8
ld h, a
; b = lower plane bitmask (0 or $FF)
ld a, c
and a, 1
rra ; carry = (c & 1)
sbc a, a
ld b, a
; c = upper plane bitmask (0 or $FF)
ld a, c
and a, 2
rra
rra ; carry = (c & 2)
sbc a, a
ld c, a
push hl
ld e, 0
.loop_a
ld a, [hl+]
bit 0, e
jr nz, .upper_a
.lower_a
and a, b
jr .set_masked_a
.upper_a
and a, c
.set_masked_a
ld d, a
ld a, [vwf_buffer_position]
inc a
.shift_a
dec a
jr z, .done_shift_a
srl d
jr .shift_a
.done_shift_a
push hl
ld hl, vwf_buffer_data
ld a, l
add a, e
ld l, a
ld a, h
adc a, 0
ld h, a
ld a, [hl]
or a, d
ld [hl], a
pop hl
inc e
ld a, e
cp a, 16
jr nz, .loop_a
pop hl
pop de
; [buffer_position] += d
ld a, [vwf_buffer_position]
add a, d
ld d, a
push de
; if a >= 8
cp a, 8
jr c, .skip_loop_b
ld e, 0
.loop_b
ld a, [hl+]
bit 0, e
jr nz, .upper_b
.lower_b
and a, b
jr .set_masked_b
.upper_b
and a, c
.set_masked_b
ld d, a
; a = 8 - [vwf_buffer_position] = 8 + (~[vwf_buffer_position] + 1) = ~[vwf_buffer_position] + 9
ld a, [vwf_buffer_position]
cpl
add a, 9
inc a
.shift_b
dec a
jr z, .done_shift_b
sla d
jr .shift_b
.done_shift_b
push hl
ld hl, vwf_buffer_data + 16
ld a, l
add a, e
ld l, a
ld a, h
adc a, 0
ld h, a
ld a, [hl]
or a, d
ld [hl], a
pop hl
inc e
ld a, e
cp a, 16
jr nz, .loop_b
.skip_loop_b
pop de
ld a, d
ld [vwf_buffer_position], a
ret
; Transfer a column of tile data to the GB tileset bitmap, potentially spanning two tiles.
vwf_flush_column_direct:
; Top tile.
ld hl, vwf_buffer_data
ld a, [vwf_dest_pixel_y]
ld e, a
ld a, [vwf_dest_tile_addr_lo]
add a, e
add a, e
ld e, a
ld a, [vwf_dest_tile_addr_hi]
adc a, 0
ld d, a
; a = 8 - [vwf_dest_pixel_y] = 8 + (~[vwf_dest_pixel_y] + 1) = ~[vwf_dest_pixel_y] + 9
ld a, [vwf_dest_pixel_y]
ld b, a
cpl
add a, 9
ld c, a
push bc
.top_tile_loop
ld a, [hl+]
ld [de], a
inc de
ld a, [hl+]
ld [de], a
inc de
dec c
jr nz, .top_tile_loop
pop bc
ld a, b
or a, a
jr z, .skip_bottom_tile
; Bottom tile.
ld a, vwf_buffer_data & $FF
add a, c
add a, c
ld l, a
ld a, vwf_buffer_data >> 8
adc a, 0
ld h, a
ld a, [vwf_dest_tile_addr_lo]
add a, (VWF_CANVAS_WIDTH * 16) & $FF
ld e, a
ld a, [vwf_dest_tile_addr_hi]
adc a, (VWF_CANVAS_WIDTH * 16) >> 8
ld d, a
.bottom_tile_loop
ld a, [hl+]
ld [de], a
inc de
ld a, [hl+]
ld [de], a
inc de
dec b
jr nz, .bottom_tile_loop
.skip_bottom_tile
ret
; Draw a glyph to the GB tileset bitmap, spanning 1 or 2 columns (which can themselves be 1 or 2 rows).
; Called after preparing a buffer with vwf_prepare_glyph.
vwf_flush_glyph_direct:
; Draw first column
call vwf_flush_column_direct
ld a, [vwf_buffer_position]
cp a, 8
jr c, .skip_wide_tile_copy
; Move buffer left one tile, so we can draw this tile, and there's more room for the next tile.
ld de, vwf_buffer_data
ld hl, vwf_buffer_data + 16
ld c, 16
.move_buffer_left
ld a, [hl]
ld [de], a
ld a, 0
ld [hl+], a
inc de
dec c
jr nz, .move_buffer_left
ld a, [vwf_buffer_position]
sub a, 8
ld [vwf_buffer_position], a
; Advance the position in dest
ld a, [vwf_dest_tile_addr_lo]
add a, 16
ld [vwf_dest_tile_addr_lo], a
ld a, [vwf_dest_tile_addr_hi]
adc a, 0
ld [vwf_dest_tile_addr_hi], a
; Draw the second column
call vwf_flush_column_direct
.skip_wide_tile_copy
ret
; Draw a zero-terminated text string directly to the GB tileset bitmap.
; The text can contain newlines \n (10), color codes (1 .. 3), or printable ASCII characters.
; Requires the screen to be disabled during use.
;
; Arguments:
; hl = pointer to source text
vwf_draw_text_direct:
ld c, 3
.loop
ld a, [hl+]
or a, a
ret z
push hl
.handle_newline
cp a, 10 ; newline: a == \n
jr nz, .handle_color_code
call vwf_next_line
jr .done
.handle_color_code
cp a, 4 ; color code: a < 4
jr nc, .handle_printable
ld c, a
jr .done
.handle_printable
cp a, 32 ; printable character: a > 32
jr c, .done
push bc
ld b, a
call vwf_prepare_glyph
call vwf_flush_glyph_direct
pop bc
.done
pop hl
jr .loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment