diff --git a/.gitignore b/.gitignore index df0be19..8530553 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ *.png *.out res/mai.data +tools/ttf-convert \ No newline at end of file diff --git a/Makefile b/Makefile index c67521b..33e4a38 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ vdp1/normal_sprite.elf: vdp1/normal_sprite.o res/mai00.data.o res/mai.data.pal.o vdp1/normal_sprite_color_bank.elf: vdp1/normal_sprite_color_bank.o res/mai00.data.o res/mai.data.pal.o -vdp1/kana.elf: vdp1/kana.o res/ipafont.bin.o sh/lib1funcs.o +vdp1/kana.elf: vdp1/kana.o res/ipapgothic.font.bin.o sh/lib1funcs.o res/mai.data: res/mai00.data res/mai01.data res/mai02.data res/mai03.data res/mai04.data res/mai05.data res/mai06.data res/mai07.data res/mai08.data res/mai09.data res/mai10.data res/mai11.data res/mai12.data res/mai13.data res/mai14.data res/mai15.data cat $(sort $^) > $@ @@ -44,11 +44,23 @@ vdp1/normal_sprite_animated.elf: vdp1/normal_sprite_animated.o res/mai.data.o re smpc/input_intback.elf: smpc/input_intback.o sh/lib1funcs.o +res/dejavusansmono.font.bin: tools/ttf-convert + ./tools/ttf-convert $(shell fc-match -f '%{file}' 'DejaVu Sans Mono') $@ + +res/ipapgothic.font.bin: tools/ttf-convert + ./tools/ttf-convert $(shell fc-match -f '%{file}' 'IPAPGothic') $@ + +smpc/input_keyboard.elf: smpc/input_keyboard.o sh/lib1funcs.o res/dejavusansmono.font.bin.o + +games/tetris.elf: games/tetris.o sh/lib1funcs.o + + # clean clean: clean-sh clean-sh: find -P \ -not -path './saturn/*' \ + -not -path './tools/*' \ -regextype posix-egrep \ -regex '.*\.(iso|o|bin|elf|cue)$$' \ -exec rm {} \; diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..e654c0d --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,15 @@ +CFLAGS = -Og -Wall -Wextra -Werror -ggdb -Wno-error=unused-parameter -Wno-error=unused-variable +CFLAGS += $(shell pkg-config --cflags freetype2) +LDFLAGS = $(shell pkg-config --libs freetype2) + +all: ttf-convert + +%.o: %.cpp + $(CXX) $(CFLAGS) -c $< -o $@ + +%: %.o + $(CXX) $(LDFLAGS) $< -o $@ + +.SUFFIXES: + +# /usr/share/fonts/OTF/ipagp.ttf diff --git a/tools/dejavusans.font b/tools/dejavusans.font new file mode 100644 index 0000000..0913295 Binary files /dev/null and b/tools/dejavusans.font differ diff --git a/tools/ttf-convert.cpp b/tools/ttf-convert.cpp new file mode 100644 index 0000000..926d0a9 --- /dev/null +++ b/tools/ttf-convert.cpp @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H + +/* +int load_bitmap_char(FT_Face face, FT_ULong char_code, uint8_t * buf) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + printf("FT_Load_Glyph %s\n", FT_Error_String(error)); + return 0; + } + + assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP); + printf("num_grays %d\n", face->glyph->bitmap.num_grays); + printf("pitch %d\n", face->glyph->bitmap.pitch); + printf("width %d\n", face->glyph->bitmap.width); + assert(face->glyph->bitmap.width == 16); + printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows); + assert(face->glyph->bitmap.rows == 8 || (face->glyph->bitmap.rows % 8) == 0); + + for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) { + uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch]; + uint8_t row_out = 0; + for (int x = 0; x < face->glyph->bitmap.width; x++) { + int bit; + if (x < (int)face->glyph->bitmap.width) { + bit = (row[x / 8] >> (7 - (x % 8))) & 1; + } else { + bit = 0; + } + fprintf(stderr, bit ? "█" : " "); + row_out |= (bit << x); + } + fprintf(stderr, "\n"); + //buf[y] = row_out; + } + + return face->glyph->bitmap.rows * face->glyph->bitmap.pitch; +} +*/ + +// metrics are 26.6 fixed point +struct glyph_metrics { + int32_t width; + int32_t height; + int32_t horiBearingX; + int32_t horiBearingY; + int32_t horiAdvance; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_metrics)) == ((sizeof (int32_t)) * 5)); + +struct glyph_bitmap { + uint32_t offset; + uint32_t rows; + uint32_t width; + uint32_t pitch; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph_bitmap)) == ((sizeof (uint32_t)) * 4)); + +struct glyph { + glyph_bitmap bitmap; + glyph_metrics metrics; +} __attribute__ ((packed)); + +static_assert((sizeof (glyph)) == ((sizeof (glyph_bitmap)) + (sizeof (glyph_metrics)))); + +struct font { + uint32_t glyph_index; + uint32_t bitmap_offset; + int32_t height; + int32_t max_advance; +} __attribute__ ((packed)); + +static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 4)); + +int32_t +load_outline_char(const FT_Face face, + const FT_ULong char_code, + glyph * glyph, + uint8_t * glyph_bitmaps, + const uint32_t bitmap_offset) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + printf("FT_Load_Glyph %s\n", FT_Error_String(error)); + return -1; + } + + assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE); + + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (error) { + printf("FT_Render_Glyph %s\n", FT_Error_String(error)); + return -1; + } + + if (!(face->glyph->bitmap.pitch > 0)) { + printf("%lx : pitch == 0\n", char_code); + return 0; + } + + uint32_t pitch = (face->glyph->bitmap.pitch + 8 - 1) & -8; + uint32_t bitmap_size = face->glyph->bitmap.rows * pitch; + + for (uint32_t y = 0; y < face->glyph->bitmap.rows; y++) { + for (uint32_t x = 0; x < face->glyph->bitmap.width; x++) { + uint8_t i = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x]; + glyph_bitmaps[bitmap_offset + (y * pitch + x)] = i >> 3; + } + } + + //memcpy(&glyph_bitmaps[bitmap_offset], face->glyph->bitmap.buffer, bitmap_size); + + glyph_bitmap& bitmap = glyph->bitmap; + bitmap.offset = bswap_32(bitmap_offset); + bitmap.rows = bswap_32(face->glyph->bitmap.rows); + bitmap.width = bswap_32(face->glyph->bitmap.width); + bitmap.pitch = bswap_32(pitch); + //printf("%lx: %d %d\n", char_code, pitch, face->glyph->bitmap.width); + assert((pitch % 8) == 0); + + glyph_metrics& metrics = glyph->metrics; + metrics.width = bswap_32(face->glyph->metrics.width); + metrics.height = bswap_32(face->glyph->metrics.height); + metrics.horiBearingX = bswap_32(face->glyph->metrics.horiBearingX); + metrics.horiBearingY = bswap_32(face->glyph->metrics.horiBearingY); + metrics.horiAdvance = bswap_32(face->glyph->metrics.horiAdvance); + + return bitmap_size; +} + +struct range { + uint32_t start; + uint32_t end; +}; + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc < 3) { + fprintf(stderr, "usage: %s [font-file-path] [output-file-path]\n", argv[0]); + return -1; + } + + error = FT_Init_FreeType(&library); + if (error) { + fprintf(stderr, "FT_Init_FreeType\n"); + return -1; + } + + error = FT_New_Face(library, argv[1], 0, &face); + if (error) { + fprintf(stderr, "FT_New_Face\n"); + return -1; + } + + /* + error = FT_Select_Size(face, 0); + if (error) { + fprintf(stderr, "FT_Select_Size: %s %d\n", FT_Error_String(error), error); + return -1; + } + */ + + error = FT_Set_Pixel_Sizes (face, 0, 30); + if (error) { + fprintf(stderr, "FT_Set_Pixel_Sizes: %s %d\n", FT_Error_String(error), error); + return -1; + } + + uint32_t start = 0x3000; + uint32_t end = 0x30ff; + //uint32_t start = 0x20; + //uint32_t end = 0x7f; + + glyph glyphs[(end - start) + 1]; + uint8_t glyph_bitmaps[1024 * 1024]; + memset(glyph_bitmaps, 0x00, 1024 * 1024); + uint32_t bitmap_offset = 0, glyph_index = 0; + int32_t bitmap_size; + + for (uint32_t char_code = start; char_code <= end; char_code++) { + bitmap_size = load_outline_char(face, + char_code, + &glyphs[glyph_index], + &glyph_bitmaps[0], + bitmap_offset); + if (bitmap_size < 0) { + fprintf(stderr, "load_outline_char error\n"); + return -1; + } + + bitmap_offset += bitmap_size; + assert(bitmap_offset < (sizeof (glyph_bitmaps))); + glyph_index++; + } + + font font; + font.glyph_index = bswap_32(glyph_index); + font.bitmap_offset = bswap_32(bitmap_offset); + font.height = bswap_32(face->size->metrics.height); + font.max_advance = bswap_32(face->size->metrics.max_advance); + + FT_Done_FreeType(library); + + printf("bitmap_offset %u\n", bitmap_offset); + printf("glyph_index %u\n", glyph_index); + + FILE * out = fopen(argv[2], "w"); + + fwrite(reinterpret_cast(&font), (sizeof (font)), 1, out); + fwrite(reinterpret_cast(&glyphs[0]), (sizeof (glyph)), glyph_index, out); + fwrite(reinterpret_cast(&glyph_bitmaps[0]), (sizeof (uint8_t)), bitmap_offset, out); + + fclose(out); +} diff --git a/vdp1/kana.cpp b/vdp1/kana.cpp index 7c8c949..2ef8aab 100644 --- a/vdp1/kana.cpp +++ b/vdp1/kana.cpp @@ -2,7 +2,7 @@ #include "vdp2.h" #include "vdp1.h" -extern void * _ipafont_data_start __asm("_binary_res_ipafont_bin_start"); +extern void * _ipafont_data_start __asm("_binary_res_ipapgothic_font_bin_start"); constexpr inline uint16_t rgb15_gray(uint32_t intensity) { @@ -11,7 +11,7 @@ constexpr inline uint16_t rgb15_gray(uint32_t intensity) | ((intensity & 31) << 0 ); // red } -void color_palette(uint32_t colors, uint32_t color_bank) +void vdp2_color_palette(uint32_t colors, uint32_t color_bank) { /* generate a palette of 32 grays */ @@ -80,6 +80,17 @@ struct glyph { glyph_metrics metrics; } __attribute__ ((packed)); +static_assert((sizeof (glyph)) == ((sizeof (glyph_bitmap)) + (sizeof (glyph_metrics)))); + +struct font { + uint32_t glyph_index; + uint32_t bitmap_offset; + int32_t height; + int32_t max_advance; +} __attribute__ ((packed)); + +static_assert((sizeof (font)) == ((sizeof (uint32_t)) * 4)); + template void copy(T * dst, const T * src, int32_t n) noexcept { @@ -146,19 +157,16 @@ void main() constexpr uint32_t colors = 256; constexpr uint32_t color_bank = 0; // completely random and arbitrary value - uint8_t * data8 = reinterpret_cast(&_ipafont_data_start); - uint32_t * data32 = reinterpret_cast(&_ipafont_data_start); + uint8_t * data = reinterpret_cast(&_ipafont_data_start); - const uint32_t glyph_index = data32[0]; - const int32_t bitmap_offset = data32[1]; - const int32_t face_height = data32[2]; - const glyph * glyphs = reinterpret_cast(&data32[3]); + const font * font = reinterpret_cast(&data[0]); + const glyph * glyphs = reinterpret_cast(&data[(sizeof (struct font))]); // there are three 32-bit fields before the start of `glyphs` - const uint8_t * glyph_bitmaps = reinterpret_cast(&data8[((sizeof (int32_t)) * 3) + glyph_index * (sizeof (glyph))]); + const uint8_t * glyph_bitmaps = &data[(sizeof (struct font)) + ((sizeof (struct glyph)) * font->glyph_index)]; - top = character_address = pixel_data(top, glyph_bitmaps, bitmap_offset); + top = character_address = pixel_data(top, glyph_bitmaps, font->bitmap_offset); - color_palette(colors, color_bank); + vdp2_color_palette(colors, color_bank); // For color bank color, COLR is concatenated bitwise with pixel data. See // Figure 6.17 in the VDP1 manual. color_address = color_bank << 8; @@ -220,10 +228,10 @@ void main() cmd_ix, _string, ((sizeof (_string)) / (sizeof (_string[0]))) - 1); - + vdp1.vram.cmd[cmd_ix].CTRL = CTRL__END; cmd_ix++; - + // start drawing (execute the command list) on every frame vdp1.reg.PTMR = PTMR__PTM__FRAME_CHANGE; }