diff --git a/CMakeLists.txt b/CMakeLists.txt index 4062422df..b68002ab2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -842,6 +842,8 @@ target_sources(${PROJECT_NAME} PRIVATE core/hw/naomi/card_reader.cpp core/hw/naomi/touchscreen.h core/hw/naomi/touchscreen.cpp + core/hw/naomi/printer.h + core/hw/naomi/printer.cpp core/hw/pvr/elan.cpp core/hw/pvr/elan.h core/hw/pvr/elan_struct.h @@ -920,6 +922,12 @@ target_sources(${PROJECT_NAME} PRIVATE core/hw/sh4/sh4_sched.h core/hw/sh4/storeq.cpp) +cmrc_add_resources(flycast-resources + fonts/printer_ascii8x16.bin + fonts/printer_ascii12x24.bin + fonts/printer_kanji16x16.bin + fonts/printer_kanji24x24.bin) + target_sources(${PROJECT_NAME} PRIVATE core/imgread/cdi.cpp core/imgread/chd.cpp diff --git a/core/emulator.cpp b/core/emulator.cpp index fe45dedd1..fda603414 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -741,10 +741,6 @@ void loadGameSpecificSettings() if (settings.content.gameId.empty()) return; } - else - { - settings.content.gameId = naomi_game_id; - } // Default per-game settings loadSpecialSettings(); diff --git a/core/hw/maple/maple_jvs.cpp b/core/hw/maple/maple_jvs.cpp index e0bd4b830..02affbb11 100644 --- a/core/hw/maple/maple_jvs.cpp +++ b/core/hw/maple/maple_jvs.cpp @@ -23,6 +23,7 @@ #include "stdclass.h" #include "cfg/option.h" #include "network/output.h" +#include "hw/naomi/printer.h" #include #include @@ -1510,6 +1511,7 @@ void maple_naomi_jamma::serialize(Serializer& ser) const { maple_base::serialize(ser); ser << crazy_mode; + ser << hotd2p; ser << jvs_repeat_request; ser << jvs_receive_length; ser << jvs_receive_buffer; @@ -1523,6 +1525,10 @@ void maple_naomi_jamma::deserialize(Deserializer& deser) { maple_base::deserialize(deser); deser >> crazy_mode; + if (deser.version() >= Deserializer::V35) + deser >> hotd2p; + else + hotd2p = settings.content.gameId == "hotd2p"; deser >> jvs_repeat_request; deser >> jvs_receive_length; deser >> jvs_receive_buffer; @@ -1674,7 +1680,7 @@ u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_ou break; default: - if (jvs_cmd >= 0x20 && jvs_cmd <= 0x38) // Read inputs and more + if ((jvs_cmd >= 0x20 && jvs_cmd <= 0x38) || jvs_cmd == 0x74) // Read inputs and more { LOGJVS("JVS Node %d: ", node_id); u32 buttons[4] {}; @@ -1910,6 +1916,25 @@ u32 jvs_io_board::handle_jvs_message(u8 *buffer_in, u32 length_in, u8 *buffer_ou cmdi += 4; break; + case 0x74: // Custom: used to read and write the board serial port (touch de uno) + { + u32 len = buffer_in[cmdi + 1]; + for (u32 i = 0; i < len; i++) + printer::print(buffer_in[cmdi + 2 + i]); + + cmdi += len + 2; + JVS_STATUS1(); // report + // data + // 00 hardware error + // 01 head up error + // 02 Vp Volt error + // 03 auto cutter error + // 04 head temp error + // 3* paper end error + JVS_OUT(0xf); // printer ok + } + break; + default: INFO_LOG(MAPLE, "JVS: Unknown input type %x", buffer_in[cmdi]); JVS_OUT(2); // report byte: command error diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp index 0d0ea9379..e1a3578bc 100644 --- a/core/hw/naomi/naomi.cpp +++ b/core/hw/naomi/naomi.cpp @@ -18,6 +18,7 @@ #include "network/output.h" #include "hw/sh4/modules/modules.h" #include "rend/gui.h" +#include "printer.h" #include @@ -846,3 +847,42 @@ void initDriveSimSerialPipe() pipe.reset(); serial_setPipe(&pipe); } + +G2PrinterConnection g2PrinterConnection; + +u32 G2PrinterConnection::read(u32 addr, u32 size) +{ + if (addr == STATUS_REG_ADDR) + { + u32 ret = printerStat; + printerStat |= 1; + DEBUG_LOG(NAOMI, "Printer status == %x", ret); + return ret; + } + else + { + INFO_LOG(NAOMI, "Unhandled G2 Ext read<%d> at %x", size, addr); + return 0; + } +} + +void G2PrinterConnection::write(u32 addr, u32 size, u32 data) +{ + switch (addr) + { + case DATA_REG_ADDR: + for (u32 i = 0; i < size; i++) + printer::print((char)(data >> (i * 8))); + break; + + case STATUS_REG_ADDR: + DEBUG_LOG(NAOMI, "Printer status = %x", data); + printerStat &= ~1; + break; + + default: + INFO_LOG(NAOMI, "Unhandled G2 Ext write<%d> at %x: %x", size, addr, data); + break; + } +} + diff --git a/core/hw/naomi/naomi.h b/core/hw/naomi/naomi.h index 2a6b457a8..b04e18623 100644 --- a/core/hw/naomi/naomi.h +++ b/core/hw/naomi/naomi.h @@ -33,19 +33,39 @@ void initDriveSimSerialPipe(); u32 libExtDevice_ReadMem_A0_006(u32 addr, u32 size); void libExtDevice_WriteMem_A0_006(u32 addr, u32 data, u32 size); +class G2PrinterConnection +{ +public: + u32 read(u32 addr, u32 size); + void write(u32 addr, u32 size, u32 data); + + static constexpr u32 STATUS_REG_ADDR = 0x1018000; + static constexpr u32 DATA_REG_ADDR = 0x1010000; + +private: + u32 printerStat = 0xf; +}; +extern G2PrinterConnection g2PrinterConnection; + extern Multiboard *multiboard; + //Area 0 , 0x01000000- 0x01FFFFFF [G2 Ext. Device] static inline u32 g2ext_readMem(u32 addr, u32 size) { + if (addr == G2PrinterConnection::STATUS_REG_ADDR || addr == G2PrinterConnection::DATA_REG_ADDR) + return g2PrinterConnection.read(addr, size); if (multiboard != nullptr) return multiboard->readG2Ext(addr, size); INFO_LOG(NAOMI, "Unhandled G2 Ext read<%d> at %x", size, addr); return 0; } + static inline void g2ext_writeMem(u32 addr, u32 data, u32 size) { - if (multiboard != nullptr) + if (addr == G2PrinterConnection::STATUS_REG_ADDR || addr == G2PrinterConnection::DATA_REG_ADDR) + g2PrinterConnection.write(addr, size, data); + else if (multiboard != nullptr) multiboard->writeG2Ext(addr, size, data); else INFO_LOG(NAOMI, "Unhandled G2 Ext write<%d> at %x: %x", size, addr, data); diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index 9be56b1be..915743c48 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -40,11 +40,11 @@ #include "card_reader.h" #include "naomi_flashrom.h" #include "touchscreen.h" +#include "printer.h" Cartridge *CurrentCartridge; bool bios_loaded = false; -char naomi_game_id[33]; InputDescriptors *NaomiGameInputs; u8 *naomi_default_eeprom; @@ -413,7 +413,7 @@ static void loadMameRom(const char *filename, LoadProgress *progress) md5.getDigest(settings.network.md5.game); } // Default game name if ROM boot id isn't found - strcpy(naomi_game_id, game->name); + settings.content.gameId = game->name; } catch (...) { delete CurrentCartridge; @@ -595,12 +595,21 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress) atomiswaveForceFeedback = false; RomBootID bootId; - if (CurrentCartridge->GetBootId(&bootId)) + if (CurrentCartridge->GetBootId(&bootId) + && (!memcmp(bootId.boardName, "NAOMI", 5) || !memcmp(bootId.boardName, "Naomi2", 6))) { std::string gameId = trim_trailing_ws(std::string(bootId.gameTitle[0], &bootId.gameTitle[0][32])); - if (strlen(gameId.c_str()) > 0) - strcpy(naomi_game_id, gameId.c_str()); - NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s] region %x players %x vertical %x", naomi_game_id, (u8)bootId.country, bootId.cabinet, bootId.vertical); + if (gameId == "SAMPLE GAME MAX LONG NAME-") + { + // Use better game names + if (!strcmp(CurrentCartridge->game->name, "sgdrvsim")) + gameId = "SEGA DRIVING SIMULATOR"; + else if (!strcmp(CurrentCartridge->game->name, "dragntr3")) + gameId = "DRAGON TREASURE 3"; + } + if (!gameId.empty()) + settings.content.gameId = gameId; + NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s] region %x players %x vertical %x", settings.content.gameId.c_str(), (u8)bootId.country, bootId.cabinet, bootId.vertical); if (gameId == "INITIAL D" || gameId == "INITIAL D Ver.2" @@ -616,7 +625,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress) } else if (gameId == "THE KING OF ROUTE66" || gameId == "CLUB KART IN JAPAN" - || gameId == "SAMPLE GAME MAX LONG NAME-") // Driving Simulator + || gameId == "SEGA DRIVING SIMULATOR") { if (settings.naomi.drivingSimSlave == 0) initMidiForceFeedback(); @@ -626,10 +635,17 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress) { touchscreen::init(); } + if (gameId == " TOUCH DE UNOH -------------" + || gameId == " TOUCH DE UNOH 2 -----------" + // only for F355 Deluxe + || (gameId == "F355 CHALLENGE JAPAN" && !strcmp(CurrentCartridge->game->name, "f355"))) + { + printer::init(); + } #ifdef NAOMI_MULTIBOARD // Not a multiboard game but needs the same desktop environment - if (gameId == "SAMPLE GAME MAX LONG NAME-") // Driving Simulator + if (gameId == "SEGA DRIVING SIMULATOR") { initDriveSimSerialPipe(); @@ -659,7 +675,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress) #endif } else - NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", naomi_game_id); + NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", settings.content.gameId.c_str()); } void naomi_cart_ConfigureEEPROM() @@ -678,6 +694,7 @@ void naomi_cart_ConfigureEEPROM() void naomi_cart_Close() { touchscreen::term(); + printer::term(); delete CurrentCartridge; CurrentCartridge = nullptr; NaomiGameInputs = nullptr; @@ -689,6 +706,7 @@ void naomi_cart_serialize(Serializer& ser) if (CurrentCartridge != nullptr) CurrentCartridge->Serialize(ser); touchscreen::serialize(ser); + printer::serialize(ser); } void naomi_cart_deserialize(Deserializer& deser) @@ -696,6 +714,7 @@ void naomi_cart_deserialize(Deserializer& deser) if (CurrentCartridge != nullptr && (!settings.platform.isAtomiswave() || deser.version() >= Deserializer::V10_LIBRETRO)) CurrentCartridge->Deserialize(deser); touchscreen::deserialize(deser); + printer::deserialize(deser); } int naomi_cart_GetPlatform(const char *path) diff --git a/core/hw/naomi/naomi_cart.h b/core/hw/naomi/naomi_cart.h index 17253b4cc..ccc5d555a 100644 --- a/core/hw/naomi/naomi_cart.h +++ b/core/hw/naomi/naomi_cart.h @@ -140,7 +140,6 @@ void naomi_cart_ConfigureEEPROM(); void naomi_cart_serialize(Serializer& ser); void naomi_cart_deserialize(Deserializer& deser); -extern char naomi_game_id[]; extern u8 *naomi_default_eeprom; extern Cartridge *CurrentCartridge; diff --git a/core/hw/naomi/naomi_roms_eeprom.h b/core/hw/naomi/naomi_roms_eeprom.h index 4dfd5a496..9d01c311c 100644 --- a/core/hw/naomi/naomi_roms_eeprom.h +++ b/core/hw/naomi/naomi_roms_eeprom.h @@ -561,25 +561,25 @@ static u8 vtennisg_eeprom_dump[] { 0x00, 0x00, }; -// printer disabled, touchscreen calibrated +// touchscreen calibrated static u8 tduno_eeprom_dump[] { 0x5f, 0xdc, 0x10, 0x42, 0x41, 0x50, 0x31, 0x09, 0x00, 0x1a, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, 0x5f, 0xdc, 0x10, 0x42, 0x41, 0x50, 0x31, 0x09, 0x00, 0x1a, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, - 0xc4, 0x10, 0x18, 0x18, 0xc4, 0x10, 0x18, 0x18, 0x14, 0x00, 0x14, 0x00, 0x16, 0x00, 0x16, 0x00, 0x97, 0x6d, - 0x80, 0x3f, 0xa4, 0x4a, 0x80, 0x3f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, - 0x16, 0x00, 0x16, 0x00, 0x97, 0x6d, 0x80, 0x3f, 0xa4, 0x4a, 0x80, 0x3f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0xe4, 0x28, 0x18, 0x18, 0xe4, 0x28, 0x18, 0x18, 0x14, 0x00, 0x14, 0x00, 0x16, 0x00, 0x16, 0x00, 0x97, 0x6d, + 0x80, 0x3f, 0xa4, 0x4a, 0x80, 0x3f, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, + 0x16, 0x00, 0x16, 0x00, 0x97, 0x6d, 0x80, 0x3f, 0xa4, 0x4a, 0x80, 0x3f, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -// printer disabled, touchscreen calibrated +// touchscreen calibrated static u8 tduno2_eeprom_dump[] { 0x8a, 0xd3, 0x10, 0x42, 0x42, 0x48, 0x32, 0x09, 0x00, 0x1a, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, 0x8a, 0xd3, 0x10, 0x42, 0x42, 0x48, 0x32, 0x09, 0x00, 0x1a, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, - 0xd1, 0xbd, 0x18, 0x18, 0xd1, 0xbd, 0x18, 0x18, 0x28, 0x00, 0x28, 0x00, 0x2a, 0x00, 0x29, 0x00, 0xbe, 0xeb, - 0x80, 0x3f, 0xaa, 0xa4, 0x80, 0x3f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, - 0x2a, 0x00, 0x29, 0x00, 0xbe, 0xeb, 0x80, 0x3f, 0xaa, 0xa4, 0x80, 0x3f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0xb2, 0x67, 0x18, 0x18, 0xb2, 0x67, 0x18, 0x18, 0x28, 0x00, 0x28, 0x00, 0x2a, 0x00, 0x29, 0x00, 0xbe, 0xeb, + 0x80, 0x3f, 0xaa, 0xa4, 0x80, 0x3f, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, + 0x2a, 0x00, 0x29, 0x00, 0xbe, 0xeb, 0x80, 0x3f, 0xaa, 0xa4, 0x80, 0x3f, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x4c, 0x27, 0xab, 0x37, 0x7e, 0x03, 0x49, 0x54, 0x78, 0x45, 0x61, 0x7a, 0xa3, 0x52, 0xfc, 0xca, 0xcb, 0x64, 0xbc, 0xe5, 0xa5, 0x68, 0x16, 0x42, 0x69, 0x4e, 0x4c, 0xe8, 0xfc, 0x82, 0x81, 0x78, 0xa6, 0x25, 0x63, 0x46, diff --git a/core/hw/naomi/printer.cpp b/core/hw/naomi/printer.cpp new file mode 100644 index 000000000..0b38efb45 --- /dev/null +++ b/core/hw/naomi/printer.cpp @@ -0,0 +1,1106 @@ +/* + Copyright 2023 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#include "types.h" +#include "printer.h" +#include "serialize.h" +#include "rend/gui.h" +#include +#include +#include +#ifdef STANDALONE_TEST +#define STB_IMAGE_WRITE_IMPLEMENTATION +#undef INFO_LOG +#define INFO_LOG(t, s, ...) printf(s "\n", __VA_ARGS__) +#else +#include +CMRC_DECLARE(flycast); +#endif +#include + +namespace printer +{ + +class BitmapWriter +{ +public: + BitmapWriter(int printerWidth) : printerWidth(printerWidth) + { +#ifndef STANDALONE_TEST + try { + cmrc::embedded_filesystem fs = cmrc::flycast::get_filesystem(); + cmrc::file fontFile = fs.open("fonts/printer_ascii8x16.bin"); + ascii8x16 = (const u8 *)fontFile.begin(); + fontFile = fs.open("fonts/printer_ascii12x24.bin"); + ascii12x24 = (const u8 *)fontFile.begin(); + fontFile = fs.open("fonts/printer_kanji16x16.bin"); + kanji16x16 = (const u8 *)fontFile.begin(); + fontFile = fs.open("fonts/printer_kanji24x24.bin"); + kanji24x24 = (const u8 *)fontFile.begin(); + } catch (const std::system_error& e) { + ERROR_LOG(NAOMI, "Failed to load a printer font: %s", e.what()); + throw; + } + +#else + loadFont("printer_ascii8x16.bin", (u8 **)&ascii8x16); + loadFont("printer_ascii12x24.bin", (u8 **)&ascii12x24); + loadFont("printer_kanji16x16.bin", (u8 **)&kanji16x16); + loadFont("printer_kanji24x24.bin", (u8 **)&kanji24x24); +#endif + } + + template + void print(T text) + { + if (text == '\n' || text == '\r') + { + linefeed(); + return; + } + const int hScale = 1 + doubleHeight; + const int wScale = 1 + doubleWidth; + const u8 *glyph; + int width; + int height; + bool msb = true; + if (sizeof(T) == 1 && customCharsEnabled && (u8)text < customChars.size() && customChars[(u8)text].width > 0) + { + glyph = &customChars[text].data[0]; + width = customChars[text].width; // / wScale; // FIXME tduno2 hack, breaks tduno + height = customChars[text].height; + msb = msbBitmap; + } + else + { + glyph = getGlyph(text); + width = sizeof(T) == 1 ? + bigFont ? 12 : 8 + : bigFont ? 24 : 16; + height = bigFont ? 24 : 16; + } + + if (penx + width * wScale > printerWidth) + linefeed(); + maxLineHeight = std::max(maxLineHeight, height * hScale + maxUnderline); + u8 *pen = getPenPosition(height * hScale); + for (int y = 0; y < height; y++) + { + for (int dbh = 0; dbh < hScale; dbh++) + { + for (int x = 0; x < width && pen <= &page.back(); x++) + { + const u8 *src = glyph + x / 8; + bool b = *src & (msb ? 0x80 >> (x % 8) : 1 << (x % 8)); + b ^= reversed; + if (b) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + } + pen++; + if (wScale > 1 && pen <= &page.back()) + { + if (b) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + } + pen++; + } + } + pen += printerWidth - width * wScale; + } + glyph += (width + 7) / 8; + } + if (!reversed) // reversed has priority over underline + for (int y = 0; y < maxUnderline; y++) + { + u8 *pen = getPenPosition(height * hScale + y) + printerWidth * (height * hScale + y); + const int ulwidth = width * wScale + hspace * (sizeof(T) == 1 ? 1 : 2); + for (int x = 0; x < ulwidth && pen <= &page.back(); x++) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + pen++; + } + } + penx += width * wScale; + if (reversed) + { + // Fill inter-char space + // FIXME only if there's another char on the same line + u8 *pen = getPenPosition(height * hScale); + for (int y = 0; y < height * hScale; y++) + { + for (int x = 0; x < hspace && pen <= &page.back(); x++) + { + if (xorMode) + *pen++ ^= 0xff; + else + *pen++ |= 0xff; + } + pen += printerWidth - hspace; + } + } + penx += hspace * (sizeof(T) == 1 ? 1 : 2); + printBufferEmpty = false; + } + + void printImage(int hpos, int width, int height, const u8 *data) + { + //printf("printImage: hpos %d w %d h %d\n", hpos, width, height); + const int savePenx = penx; + penx = hpos; + u8 *pen = getPenPosition(height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + bool b = data[x / 8] & (msbBitmap ? 0x80 >> (x % 8) : 1 << (x % 8)); + if (b) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + } + pen++; + } + data += (width + 7) / 8; + pen += printerWidth - width; + } + penx = savePenx; + if (_advancePenOnBitmap) + // F355 and tduno[2] seem to handle bitmap differently. tduno[2] needs the pen to advance when printing bitmaps, + // and will do reverse feed to print over it. F355 doesn't reverse feed so the pen must not advance. + peny += height; + } + + void linefeed(int dots) + { + if (!printBufferEmpty) + linefeed(); + if (dots > 0) + getPenPosition(dots); + penx = 0; + peny = std::max(0, peny + dots); + } + + void selectFont(bool big) { + bigFont = big; + } + void setReversed(bool enable) { + reversed = enable; + } + void setHSpace(int dots) { + hspace = dots; + } + void setVSpace(int dots) { + vspace = dots; + } + void setDoubleWidth(bool enabled) { + doubleWidth = enabled; + } + void setDoubleHeight(bool enabled) { + doubleHeight = enabled; + } + void setBitmapMSB(bool enabled) { + msbBitmap = enabled; + } + void setXorMode(bool enabled) { + xorMode = enabled; + } + void setCustomChar(char code, int width, int height, const u8 *data) + { + if ((u8)code >= customChars.size()) + customChars.resize(code + 1); + CustomChar& cchar = customChars[code]; + cchar.width = std::min(width, 48); + cchar.height = height; + cchar.data.resize((cchar.width + 7) / 8 * height); + if (cchar.width == width) { + memcpy(cchar.data.data(), data, cchar.data.size()); + } + else + { + for (int y = 0; y < height; y++) + memcpy(cchar.data.data() + cchar.width / 8 * y, + data + (width + 7) / 8 * y, + cchar.width / 8); + } + } + void enableCustomChars(bool enable) { + customCharsEnabled = enable; + } + void drawRuledLine(int from, int to) + { + if (from > to) + std::swap(from, to); + if (ruledLine.empty()) + ruledLine.resize(printerWidth); + for (int d = from; d <= to && d < (int)ruledLine.size(); d++) + ruledLine[d] = 0xff; + } + void clearRuledLine() { + ruledLine.clear(); + } + void printRuledLine() + { + if (!ruledLineMode) + { + linefeed(1); + return; + } + if (!printBufferEmpty) + linefeed(); + penx = 0; + u8 *pen = getPenPosition(1); + for (int x = 0; x < printerWidth && x < (int)ruledLine.size(); x++) + { + if (ruledLine[x] != 0) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + } + pen++; + } + peny++; + } + void setRuledLineMode(bool enabled) { + ruledLineMode = enabled; + } + + void advancePenOnBitmap() { + _advancePenOnBitmap = true; + } + + void setUnderline(int dots) { + underline = dots; + maxUnderline = std::max(maxUnderline, underline); + } + + bool isDirty() const { + return lines > 0; + } + bool save(const std::string& filename) + { + for (u8& b : page) + b = 0xff - b; + stbi_write_png(filename.c_str(), printerWidth, lines, 1, &page[0], printerWidth); + return true; + } + + void serialize(Serializer& ser) + { + ser << printerWidth; + ser << (u32)page.size(); + ser.serialize(&page[0], page.size()); + ser << lines; + ser << penx; + ser << peny; + ser << vspace; + ser << hspace; + ser << bigFont; + ser << reversed; + ser << doubleWidth; + ser << doubleHeight; + ser << msbBitmap; + ser << maxLineHeight; + ser << xorMode; + ser << _advancePenOnBitmap; + ser << printBufferEmpty; + + ser << customCharsEnabled; + + ser << (u32)customChars.size(); + for (const CustomChar& cc : customChars) + { + ser << cc.width; + ser << cc.height; + ser << (u32)cc.data.size(); + ser.serialize(&cc.data[0], cc.data.size()); + } + + ser << (u32)ruledLine.size(); + ser.serialize(&ruledLine[0], ruledLine.size()); + ser << ruledLineMode; + ser << underline; + ser << maxUnderline; + } + void deserialize(Deserializer& deser) + { + deser >> printerWidth; + u32 size; + deser >> size; + page.resize(size); + deser.deserialize(&page[0], page.size()); + deser >> lines; + deser >> penx; + deser >> peny; + deser >> vspace; + deser >> hspace; + deser >> bigFont; + deser >> reversed; + deser >> doubleWidth; + deser >> doubleHeight; + deser >> msbBitmap; + deser >> maxLineHeight; + deser >> xorMode; + deser >> _advancePenOnBitmap; + deser >> printBufferEmpty; + + deser >> customCharsEnabled; + + deser >> size; + customChars.resize(size); + for (CustomChar& cc : customChars) + { + deser >> cc.width; + deser >> cc.height; + deser >> size; + cc.data.resize(size); + deser.deserialize(&cc.data[0], cc.data.size()); + } + + deser >> size; + ruledLine.resize(size); + deser.deserialize(&ruledLine[0], ruledLine.size()); + deser >> ruledLineMode; + deser >> underline; + deser >> maxUnderline; + } + +private: + void linefeed() + { + if (maxLineHeight == 0) + { + const int height = bigFont ? 24 : 16; + const int hScale = 1 + doubleHeight; + maxLineHeight = height * hScale; + } + penx = 0; + const int fromY = peny; + peny += maxLineHeight + maxUnderline + vspace; + maxUnderline = 0; + maxLineHeight = 0; + printBufferEmpty = true; + if (ruledLineMode) + { + getPenPosition(0); + for (int y = fromY; y < peny; y++) + { + u8 *pen = &page[y * printerWidth]; + for (int x = 0; x < printerWidth && x < (int)ruledLine.size(); x++) + { + if (ruledLine[x] != 0) + { + if (xorMode) + *pen ^= 0xff; + else + *pen |= 0xff; + } + pen++; + } + } + } + } + + const u8 *getGlyph(char c) + { + const u8 *glyph; + int glyphSize; + if (bigFont) + { + glyph = ascii12x24; + glyphSize = 2 * 24; + } + else + { + glyph = ascii8x16; + glyphSize = 16; + } + if ((uint8_t)c < ' ') + return glyph; + else + return glyph + glyphSize * ((uint8_t)c - ' '); + } + + const u8 *getGlyph(wchar_t c) + { + const u8 *glyph; + int glyphSize; + if (bigFont) + { + glyph = kanji24x24; + glyphSize = 3 * 24; + } + else + { + glyph = kanji16x16; + glyphSize = 2 * 16; + } + if (c == ' ') + return glyph; + uint8_t plane = c >> 8; + c &= 0xff; + if (plane < 0x21 || plane > 0x7e || c < 0x21 || c > 0x7e) + return glyph; + else + return glyph + glyphSize * (1 + (plane - 0x21) * 94 + (c - 0x21)); + } + + u8 *getPenPosition(int height) + { + if (peny + height > lines) + addLines(peny + height - lines); + return &page[peny * printerWidth + penx]; + } + + void addLines(int count) + { + lines += count; + page.insert(page.end(), printerWidth * count, 0); + } + + void loadFont(const char *fname, u8 **data) + { + FILE *f = fopen(fname, "rb"); + if (!f) + perror(fname); + else + { + fseek(f, 0, SEEK_END); + size_t sz = ftell(f); + fseek(f, 0, SEEK_SET); + *data = (u8 *)malloc(sz); + fread(*data, sz, 1, f); + fclose(f); + } + } + + int printerWidth = 920; + std::vector page; + int lines = 0; + int penx = 0; + int peny = 0; + int vspace = 28; + int hspace = 2; + bool bigFont = false; + bool reversed = false; + bool doubleWidth = false; + bool doubleHeight = false; + bool msbBitmap = false; + int maxLineHeight = 0; + bool xorMode = false; + bool _advancePenOnBitmap = false; + bool printBufferEmpty = false; + + bool customCharsEnabled = false; + struct CustomChar + { + int width; + int height; + std::vector data; + }; + std::vector customChars; + + std::vector ruledLine; + bool ruledLineMode = false; + int underline = 0; + int maxUnderline = 0; + + const u8 *ascii8x16; + const u8 *ascii12x24; + const u8 *kanji16x16; + const u8 *kanji24x24; +}; + +// +// Nichipri NP413-FA esc/pos printer emulation +// Nichipri Industrial is now Nippon Primex +// +class ThermalPrinter +{ +public: + void print(char c) + { + if (expectedDataBytes > 0) + { + dataBytes.push_back(c); + if (expectedDataBytes == dataBytes.size()) + { + switch (state) + { + case ESC: + executeEscCommand(); + break; + case DC2: + executeDc2Command(); + break; + case DC3: + executeDc3Command(); + break; + default: + assert(false); + break; + } + if (expectedDataBytes == dataBytes.size()) + { + expectedDataBytes = 0; + if (!dc3Lock || state != DC3) + state = Default; + dataBytes.clear(); + } + } + } + else + { + switch (state) + { + case ESC: + escCommand(c); + break; + + case DC2: + dc2Command(c); + break; + + case DC3: + dc3Command(c); + break; + + default: + switch (c) + { + case 0x1b: // ESC + state = ESC; + break; + case 0x12: // DC2 + state = DC2; + break; + case 0x13: // DC3 + state = DC3; + break; + case '\r': + case '\n': + //printf("\\r\n"); + getWriter().print('\r'); + break; + case 0x18: // CAN Erase print buffer + case '\0': + // ignore + break; + default: + if (kanji) + { + if (kanjiByte0 == 0) + { + if (c <= ' ') + getWriter().print(c); + else + kanjiByte0 = c; + } + else + { + wchar_t code = ((u8)kanjiByte0 << 8) | (u8)c; + //printf("[%x]", code); fflush(stdout); + getWriter().print(code); + kanjiByte0 = 0; + } + } + else + { + //if (c >= ' ') + // printf("%c", c); + //else + // printf("[%02x]", (u8)c); + getWriter().print(c); + } + break; + } + } + } + } + + void serialize(Serializer& ser) + { + ser << state; + ser << dc3Lock; + ser << curCommand; + ser << expectedDataBytes; + + ser << (u32)dataBytes.size(); + ser.serialize(&dataBytes[0], dataBytes.size()); + + ser << kanji; + ser << kanjiByte0; + + ser << (u32)bitmaps.size(); + for (const Bitmap& bm : bitmaps) + { + ser << bm.width; + ser << bm.height; + ser << (u32)bm.data.size(); + ser.serialize(&bm.data[0], bm.data.size()); + } + + if (bitmapWriter == nullptr) + ser << false; + else + { + ser << true; + bitmapWriter->serialize(ser); + } + } + + void deserialize(Deserializer& deser) + { + deser >> state; + deser >> dc3Lock; + deser >> curCommand; + deser >> expectedDataBytes; + + u32 size; + deser >> size; + dataBytes.resize(size); + deser.deserialize(&dataBytes[0], dataBytes.size()); + + deser >> kanji; + deser >> kanjiByte0; + + deser >> size; + bitmaps.resize(size); + for (Bitmap& bm : bitmaps) + { + deser >> bm.width; + deser >> bm.height; + deser >> size; + bm.data.resize(size); + deser.deserialize(&bm.data[0], bm.data.size()); + } + + bool b; + deser >> b; + if (!b) + bitmapWriter.reset(); + else + getWriter().deserialize(deser); + } + +private: + void escCommand(char c) + { + curCommand = c; + switch (c) + { + case '3': // smallest LF pitch set (same as A) + case 'A': // line space setting (n dots) + case 'J': // smallest pitch line feed + case 'j': // Reverse paper feed after printing (n dots) + case ' ': // Character right space set (n dots) + case 'I': // Reversed b/w character on/off + case 'W': // set/reset double-width chars + case 'w': // set/reset double-height chars + case '=': // Image LSB/MSB selection + case '#': // Overlay mode selection (0: OR, 1: XOR) + case '-': // Set/reset underline + expectedDataBytes = 1; + break; + case 'i': // Full cut + //printf("<<>>\n"); + state = Default; + if (bitmapWriter && bitmapWriter->isDirty()) + { + std::string s = settings.content.gameId + "-results.png"; + bitmapWriter->save(s); + bitmapWriter.reset(); + s = "Print out saved to " + s; + gui_display_notification(s.c_str(), 5000); + } + break; + case 'K': // Set Kanji mode + kanji = true; + state = Default; + break; + case 'H': // Cancel Kanji mode + kanji = false; + state = Default; + break; + case '2': + getWriter().setVSpace(16); + state = Default; + break; + case 'E': // set/reset Emphasized Characters FIXME doesn't seem to take a param in tduno + case 'F': // Undocumented?? + state = Default; + break; + default: + // unhandled but need to ignore data... + INFO_LOG(NAOMI, "Unhandled ESC [%c]\n", c); + state = Default; + break; + } + } + + void executeEscCommand() + { + switch (curCommand) + { + case '3': + case 'A': + getWriter().setVSpace((u8)dataBytes[0]); + break; + case 'I': + getWriter().setReversed(dataBytes[0] & 1); + break; + case 'J': + getWriter().linefeed((u8)dataBytes[0]); + break; + case ' ': + getWriter().setHSpace((u8)dataBytes[0] & 0x7f); + break; + case 'W': + getWriter().setDoubleWidth(dataBytes[0] & 1); + break; + case 'w': + getWriter().setDoubleHeight(dataBytes[0] & 1); + break; + case '-': + getWriter().setUnderline(dataBytes[0] & 7); + break; + case '=': + getWriter().setBitmapMSB((dataBytes[0] & 1) != 0); + break; + case '#': + getWriter().setXorMode(dataBytes[0] & 1); + break; + case 'j': + getWriter().linefeed(-(u8)dataBytes[0]); + break; + default: + //printf("ESC %c", curCommand); + //for (auto c : dataBytes) + // printf(" %d", (u8)c); + //printf("\n"); + break; + } + } + + void dc2Command(char c) + { + curCommand = c; + switch (c) + { + case 'R': // Read memory switch (should take a param but apparently doesn't ?) + case 'V': // Raster bit image printing (w h [bytes ...]) FIXME tduno: w=0, h=0 and no data (?) or no param at all, f355: 1 param only (0) + state = Default; + break; + case 'D': // Allocate/free download char area + case 'F': // Font size selection. 0: Font B (8×16, 16×16), 1: Font A (12×24, 24×24) + case 'G': // Allocate/free external char area + case 'U': // Deletes the bitmap specified by n and releases the memory area used + case 'p': // Select out-of-paper error ??? probably not + case '~': // Set print density setting (n% with n in [50, 200]) + case 'O': // set/reset optional font printing + expectedDataBytes = 1; + break; + case 'S': // Select a bitmap (n1) and specify the print position in the horizontal direction (n2 * 8 dots) + expectedDataBytes = 2; + break; + case 'T': // Create bitmap: n w yl yh [data...] where n is the bitmap id [0, 255], w is the width in bytes [1, 127], + // yl + yh * 256 is the number of lines [1, 1023] + case 'm': // Mark position detection: s nl nh where n is the max feed amount until detection [0, 65535] + // s & 3 == 0: Feed the paper in the forward direction until it passes the marking position. + // s & 3 == 1: Feed the paper in the forward direction to the marking position. + // s & 3 == 2: Feed the paper in the opposite direction until it passes the marking position. + // s & 3 == 3: Feed the paper in the opposite direction to the marking position. + expectedDataBytes = 3; + break; + case 'P': // Register Optional Font? + expectedDataBytes = 4; + break; + default: + // unhandled but need to ignore data... + INFO_LOG(NAOMI, "Unhandled DC2 [%c]\n", c); + state = Default; + break; + } + } + + void executeDc2Command() + { + switch (curCommand) + { + case 'F': + getWriter().selectFont(dataBytes[0] & 1); + break; + case 'O': + getWriter().enableCustomChars(dataBytes[0] & 1); + break; + case 'P': + // Register optional font: s e y x [data...] k + // s: start char code [20, 7e] + // e: end char code [20, 7e] + // y: vertical dots [1, 7f] + // x: horizontal dots [1, 7f] + // d: data bytes + // k: data bytes count = INT((y + 7) / 8) × x × (e - s + 1) + if (expectedDataBytes == 4) + { + expectedDataBytes += ((dataBytes[2] & 0x7f) + 7) / 8 * (dataBytes[3] & 0x7f) * ((u8)dataBytes[1] - (u8)dataBytes[0] + 1); + } + else + { + const char s = dataBytes[0] & 0x7f; + const char e = dataBytes[1] & 0x7f; + const int y = dataBytes[2] & 0x7f; + const int x = dataBytes[3] & 0x7f; + const u8 *p = (u8 *)&dataBytes[4]; + const int charSize = (x + 7) / 8 * y; + for (char c = s; c <= e; c++) + { + getWriter().setCustomChar(c, x, y, p); + p += charSize; + } + //printf("Characters %c to %c: %d x %d\n", s, e, x, y); + } + break; + case 'S': + { + const size_t idx = (u8)dataBytes[0]; + const int hpos = (u8)dataBytes[1] * 8; + if (idx < bitmaps.size()) + getWriter().printImage(hpos, bitmaps[idx].width, bitmaps[idx].height, &bitmaps[idx].data[0]); + } + break; + case 'T': + { + // bitmap + if (expectedDataBytes == 3) + { + expectedDataBytes += (u8)dataBytes[1] * (u8)dataBytes[2]; + } + else + { + const size_t idx = (u8)dataBytes[0]; + const int w = (dataBytes[1] & 0x7f) * 8; + const int h = std::min((u8)dataBytes[2] | ((u8)dataBytes[3] << 8), 1023); + //printf("bitmap[%zd] %d x %d\n", idx, w, h); + // B&W 1bit + std::vector bitmap(w / 8 * h); + memcpy(bitmap.data(), &dataBytes[4], bitmap.size()); + if (bitmaps.size() <= idx) + bitmaps.resize(idx + 1); + bitmaps[idx].width = w; + bitmaps[idx].height = h; + std::swap(bitmaps[idx].data, bitmap); + } + } + break; + case 'U': + { + const u32 idx = (u8)dataBytes[0]; + if (idx < bitmaps.size()) + { + bitmaps[idx].width = bitmaps[idx].height = 0; + bitmaps[idx].data.clear(); + } + } + break; + case 'p': + // This very unlikely related to bitmaps but is used to distinguish between f355 and tduno[2] handling of bitmaps. + if (dataBytes[0] != 0) + getWriter().advancePenOnBitmap(); + break; + default: + //printf("DC2 %c", curCommand); + //for (auto c : dataBytes) + // printf(" %d", (u8)c); + //printf("\n"); + break; + } + } + void dc3Command(char c) + { + curCommand = c; + switch (c) + { + case '(': + dc3Lock = true; + break; + case ')': + dc3Lock = false; + state = Default; + break; + case '+': // Enable ruled line buffer printing + if (!dc3Lock) + state = Default; + getWriter().setRuledLineMode(true); + break; + case '-': // Disable ruled line buffer printing + if (!dc3Lock) + state = Default; + getWriter().setRuledLineMode(false); + break; + case 'A': // Select Ruled line buffer A + case 'B': // Select Ruled line buffer B + if (!dc3Lock) + state = Default; + break; + case 'C': // Clear rule buffer + if (!dc3Lock) + state = Default; + getWriter().clearRuledLine(); + break; + case 'P': // Prints the data in the print buffer and prints 1 line of the ruled line buffer + if (!dc3Lock) + state = Default; + getWriter().printRuledLine(); + break; + case 'L': // Writes "1" (black) in the range from nhnl to mhml in the selected ruled line buffer. + // ml mh nl nh + expectedDataBytes = 4; + break; + default: + // unhandled but need to ignore data... + INFO_LOG(NAOMI, "Unhandled DC3 [%c]\n", c); + if (!dc3Lock) + state = Default; + break; + } + } + + void executeDc3Command() + { + switch (curCommand) + { + case 'L': + getWriter().drawRuledLine((u8)dataBytes[0] + (u8)dataBytes[1] * 256, (u8)dataBytes[2] + (u8)dataBytes[3] * 256); + break; + default: + //printf("DC3 %c", curCommand); + //for (auto c : dataBytes) + // printf(" %d", (u8)c); + //printf("\n"); + break; + } + } + + enum { Default, ESC, DC2, DC3 } state = Default; + bool dc3Lock = false; + char curCommand = 0; + unsigned expectedDataBytes = 0; + std::vector dataBytes; + + bool kanji = false; + char kanjiByte0 = 0; + + struct Bitmap + { + int width; + int height; + std::vector data; + }; + std::vector bitmaps; + + BitmapWriter& getWriter() + { + if (bitmapWriter == nullptr) + bitmapWriter = std::make_unique(832); // tduno test print: min 894, but uses 832 for rules lines + // tduno2 test print ok with 832 + // f355 ok with 832 + return *bitmapWriter; + } + std::unique_ptr bitmapWriter; +}; + +static std::unique_ptr printer; + +void init() +{ + printer = std::make_unique(); +} + +void term() +{ + printer.reset(); +} + +void print(char c) +{ + if (printer != nullptr) + printer->print(c); +} + +#ifndef STANDALONE_TEST +void serialize(Serializer& ser) +{ + if (printer != nullptr) + printer->serialize(ser); +} + +void deserialize(Deserializer& deser) +{ + if (printer != nullptr) + { + if (deser.version() >= Deserializer::V35) + printer->deserialize(deser); + else + init(); + } +} +#endif + +} + +#ifdef STANDALONE_TEST +settings_t settings; + +int main(int argc, char *argv[]) +{ + if (argc < 2) + return 1; + FILE *f = fopen(argv[1], "rb"); + if (f == nullptr) { + perror(argv[1]); + return 1; + } + settings.content.gameId = "somegame"; + printer::ThermalPrinter printer; + for (;;) + { + int c = fgetc(f); + if (c == EOF) + break; + printer.print((char)c); + } + fclose(f); + + return 0; +} +#endif diff --git a/core/hw/naomi/printer.h b/core/hw/naomi/printer.h new file mode 100644 index 000000000..c8c2ba91e --- /dev/null +++ b/core/hw/naomi/printer.h @@ -0,0 +1,31 @@ +/* + Copyright 2023 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#pragma once +#include "types.h" + +namespace printer +{ + +void init(); +void term(); +void print(char c); +void serialize(Serializer& ser); +void deserialize(Deserializer& deser); + +} diff --git a/core/network/naomi_network.cpp b/core/network/naomi_network.cpp index f7d73d565..6812a54d0 100644 --- a/core/network/naomi_network.cpp +++ b/core/network/naomi_network.cpp @@ -371,7 +371,7 @@ void SetNaomiNetworkConfig(int node) { configure_maxspeed_flash(node != -1, node == 0); } - else if (!strcmp("F355 CHALLENGE JAPAN", naomi_game_id)) + else if (gameId == "F355 CHALLENGE JAPAN") { // FIXME need default flash write_naomi_flash(0x230, node == -1 ? 0 : node == 0 ? 1 : 2); @@ -392,7 +392,7 @@ bool NaomiNetworkSupported() "F355 CHALLENGE JAPAN", // Naomi 2 "CLUB KART IN JAPAN", "INITIAL D", "INITIAL D Ver.2", "INITIAL D Ver.3", "THE KING OF ROUTE66", - "SAMPLE GAME MAX LONG NAME-" // Driving Simulator + "SEGA DRIVING SIMULATOR" }; if (!config::NetworkEnable) return false; diff --git a/core/serialize.h b/core/serialize.h index 212f0a9ea..5e29481ac 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -59,7 +59,8 @@ public: V32, V33, V34, - Current = V34, + V35, + Current = V35, Next = Current + 1, }; diff --git a/fonts/CMakeLists.txt b/fonts/CMakeLists.txt new file mode 100644 index 000000000..32d6a6082 --- /dev/null +++ b/fonts/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) + +project(FlycastFonts) +add_executable(biosfont biosfont.cpp fontutil.cpp) +add_executable(printerfont printerfont.cpp fontutil.cpp) + +target_compile_features(biosfont PRIVATE cxx_std_17) +target_compile_features(printerfont PRIVATE cxx_std_17) +set_target_properties(biosfont PROPERTIES CXX_EXTENSIONS OFF LINK_FLAGS_RELEASE -s) +set_target_properties(printerfont PROPERTIES CXX_EXTENSIONS OFF LINK_FLAGS_RELEASE -s) +target_compile_options(biosfont PRIVATE $<$:-Wall>) +target_compile_options(printerfont PRIVATE $<$:-Wall>) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(freetype2 IMPORTED_TARGET freetype2) +target_link_libraries(biosfont PRIVATE PkgConfig::freetype2) +target_link_libraries(printerfont PRIVATE PkgConfig::freetype2) diff --git a/fonts/biosfont.bin b/fonts/biosfont.bin index 7d09fdbcc..f0825fe8b 100644 Binary files a/fonts/biosfont.bin and b/fonts/biosfont.bin differ diff --git a/fonts/biosfont.cpp b/fonts/biosfont.cpp new file mode 100644 index 000000000..dc22c078d --- /dev/null +++ b/fonts/biosfont.cpp @@ -0,0 +1,455 @@ +// +// Build the dreamcast BIOS font table using the specified fonts with the help of FreeType2. +// Fonts should be listed in increasing priority order since glyphs are replaced by later fonts if found. +// +// biosfont 12x24rk.pcf jiskan24.pcf neep-iso8859-1-12x24.pcf +// +// 12x24rk.pcf: Copyright 1989 by Sony Corp. +// Attrib license +// jiskan24.pcf: Licensed under Public Domain +// neep-iso8859-1-12x24.pcf: Copyright Jim Knoble +// GPL v2+ +// +#include "fontutil.h" +#include +#include FT_FREETYPE_H +#include + +#include +#include +#include +#include +#include +#include +#include + +// BIOS table +// 288 12x24 characters (unicode encoding) +const unsigned charcodes12[] = { +// from, to + // overbar + 0xaf, 0xaf, + // ASCII 33-126 + '!', '~', + // Yen + 0xa5, 0xa5, + // ISO-8859-1 (chars 160-255) + 0xa0, 0xff, + // JIS X0201 (chars 160-255) + ' ', ' ', + 0xff61, 0xff9f, + // FIXME chars E0-FF are unknown??? + 0xff61, 0xff7f, // wrong + ' ', ' ', +}; + +// 7078 24x24 characters (JIS X0208 encoding) +const unsigned charcodes24[] = { +// prefix, from, to + // JIS X0208 row 33, Symbols + 0x21, 0x21, 0x7e, + // JIS X0208 row 34, Symbols + 0x22, 0x21, 0x7e, + // JIS X0208 row 35, Roman alphabet + 0x23, 0x21, 0x7e, + // JIS X0208 row 36, Hiragana + 0x24, 0x21, 0x7e, + // JIS X0208 row 37, Katakana + 0x25, 0x21, 0x7e, + // JIS X0208 row 38, Greek + 0x26, 0x21, 0x7e, + // JIS X0208 row 39, Cyrillic + 0x27, 0x21, 0x7e, + // JIS X0208 row 48 + 0x30, 0x21, 0x7e, + // JIS X0208 row 49 + 0x31, 0x21, 0x7e, + // JIS X0208 row 50 + 0x32, 0x21, 0x7e, + // JIS X0208 row 51 + 0x33, 0x21, 0x7e, + // JIS X0208 row 52 + 0x34, 0x21, 0x7e, + // JIS X0208 row 53 + 0x35, 0x21, 0x7e, + // JIS X0208 row 54 + 0x36, 0x21, 0x7e, + // JIS X0208 row 55 + 0x37, 0x21, 0x7e, + // JIS X0208 row 56 + 0x38, 0x21, 0x7e, + // JIS X0208 row 57 + 0x39, 0x21, 0x7e, + // JIS X0208 row 58 + 0x3a, 0x21, 0x7e, + // JIS X0208 row 59 + 0x3b, 0x21, 0x7e, + // JIS X0208 row 60 + 0x3c, 0x21, 0x7e, + // JIS X0208 row 61 + 0x3d, 0x21, 0x7e, + // JIS X0208 row 62 + 0x3e, 0x21, 0x7e, + // JIS X0208 row 63 + 0x3f, 0x21, 0x7e, + // JIS X0208 row 64 + 0x40, 0x21, 0x7e, + // JIS X0208 row 65 + 0x41, 0x21, 0x7e, + // JIS X0208 row 66 + 0x41, 0x21, 0x7e, + // JIS X0208 row 67 + 0x43, 0x21, 0x7e, + // JIS X0208 row 68 + 0x44, 0x21, 0x7e, + // JIS X0208 row 69 + 0x45, 0x21, 0x7e, + // JIS X0208 row 70 + 0x46, 0x21, 0x7e, + // JIS X0208 row 71 + 0x47, 0x21, 0x7e, + // JIS X0208 row 72 + 0x48, 0x21, 0x7e, + // JIS X0208 row 73 + 0x49, 0x21, 0x7e, + // JIS X0208 row 74 + 0x4a, 0x21, 0x7e, + // JIS X0208 row 75 + 0x4b, 0x21, 0x7e, + // JIS X0208 row 76 + 0x4c, 0x21, 0x7e, + // JIS X0208 row 77 + 0x4d, 0x21, 0x7e, + // JIS X0208 row 78 + 0x4e, 0x21, 0x7e, + // JIS X0208 row 79 + 0x4f, 0x21, 0x7e, + + // JIS X0208 row 80 + 0x50, 0x21, 0x7e, + // JIS X0208 row 81 + 0x51, 0x21, 0x7e, + // JIS X0208 row 82 + 0x52, 0x21, 0x7e, + // JIS X0208 row 83 + 0x53, 0x21, 0x7e, + // JIS X0208 row 84 + 0x54, 0x21, 0x7e, + // JIS X0208 row 85 + 0x55, 0x21, 0x7e, + // JIS X0208 row 86 + 0x56, 0x21, 0x7e, + // JIS X0208 row 87 + 0x57, 0x21, 0x7e, + // JIS X0208 row 88 + 0x58, 0x21, 0x7e, + // JIS X0208 row 89 + 0x59, 0x21, 0x7e, + // JIS X0208 row 90 + 0x5a, 0x21, 0x7e, + // JIS X0208 row 91 + 0x5b, 0x21, 0x7e, + // JIS X0208 row 92 + 0x5c, 0x21, 0x7e, + // JIS X0208 row 93 + 0x5d, 0x21, 0x7e, + // JIS X0208 row 94 + 0x5e, 0x21, 0x7e, + // JIS X0208 row 95 + 0x5f, 0x21, 0x7e, + // JIS X0208 row 96 + 0x60, 0x21, 0x7e, + // JIS X0208 row 97 + 0x61, 0x21, 0x7e, + // JIS X0208 row 98 + 0x62, 0x21, 0x7e, + // JIS X0208 row 99 + 0x63, 0x21, 0x7e, + // JIS X0208 row 100 + 0x64, 0x21, 0x7e, + // JIS X0208 row 101 + 0x65, 0x21, 0x7e, + // JIS X0208 row 102 + 0x66, 0x21, 0x7e, + // JIS X0208 row 103 + 0x67, 0x21, 0x7e, + // JIS X0208 row 104 + 0x68, 0x21, 0x7e, + // JIS X0208 row 105 + 0x69, 0x21, 0x7e, + // JIS X0208 row 106 + 0x6a, 0x21, 0x7e, + // JIS X0208 row 107 + 0x6b, 0x21, 0x7e, + // JIS X0208 row 108 + 0x6c, 0x21, 0x7e, + // JIS X0208 row 109 + 0x6d, 0x21, 0x7e, + // JIS X0208 row 110 + 0x6e, 0x21, 0x7e, + // JIS X0208 row 111 + 0x6f, 0x21, 0x7e, + // JIS X0208 row 112 + 0x70, 0x21, 0x7e, + // JIS X0208 row 113 + 0x71, 0x21, 0x7e, + // JIS X0208 row 114 + 0x72, 0x21, 0x7e, + // JIS X0208 row 115 + 0x73, 0x21, 0x7e, + // JIS X0208 row 116 + 0x74, 0x21, 0x26, + // TODO Dreamcast symbols (22 chars) + // copyright U+24B8 + // register U+24C7 + // trademark U+2122 + // up arrow U+2B06 + // down U+2B07 + // left U+2B05 + // right U+27A1 ??? + // up+right U+2B08 + // down+right U+2B0A + // down+left U+2B0B + // up+left U+2B09 + // circled A U+24B6 + // circled B U+24B7 + // circled C U+24B8 + // circled D U+24B8 + // circled X U+24CD + // circled Y U+24CE + // circled Z U+24CF + // squared L U+1F13B (Supplementary Multilingual Plane) + // squared R U+1F141 (Supplementary Multilingual Plane) + // start button U+1F142 (Squared S, SMP) + // VMU U+1F4DF (pager) U+1F4F1 (mobile phone) +}; + +static uint8_t biosfont[288 * 36 + 7078 * 72]; + +static FT_Library library; +static FT_Face face; + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + loadjisx208Table(); + + if (argc < 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + int error = FT_Init_FreeType(&library); + if (error) + { + fprintf(stderr, "FreeType init failed\n"); + return 1; + } + for (int font = 1; font < argc; font++) + { + const char *fontname = argv[font]; + long index = 0; + if (strlen(fontname) >= 4 && !strcmp(fontname + strlen(fontname) - 4, ".ttc")) + // 5: NotoSans Mono CJK.ttc + index = strtoul(argv[++font], nullptr, 10); + error = FT_New_Face(library, fontname, index, &face); + if (error) + { + fprintf(stderr, "Can't load %s\n", fontname); + return 1; + } + + const char *registry; + const char *encoding; + FT_Get_BDF_Charset_ID(face, &encoding, ®istry); + printf("%s: %s %s\n%ld glyphs\n%d fixed sizes\n%d charmaps\n", fontname, encoding, registry, face->num_glyphs, face->num_fixed_sizes, face->num_charmaps); + FT_Bitmap_Size *size = face->available_sizes; + for (int i = 0; i < face->num_fixed_sizes; i++, size++) + printf("%d: %d x %d\n", i + 1, size->width, size->height); + if (face->num_fixed_sizes == 0) + FT_Set_Pixel_Sizes(face, 0, 24); + + bool jisx0201Encoding = false; + bool jisx0208Encoding = false; + if (registry != nullptr) + { + if (!strcmp(registry, "JISX0208.1983")) + { + jisx0208Encoding = true; + FT_Set_Charmap(face, face->charmaps[0]); + } + else if (!strcmp(registry, "JISX0201.1976")) + { + jisx0201Encoding = true; + FT_Set_Charmap(face, face->charmaps[0]); + } + } + /* list chars + unsigned gindex; + long charcode = FT_Get_First_Char(face, &gindex); + while (gindex != 0) + { + printf("code %lx index %d\n", charcode, gindex); + charcode = FT_Get_Next_Char(face, charcode, &gindex); + } + */ + + unsigned offset = 0; + if (jisx0208Encoding) + { + offset = 288 * 36; + } + else + { + for (size_t i = 0; i < std::size(charcodes12); i += 2) + { + int from = charcodes12[i]; + int to = charcodes12[i + 1]; + for (int j = from; j <= to; j++) + { + int code = j; + if (jisx0201Encoding) + { + if (code >= 0xff61 && code <= 0xff9f) + code = code - 0xff61 + 0xa1; + else if (code >= 0x80) { + offset += 36; + continue; + } + } + if (!loadGlyph(face, code, 12, 24)) { + offset += 36; + continue; + } + uint8_t *p = face->glyph->bitmap.buffer; + for (int r = 0; r < 24; r++) + { + if (r & 1) + { + biosfont[offset + 1] |= p[0] >> 4; + biosfont[offset + 2] = ((p[0] & 0xf) << 4) | (p[1] >> 4); + offset += 3; + } + else + { + biosfont[offset] = p[0]; + biosfont[offset + 1] = p[1]; + } + p += face->glyph->bitmap.pitch; + } + } + } + printf("Final offset(12) %d 288 * 36 = %d\n", offset, 288 * 36); + } + + if (jisx0201Encoding) + { + offset += 7078 * 72; + } + else + { + for (size_t range = 0; range < std::size(charcodes24); range += 3) + { + int prefix = charcodes24[range]; + int from = charcodes24[range + 1]; + int to = charcodes24[range + 2]; + for (int j = from; j <= to; j++) + { + uint16_t jis = (prefix << 8) | j; + wchar_t u; + if (jisx0208Encoding) { + u = jis; + } + else + { + u = jisx208[jis]; + if (u == 0) { + printf("JISX208 conversion failed: [%02x %02x]\n", prefix, j); + offset += 72; + continue; + } + } + if (!loadGlyph(face, u, 24, 24)) { + offset += 72; + continue; + } + const FT_GlyphSlot glyph = face->glyph; + uint8_t *p = glyph->bitmap.buffer; + // left=0 top=22 is ok + // other values need to shift the bitmap + if (glyph->bitmap.pitch == 3 && glyph->bitmap_left == 0 && glyph->bitmap_top == 22) { + memcpy(&biosfont[offset], p, 72); + offset += 72; + } + else + { + const int topFill = std::max(0, 21 - glyph->bitmap_top); + memset(&biosfont[offset], 0, topFill * 3); + offset += topFill * 3; + const int rows = std::min((int)glyph->bitmap.rows, 24 - topFill); + const int width = glyph->bitmap.width; + for (int r = 0; r < rows; r++) + { + if (width == 24) + { + memcpy(&biosfont[offset], p, 3); + } + else + { + int left = glyph->bitmap_left; + unsigned o = offset; + while (left >= 8 && o - offset < 3) + { + left -= 8; + biosfont[o++] = 0; + } + if (o - offset < 3) + { + biosfont[o++] = p[0] >> left; + if (left > 0 && o - offset < 3) + biosfont[o] = p[0] << (8 - left); + if (width > 8 && o - offset < 3) + { + biosfont[o++] |= p[1] >> left; + if (o - offset < 3) + { + if (left > 0) + biosfont[o] = p[1] << (8 - left); + if (width > 16) + biosfont[o++] |= p[2] >> left; + } + } + else + { + o++; + if (o - offset < 3) + biosfont[o] = 0; + } + } + } + offset += 3; + p += glyph->bitmap.pitch; + } + const int bottomFill = 24 - (topFill + rows); + assert(bottomFill >= 0); + memset(&biosfont[offset], 0, bottomFill * 3); + offset += bottomFill * 3; + } + } + } + } + printf("Final offset(24) %d 288 * 36 + 7078 * 72 = %d\n", offset, 288 * 36 + 7078 * 72); + FT_Done_Face(face); + } + FT_Done_FreeType(library); + + FILE *f = fopen("biosfont.bin", "wb"); + if (f == nullptr) { + perror("biosfont.bin"); + return 1; + } + fwrite(biosfont, 1, sizeof(biosfont), f); + fclose(f); + + return 0; +} diff --git a/fonts/biosfontpng.cpp b/fonts/biosfontpng.cpp new file mode 100644 index 000000000..b81491999 --- /dev/null +++ b/fonts/biosfontpng.cpp @@ -0,0 +1,285 @@ +// +// Make PNGs of the BIOS font for half- and full-width glyphs. +// Compare with http://submarine.org.uk/info/biosfont/ +// +#include +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include +#include + +// BIOS table +// 288 12x24 characters (unicode encoding) +const unsigned charcodes12[] = { +// from, to + // overbar + 1, + // ASCII 33-126 + 94, + // Yen + 1, + // ISO-8859-1 (chars 160-255) + 96, + // JIS X0201 (chars 160-255) + 96, +}; + +// 7078 24x24 characters (JIS X0208 encoding) +const unsigned charcodes24[] = { + // JIS X0208 row 33, Symbols + 94, + // JIS X0208 row 34, Symbols + 94, + // JIS X0208 row 35, Roman alphabet + 94, + // JIS X0208 row 36, Hiragana + 94, + // JIS X0208 row 37, Katakana + 94, + // JIS X0208 row 38, Greek + 94, + // JIS X0208 row 39, Cyrillic + 94, + // JIS X0208 row 48 + 94, + // JIS X0208 row 49 + 94, + // JIS X0208 row 50 + 94, + // JIS X0208 row 51 + 94, + // JIS X0208 row 52 + 94, + // JIS X0208 row 53 + 94, + // JIS X0208 row 54 + 94, + // JIS X0208 row 55 + 94, + // JIS X0208 row 56 + 94, + // JIS X0208 row 57 + 94, + // JIS X0208 row 58 + 94, + // JIS X0208 row 59 + 94, + // JIS X0208 row 60 + 94, + // JIS X0208 row 61 + 94, + // JIS X0208 row 62 + 94, + // JIS X0208 row 63 + 94, + // JIS X0208 row 64 + 94, + // JIS X0208 row 65 + 94, + // JIS X0208 row 66 + 94, + // JIS X0208 row 67 + 94, + // JIS X0208 row 68 + 94, + // JIS X0208 row 69 + 94, + // JIS X0208 row 70 + 94, + // JIS X0208 row 71 + 94, + // JIS X0208 row 72 + 94, + // JIS X0208 row 73 + 94, + // JIS X0208 row 74 + 94, + // JIS X0208 row 75 + 94, + // JIS X0208 row 76 + 94, + // JIS X0208 row 77 + 94, + // JIS X0208 row 78 + 94, + // JIS X0208 row 79 + 94, + + // JIS X0208 row 80 + 94, + // JIS X0208 row 81 + 94, + // JIS X0208 row 82 + 94, + // JIS X0208 row 83 + 94, + // JIS X0208 row 84 + 94, + // JIS X0208 row 85 + 94, + // JIS X0208 row 86 + 94, + // JIS X0208 row 87 + 94, + // JIS X0208 row 88 + 94, + // JIS X0208 row 89 + 94, + // JIS X0208 row 90 + 94, + // JIS X0208 row 91 + 94, + // JIS X0208 row 92 + 94, + // JIS X0208 row 93 + 94, + // JIS X0208 row 94 + 94, + // JIS X0208 row 95 + 94, + // JIS X0208 row 96 + 94, + // JIS X0208 row 97 + 94, + // JIS X0208 row 98 + 94, + // JIS X0208 row 99 + 94, + // JIS X0208 row 100 + 94, + // JIS X0208 row 101 + 94, + // JIS X0208 row 102 + 94, + // JIS X0208 row 103 + 94, + // JIS X0208 row 104 + 94, + // JIS X0208 row 105 + 94, + // JIS X0208 row 106 + 94, + // JIS X0208 row 107 + 94, + // JIS X0208 row 108 + 94, + // JIS X0208 row 109 + 94, + // JIS X0208 row 110 + 94, + // JIS X0208 row 111 + 94, + // JIS X0208 row 112 + 94, + // JIS X0208 row 113 + 94, + // JIS X0208 row 114 + 94, + // JIS X0208 row 115 + 94, + // JIS X0208 row 116 + 6, + // Dreamcast symbols + 22 +}; + +static uint8_t biosfont[288 * 36 + 7078 * 72]; + +void make12x24() +{ + std::vector bitmap; + constexpr int WIDTH = 16 * 32; + bitmap.resize(WIDTH * 20 * 32); // 16 cols and 20 rows of 32x32 pix + int c = 0; + int y = 0; + for (int pg : charcodes12) + { + while (pg > 0) + { + for (int x = 0; x < 16 && pg > 0; x++, pg--, c++) + { + uint8_t *src = &biosfont[c * 3 * 12]; + for (int row = 0; row < 24; row += 2) + { + uint8_t *dst = &bitmap[y * 32 * WIDTH + x * 32 + WIDTH * row]; + for (int i = 0; i < 8; i++, dst++) + if (src[0] & (0x80 >> i)) + *dst = 0xff; + for (int i = 0; i < 4; i++, dst++) + if (src[1] & (0x80 >> i)) + *dst = 0xff; + + dst += WIDTH - 12; + for (int i = 0; i < 4; i++, dst++) + if (src[1] & (0x8 >> i)) + *dst = 0xff; + for (int i = 0; i < 8; i++, dst++) + if (src[2] & (0x80 >> i)) + *dst = 0xff; + src += 3; + } + } + y++; + } + } + stbi_write_png("bios12x24.png", WIDTH, 20 * 32, 1, &bitmap[0], WIDTH); +} + +void make24x24() +{ + int lines = 0; + for (int pg : charcodes24) + lines += (pg + 15) / 16; + + std::vector bitmap; + constexpr int WIDTH = 16 * 32; + bitmap.resize(WIDTH * lines * 32); // 16 cols and n rows of 32x32 pix + uint8_t *fontbase = &biosfont[288 * 3 * 12]; + int c = 0; + int y = 0; + for (int pg : charcodes24) + { + while (pg > 0) + { + for (int x = 0; x < 16 && pg > 0; x++, pg--, c++) + { + uint8_t *src = &fontbase[c * 3 * 24]; + for (int row = 0; row < 24; row++) + { + uint8_t *dst = &bitmap[y * 32 * WIDTH + x * 32 + WIDTH * row]; + for (int i = 0; i < 8; i++, dst++) + if (src[0] & (0x80 >> i)) + *dst = 0xff; + for (int i = 0; i < 8; i++, dst++) + if (src[1] & (0x80 >> i)) + *dst = 0xff; + for (int i = 0; i < 8; i++, dst++) + if (src[2] & (0x80 >> i)) + *dst = 0xff; + src += 3; + } + } + y++; + } + } + stbi_write_png("bios24x24.png", WIDTH, lines * 32, 1, &bitmap[0], WIDTH); +} + +int main(int argc, char *argv[]) +{ + FILE *f = fopen("biosfont.bin", "rb"); + if (f == nullptr) { + perror("biosfont.bin"); + return 1; + } + if (fread(biosfont, 1, sizeof(biosfont), f) != sizeof(biosfont)) + { + fprintf(stderr, "Invalid bios font file: truncated read"); + fclose(f); + return 1; + } + fclose(f); + + make12x24(); + make24x24(); +} diff --git a/fonts/fontutil.cpp b/fonts/fontutil.cpp new file mode 100644 index 000000000..59a8bb2f4 --- /dev/null +++ b/fonts/fontutil.cpp @@ -0,0 +1,61 @@ +#include "fontutil.h" + +#include +#include +#include +#include + +wchar_t jisx208[65536]; + +void loadjisx208Table() +{ + FILE *f = fopen("jisx0213-2004-8bit-std.txt" ,"rb"); + if (f == nullptr) { + perror("jisx0213-2004-8bit-std.txt"); + return; + } + char buf[512]; + while (fgets(buf, sizeof(buf), f) != nullptr) + { + if (buf[0] == '#') + continue; + char *p; + uint16_t jis = strtoul(buf, &p, 16); + p += 3; // "\tU+" + wchar_t utf = strtoul(p, &p, 16); + if (!isspace((int8_t)*p)) + continue; + jisx208[jis] = utf; + } + fclose(f); +} + +bool loadGlyph(FT_Face face, unsigned glyph, unsigned w, unsigned h) +{ + unsigned glyph_index = FT_Get_Char_Index(face, glyph); + if (glyph_index == 0) { + //fprintf(stderr, "Missing glyph(%d) code %x char %lc\n", w, glyph, glyph); + return false; + } + int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + fprintf(stderr, "Can't load glyph(%d) code %x char %lc\n", w, glyph, glyph); + return false; + } + if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP) + { + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO); + if (error) { + fprintf(stderr, "Render glyph failed: glyph(%d) code %x char %lc\n", w, glyph, glyph); + return false; + } + } + else if (face->glyph->bitmap.width != w || face->glyph->bitmap.rows != h) { + //fprintf(stderr, "glyph(%d) code %x char %lc wrong size %d x %d\n", w, glyph, glyph, face->glyph->bitmap.width, face->glyph->bitmap.rows); + return false; + } + // printf("%lc bitmap %d x %d pitch %d px mode %d left %d top %d\n", glyph < 0x80 || glyph >= 0xA0 ? glyph : '?', + // face->glyph->bitmap.width, face->glyph->bitmap.rows, face->glyph->bitmap.pitch, face->glyph->bitmap.pixel_mode, + // face->glyph->bitmap_left, face->glyph->bitmap_top); + return true; +} diff --git a/fonts/fontutil.h b/fonts/fontutil.h new file mode 100644 index 000000000..ae7120d3a --- /dev/null +++ b/fonts/fontutil.h @@ -0,0 +1,7 @@ +#include +#include FT_FREETYPE_H + +extern wchar_t jisx208[65536]; +void loadjisx208Table(); + +bool loadGlyph(FT_Face face, unsigned glyph, unsigned w, unsigned h); diff --git a/fonts/maketestpages.cpp b/fonts/maketestpages.cpp new file mode 100644 index 000000000..a4fd38e20 --- /dev/null +++ b/fonts/maketestpages.cpp @@ -0,0 +1,75 @@ +#include + +void printAsciiTestPage(bool largeFont) +{ + const char *fname = largeFont ? "test12x24.dump" : "test8x16.dump"; + FILE *f = fopen(fname, "wb"); + if (!f) { + perror(fname); + return; + } + fprintf(f, "\33H\22F%c\n", largeFont); // disable Kanji, select font + fprintf(f, " \33-\2TEST PAGE %s\33-%c\n\n", largeFont ? "12x24" : "8x16", 0); + for (int i = 0x20; i < 0x100; i += 0x10) + { + fprintf(f, "%02X", i); + for (int j = 0; j < 0x10; j++) + { + if (i + j == 0xa0 || i + j == 0xff) + fprintf(f, " "); + else + fprintf(f, " %c", i + j); + } + fputc('\n', f); + } + fputc('\n', f); + fputc('\n', f); + fprintf(f, "\33i"); // full cut + fclose(f); + + printf("%s created\n", fname); +} + +void printKanjiTestPage(bool largeFont) +{ + const char *fname = largeFont ? "test24x24.dump" : "test16x16.dump"; + FILE *f = fopen(fname, "wb"); + if (!f) { + perror(fname); + return; + } + fprintf(f, "\33H\22F%c\n", largeFont); // disable Kanji, select font + fprintf(f, " \33-\2KANJI TEST PAGE %s\33-%c\n\n", largeFont ? "24x24" : "16x16", 0); + for (int plane = 0x21; plane <= 0x7e; plane++) + { + fprintf(f, " Plane %02X\n\n", plane); + for (int c = 0x21; c <= 0x7e; c++) + { + if (c == 0x21 || (c & 0xf) == 0) + fprintf(f, " %02X ", (c & 0xf0)); + if (c == 0x21) + fprintf(f, " "); + fprintf(f, "\33K%c%c\33H ", plane, c); + if ((c & 0xf) == 0xf) + fputc('\n', f); + } + fputc('\n', f); + fputc('\n', f); + } + fputc('\n', f); + fputc('\n', f); + fprintf(f, "\33i"); // full cut + fclose(f); + + printf("%s created\n", fname); +} + +int main(int argc, char *argv[]) +{ + printAsciiTestPage(false); + printAsciiTestPage(true); + printKanjiTestPage(false); + printKanjiTestPage(true); + + return 0; +} diff --git a/fonts/printer_ascii12x24.bin b/fonts/printer_ascii12x24.bin new file mode 100644 index 000000000..ac977c3f4 Binary files /dev/null and b/fonts/printer_ascii12x24.bin differ diff --git a/fonts/printer_ascii8x16.bin b/fonts/printer_ascii8x16.bin new file mode 100644 index 000000000..2083d5670 Binary files /dev/null and b/fonts/printer_ascii8x16.bin differ diff --git a/fonts/printer_kanji16x16.bin b/fonts/printer_kanji16x16.bin new file mode 100644 index 000000000..c580d4d5f Binary files /dev/null and b/fonts/printer_kanji16x16.bin differ diff --git a/fonts/printer_kanji24x24.bin b/fonts/printer_kanji24x24.bin new file mode 100644 index 000000000..e779563e0 Binary files /dev/null and b/fonts/printer_kanji24x24.bin differ diff --git a/fonts/printerfont.cpp b/fonts/printerfont.cpp new file mode 100644 index 000000000..1ee955b63 --- /dev/null +++ b/fonts/printerfont.cpp @@ -0,0 +1,255 @@ +// +// printerfont 12x24rk.pcf jiskan24.pcf jiskan16.pcf ter-u24b.bdf unifont-15.0.01.bdf +// +// TODO: +// 8x16 ascii: missing F1-FD +// 12x24 ascii: missing 80-9F, E0-FF +// 16x16 kanji: +// plane 22 additional chars line 30 +// plane 23-28 some additional chars +// plane 2D missing some line 30, line 70 missing all +// kanji planes ok, some additional chars +// 24x24 kanji: ok +// +// 12x24rk.pcf uses JISX0201.1976 +// jiskan24 and jiskan16 kanji only +// +// Copyright +// 12x24rk.pcf: Copyright 1989 by Sony Corp. +// Attrib license +// jiskan16.pcf, jiskan24.pcf: Licensed under Public Domain +// GNU unifont: GPL +// Terminus: SIL Open Font License, Version 1.1 +// +#include "fontutil.h" +#include +#include FT_FREETYPE_H +#include + +#include +#include +#include +#include +#include +#include +#include + +static FT_Library library; +static FT_Face face; + +// L"▁▂▃▄▅▆▇█▏▎▍▌▋▊▉┼┴┬┤├▔─│▕┌┐└┘╭╮╰╯" +const wchar_t katakana80[33] = L"\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u258F\u258E\u258D\u258C\u258B\u258A\u2589\u253C" + "\u2534\u252C\u2524\u251C\u2594\u2500\u2502\u2595\u250C\u2510\u2514\u2518\u256D\u256E\u2570\u256F"; +// L" 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソ" +const wchar_t katakanaA0[33] = L" \uFF61\uFF62\uFF63\uFF64\uFF65\uFF66\uFF67\uFF68\uFF69\uFF6A\uFF6B\uFF6C\uFF6D\uFF6E\uFF6F" + "\uFF70\uFF71\uFF72\uFF73\uFF74\uFF75\uFF76\uFF77\uFF78\uFF79\uFF7A\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F"; +// L"タチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚" +const wchar_t katakanaC0[33] = L"\uFF80\uFF81\uFF82\uFF83\uFF84\uFF85\uFF86\uFF87\uFF88\uFF89\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E\uFF8F" + "\uFF90\uFF91\uFF92\uFF93\uFF94\uFF95\uFF96\uFF97\uFF98\uFF99\uFF9A\uFF9B\uFF9C\uFF9D\uFF9E\uFF9F"; +// L"゠ᅣ‡ᅧ◢◣◤◥♠♥♦♣●○╱╲╳円年月日時分秒〒市区町村人▒ " +const wchar_t katakanaE0[33] = L"\u30A0\uFFC4\u2021\uFFCA\u25E2\u25E3\u25E4\u25E5\u2660\u2665\u2666\u2663\u25CF\u25CB\u2571\u2572" + "\u2573\u5186\u5E74\u6708\u65E5\u6642\u5206\u79D2\u3012\u5E02\u533A\u753A\u6751\u4EBA\u2592 "; + +constexpr int ASCII_CHARS = 256 - 32; +constexpr int KANJI_CHARS = 1 + 94 * 94; + +static uint8_t ascii8x16[ASCII_CHARS * 16]; +static uint8_t kanji16x16[KANJI_CHARS * 2 * 16]; +static uint8_t ascii12x24[ASCII_CHARS * 2 * 24]; +static uint8_t kanji24x24[KANJI_CHARS * 3 * 24]; + +bool save(const char *name, void *data, size_t size) +{ + FILE *f = fopen(name, "wb"); + if (f == nullptr) { + perror(name); + return false; + } + bool status = fwrite(data, size, 1, f) == 1; + fclose(f); + + return status; +} + +wchar_t convertKatakana(wchar_t c) +{ + if (c >= 0xe0) + return katakanaE0[c - 0xe0]; + if (c >= 0xc0) + return katakanaC0[c - 0xc0]; + if (c >= 0xa0) + return katakanaA0[c - 0xa0]; + if (c >= 0x80) + return katakana80[c - 0x80]; + return c; +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + loadjisx208Table(); + + if (argc < 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + int error = FT_Init_FreeType(&library); + if (error) + { + fprintf(stderr, "FreeType init failed\n"); + return 1; + } + for (int font = 1; font < argc; font++) + { + const char *fontname = argv[font]; + long index = 0; + if (strlen(fontname) >= 4 && !strcmp(fontname + strlen(fontname) - 4, ".ttc")) + // 5: NotoSans Mono CJK.ttc + index = strtoul(argv[++font], nullptr, 10); + error = FT_New_Face(library, fontname, index, &face); + if (error) + { + fprintf(stderr, "Can't load %s\n", fontname); + return 1; + } + + const char *registry; + const char *encoding; + int fontHeight = 0; + FT_Get_BDF_Charset_ID(face, &encoding, ®istry); + printf("%s: %s %s\n%ld glyphs\n%d fixed sizes\n%d charmaps\n", fontname, encoding, registry, face->num_glyphs, face->num_fixed_sizes, face->num_charmaps); + FT_Bitmap_Size *size = face->available_sizes; + for (int i = 0; i < face->num_fixed_sizes; i++, size++) + { + printf("%d: %d x %d\n", i + 1, size->width, size->height); + fontHeight = size->height; + } + if (face->num_fixed_sizes == 0) + FT_Set_Pixel_Sizes(face, 0, 24); + + bool jisx0201Encoding = false; + bool jisx0208Encoding = false; + if (registry != nullptr) + { + if (!strcmp(registry, "JISX0208.1983")) + { + jisx0208Encoding = true; + FT_Set_Charmap(face, face->charmaps[0]); + } + else if (!strcmp(registry, "JISX0201.1976")) + { + jisx0201Encoding = true; + FT_Set_Charmap(face, face->charmaps[0]); + } + } + + if (fontHeight == 16) + { + for (wchar_t c = 32; c < 256; c++) + { + wchar_t uni = convertKatakana(c); + if (!loadGlyph(face, uni, 8, 16)) + continue; + uint8_t *src = face->glyph->bitmap.buffer; + uint8_t *dst = &ascii8x16[(c - 32) * 16]; + memcpy(dst, src, 16); + } + for (int plane = 0x21; plane <= 0x7e; plane++) + { + for (int c = 0x21; c <= 0x7e; c++) + { + wchar_t code = (plane << 8) | c; + if (!jisx0208Encoding) + code = jisx208[code]; + if (!loadGlyph(face, code, 16, 16)) + continue; + uint8_t *src = face->glyph->bitmap.buffer; + uint8_t *dst = &kanji16x16[(1 + (plane - 0x21) * 94 + (c - 0x21)) * 2 * 16]; + if (face->glyph->bitmap.pitch == 2) + { + memcpy(dst, src, 2 * 16); + } + else + { + for (int y = 0; y < 16; y++) + { + *dst++ = src[0]; + *dst++ = src[1]; + src += face->glyph->bitmap.pitch; + } + } + } + } + } + else if (fontHeight == 24) + { + for (wchar_t c = 32; c < 256; c++) + { + wchar_t uni; + if (jisx0201Encoding) + { + if ((c >= 0x80 && c <= 0x9F) || (c >= 0xE0 && c <= 0xFF)) + continue; + uni = c; + } + else + { + if (c == 0x7f) + continue; + uni = convertKatakana(c); + } + if (!loadGlyph(face, uni, 12, 24)) + continue; + uint8_t *src = face->glyph->bitmap.buffer; + uint8_t *dst = &ascii12x24[(c - 32) * 2 * 24]; + if (face->glyph->bitmap.pitch == 2) + memcpy(dst, src, 2 * 24); + else + { + for (int y= 0; y < 24; y++) + { + *dst++ = src[0]; + *dst++ = src[1]; + src += face->glyph->bitmap.pitch; + } + } + } + for (int plane = 0x21; plane <= 0x7e; plane++) + { + for (int c = 0x21; c <= 0x7e; c++) + { + wchar_t code = (plane << 8) | c; + if (!jisx0208Encoding) + code = jisx208[code]; + if (!loadGlyph(face, code, 24, 24)) + continue; + uint8_t *src = face->glyph->bitmap.buffer; + uint8_t *dst = &kanji24x24[(1 + (plane - 0x21) * 94 + (c - 0x21)) * 3 * 24]; + if (face->glyph->bitmap.pitch == 3) + { + memcpy(dst, src, 3 * 24); + } + else + { + for (int y = 0; y < 24; y++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + src += face->glyph->bitmap.pitch - 3; + } + } + } + } + } + FT_Done_Face(face); + } + FT_Done_FreeType(library); + save("printer_ascii8x16.bin", ascii8x16, sizeof(ascii8x16)); + save("printer_ascii12x24.bin", ascii12x24, sizeof(ascii12x24)); + save("printer_kanji16x16.bin", kanji16x16, sizeof(kanji16x16)); + save("printer_kanji24x24.bin", kanji24x24, sizeof(kanji24x24)); + + return 0; +}