Created
December 29, 2016 03:20
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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