/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * */ #include "common/config-manager.h" #include "common/translation.h" #include "gui/gui-manager.h" #include "gui/message.h" #include "graphics/fonts/amigafont.h" #include "graphics/fonts/dosfont.h" #include "agi/agi.h" #include "agi/font.h" #include "agi/text.h" namespace Agi { GfxFont::GfxFont(AgiBase *vm) { _vm = vm; _fontData = nullptr; _fontDataAllocated = nullptr; _fontIsHires = false; } GfxFont::~GfxFont() { free(_fontDataAllocated); } // Arrow to the right character, used for original saved game dialogs // Needs to get patched into at least the Apple IIgs font, because the font didn't support // that character and original AGI on Apple IIgs used Apple II menus for saving/restoring. static const uint8 fontData_ArrowRightCharacter[8] = { 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, }; void GfxFont::init() { if (ConfMan.getBool("herculesfont")) { // User wants, that we use Hercules hires font, try to load it loadFontHercules(); } else { switch (_vm->_renderMode) { case Common::kRenderHercA: case Common::kRenderHercG: // Render mode is Hercules, we try to load Hercules hires font loadFontHercules(); break; default: break; } } if (!_fontData) { switch (_vm->_renderMode) { case Common::kRenderAmiga: // Try user-file first, if that fails use our internal inaccurate topaz font loadFontScummVMFile("agi-font-amiga.bin"); if (!_fontData) { loadFontAmigaPseudoTopaz(); } break; case Common::kRenderApple2GS: // Special font, stored in file AGIFONT loadFontAppleIIgs(); break; case Common::kRenderAtariST: // TODO: Atari ST uses another font // Seems to be the standard Atari ST 8x8 system font loadFontScummVMFile("agi-font-atarist.bin"); if (!_fontData) { loadFontAtariST("agi-font-atarist-system.fnt"); if (!_fontData) { // TODO: in case we find a recreation of the font, add it in here } } break; case Common::kRenderHercA: case Common::kRenderHercG: case Common::kRenderCGA: case Common::kRenderEGA: case Common::kRenderVGA: switch (_vm->getGameID()) { case GID_MICKEY: // load mickey mouse font from interpreter file loadFontMickey(); break; default: loadFontScummVMFile("agi-font-dos.bin"); break; } break; default: break; } if (!_fontData) { // no font assigned? // use regular PC-BIOS font (taken from Dos-Box with a few modifications) _fontData = Graphics::DosFont::fontData_PCBIOS; debug("AGI: Using PC-BIOS font"); } } if (_vm->getLanguage() == Common::RU_RUS) { // Russian versions need special extended set overwriteExtendedWithRussianSet(); } } const byte *GfxFont::getFontData() { assert(_fontData); return _fontData; } bool GfxFont::isFontHires() { return _fontIsHires; } void GfxFont::overwriteSaveRestoreDialogCharacter() { // overwrite character 0x1A with the standard Sierra arrow to the right character // required for the original save/restore dialogs memcpy(_fontDataAllocated + (0x1A * 8), fontData_ArrowRightCharacter, sizeof(fontData_ArrowRightCharacter)); } // Overwrite extended character set (0x80-0xFF) with Russian characters void GfxFont::overwriteExtendedWithRussianSet() { if (_fontIsHires) { // TODO: Implement overwriting hires font characters too return; } if (!_fontDataAllocated) { // nothing allocated, we need to allocate space ourselves to be able to modify an internal font _fontDataAllocated = (uint8 *)calloc(256, 8); memcpy(_fontDataAllocated, _fontData, 128 * 8); // copy ASCII set over _fontData = _fontDataAllocated; } // Overwrite extended set with Russian characters memcpy(_fontDataAllocated + (128 * 8), Graphics::DosFont::fontData_ExtendedRussian, 128 * 8); debug("AGI: Using Russian extended font set"); } // This code loads a ScummVM-specific user-supplied binary font file // It's assumed that it's a plain binary file, that contains 256 characters. 8 bytes per character. // 8x8 pixels per character. File size 2048 bytes. // // Currently used for: // Atari ST - "agi-font-atarist.bin" -> should be the Atari ST 8x8 system font // Amiga - "agi-font-amiga.bin" -> should be the Amiga 8x8 Topaz font // DOS - "agi-font-dos.bin" void GfxFont::loadFontScummVMFile(const Common::Path &fontFilename) { Common::File fontFile; if (!fontFile.open(fontFilename)) { // Continue, if file not found // These ScummVM font files are totally optional, so don't show a warning return; } int32 fontFileSize = fontFile.size(); if (fontFileSize != (256 * 8)) { // unexpected file size warning("Fontfile '%s': unexpected file size", fontFilename.toString(Common::Path::kNativeSeparator).c_str()); return; } // allocate space for font bitmap data _fontDataAllocated = (uint8 *)calloc(256, 8); _fontData = _fontDataAllocated; // read font data, is already in the format that we need (plain bitmap 8x8) fontFile.read(_fontDataAllocated, 256 * 8); overwriteSaveRestoreDialogCharacter(); debug("AGI: Using user-supplied font"); } // We load the Mickey Mouse font from MICKEY.EXE void GfxFont::loadFontMickey() { Common::File interpreterFile; if (!interpreterFile.open("mickey.exe")) { // Continue, if file not found warning("Could not open file 'mickey.exe' for Mickey Mouse font"); return; } int32 interpreterFileSize = interpreterFile.size(); if (interpreterFileSize != 55136) { // unexpected file size warning("File 'mickey.exe': unexpected file size"); return; } interpreterFile.seek(32476); // offset of font data // allocate space for font bitmap data byte *fontData = (uint8 *)calloc(256, 8); _fontData = fontData; _fontDataAllocated = fontData; // read font data, is already in the format that we need (plain bitmap 8x8) interpreterFile.read(fontData, 256 * 8); debug("AGI: Using Mickey Mouse font"); } // we create a bitmap out of the topaz data used in parallaction (which is normally found in staticres.cpp) // it's a recreation of the Amiga Topaz font but not really accurate void GfxFont::loadFontAmigaPseudoTopaz() { Graphics::Surface surf; surf.create(8, 8, Graphics::PixelFormat::createFormatCLUT8()); byte *fontData = (uint8 *)calloc(256, 8); _fontData = fontData; _fontDataAllocated = fontData; // copy first 32 PC-BIOS characters over memcpy(fontData, Graphics::DosFont::fontData_PCBIOS, FONT_DISPLAY_WIDTH * 32); fontData += FONT_DISPLAY_WIDTH * 32; Graphics::AmigaFont topaz; for (uint16 curChar = topaz.getLoChar(); curChar <= topaz.getHiChar(); curChar++) { topaz.drawChar(&surf, curChar, 0, 0, 0xff); // Turn it into 1bpp for (int y = 0; y < 8; y++) { byte line = 0; byte *ptr = (byte *)surf.getBasePtr(0, y); for (int x = 0; x < 8; x++, ptr++) { line <<= 1; line |= *ptr & 0x1; } *fontData++ = line; } memset(surf.getPixels(), 0, 8 * 8); } surf.free(); debug("AGI: Using recreation of Amiga Topaz font"); } void GfxFont::loadFontAppleIIgs() { Common::File fontFile; uint16 headerIIgs_OffsetMacHeader = 0; uint16 headerIIgs_Version = 0; uint16 macRecord_FirstChar = 0; uint16 macRecord_LastChar = 0; int16 macRecord_MaxKern = 0; uint16 macRecord_RectHeight = 0; uint16 macRecord_StrikeWidth = 0; uint16 strikeDataLen = 0; byte *strikeDataPtr = nullptr; uint16 actualCharacterCount = 0; uint16 totalCharacterCount = 0; uint16 *locationTablePtr = nullptr; uint16 *offsetWidthTablePtr = nullptr; uint16 curCharNr = 0; uint16 curRow = 0; uint16 curLocation = 0; uint16 curLocationBytes = 0; uint16 curLocationBits = 0; uint16 curCharOffsetWidth = 0; uint16 curCharOffset = 0; uint16 curCharWidth = 0; uint16 curStrikeWidth = 0; uint16 curPixelNr = 0; uint16 curBitMask = 0; int16 positionAdjust = 0; byte curByte = 0; byte fontByte = 0; uint16 strikeRowOffset = 0; uint16 strikeCurOffset = 0; byte *fontData = nullptr; if (!fontFile.open("agifont")) { // Continue, // This also happens when the user selected Apple IIgs as render for the palette for non-AppleIIgs games warning("Could not open file 'agifont' for Apple IIgs font"); return; } // Apple IIgs header headerIIgs_OffsetMacHeader = fontFile.readUint16LE(); fontFile.skip(2); // font family fontFile.skip(2); // font style fontFile.skip(2); // point size headerIIgs_Version = fontFile.readUint16LE(); fontFile.skip(2); // bounds type // end of Apple IIgs header // Macintosh font record fontFile.skip(2); // font type macRecord_FirstChar = fontFile.readUint16LE(); macRecord_LastChar = fontFile.readUint16LE(); fontFile.skip(2); // max width macRecord_MaxKern = fontFile.readSint16LE(); fontFile.skip(2); // negative descent fontFile.skip(2); // rect width macRecord_RectHeight = fontFile.readUint16LE(); fontFile.skip(2); // low word ptr table fontFile.skip(2); // font ascent fontFile.skip(2); // font descent fontFile.skip(2); // leading macRecord_StrikeWidth = fontFile.readUint16LE(); // security-checks if (headerIIgs_OffsetMacHeader != 6) error("AppleIIgs-font: unexpected header"); if (headerIIgs_Version != 0x0101) error("AppleIIgs-font: not a 1.1 font"); if ((macRecord_FirstChar != 0) || (macRecord_LastChar != 255)) error("AppleIIgs-font: unexpected characters"); if (macRecord_RectHeight != 8) error("AppleIIgs-font: expected 8x8 font"); // Calculate table sizes strikeDataLen = macRecord_StrikeWidth * macRecord_RectHeight * 2; actualCharacterCount = (macRecord_LastChar - macRecord_FirstChar + 1); totalCharacterCount = actualCharacterCount + 2; // replacement-char + extra character // Allocate memory for tables strikeDataPtr = (byte *)calloc(strikeDataLen, 1); locationTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // 1 word per character offsetWidthTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // ditto // read tables fontFile.read(strikeDataPtr, strikeDataLen); for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { locationTablePtr[curCharNr] = fontFile.readUint16LE(); } for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { offsetWidthTablePtr[curCharNr] = fontFile.readUint16LE(); } fontFile.close(); // allocate space for font bitmap data fontData = (uint8 *)calloc(256, 8); _fontData = fontData; _fontDataAllocated = fontData; // extract font bitmap data for (curCharNr = 0; curCharNr < actualCharacterCount; curCharNr++) { curCharOffsetWidth = offsetWidthTablePtr[curCharNr]; curLocation = locationTablePtr[curCharNr]; if (curCharOffsetWidth == 0xFFFF) { // character does not exist in font, use replacement character instead curCharOffsetWidth = offsetWidthTablePtr[actualCharacterCount]; curLocation = locationTablePtr[actualCharacterCount]; curStrikeWidth = locationTablePtr[actualCharacterCount + 1] - curLocation; } else { curStrikeWidth = locationTablePtr[curCharNr + 1] - curLocation; } // Figure out bytes + bits location curLocationBytes = curLocation >> 3; curLocationBits = curLocation & 0x0007; curCharWidth = curCharOffsetWidth & 0x00FF; // isolate width curCharOffset = curCharOffsetWidth >> 8; // isolate offset if (!curCharWidth) { fontData += 8; // skip over this character continue; } if (curCharWidth != 8) { if (curCharNr != 0x3B) error("AppleIIgs-font: expected 8x8 font"); } // Get all rows of the current character strikeRowOffset = 0; for (curRow = 0; curRow < macRecord_RectHeight; curRow++) { strikeCurOffset = strikeRowOffset + curLocationBytes; // Copy over bits fontByte = 0; curByte = strikeDataPtr[strikeCurOffset]; curBitMask = 0x80 >> curLocationBits; for (curPixelNr = 0; curPixelNr < curStrikeWidth; curPixelNr++) { fontByte = fontByte << 1; if (curByte & curBitMask) { fontByte |= 0x01; } curBitMask = curBitMask >> 1; if (!curBitMask) { curByte = strikeDataPtr[strikeCurOffset + 1]; curBitMask = 0x80; } } // adjust, so that it's aligned to the left (starting at 0x80 bit) fontByte = fontByte << (8 - curStrikeWidth); // now adjust according to offset + MaxKern positionAdjust = macRecord_MaxKern + curCharOffset; // adjust may be negative for space, or 8 for "empty" characters if (positionAdjust > 8) error("AppleIIgs-font: invalid character spacing"); if (positionAdjust < 0) { // negative adjust strangely happens for empty characters like space if (curStrikeWidth) error("AppleIIgs-font: invalid character spacing"); } if (positionAdjust > 0) { // move the amount of pixels to the right fontByte = fontByte >> positionAdjust; } *fontData = fontByte; fontData++; strikeRowOffset += macRecord_StrikeWidth * 2; } } free(offsetWidthTablePtr); free(locationTablePtr); free(strikeDataPtr); overwriteSaveRestoreDialogCharacter(); debug("AGI: Using Apple IIgs font"); } // Loads Atari ST font file // It's found inside Atari ST ROMs. Just search for "8x8 system font". Font starts 4 bytes before that. void GfxFont::loadFontAtariST(const Common::Path &fontFilename) { Common::File fontFile; uint16 header_FirstChar = 0; uint16 header_LastChar = 0; uint16 header_MaxWidth = 0; uint16 header_MaxHeight = 0; uint16 header_Flags = 0; //uint32 header_OffsetOfCharOffsets = 0; //uint32 header_OffsetOfFontData = 0; uint16 header_FormWidth = 0; uint16 header_FormHeight = 0; uint16 totalCharacterCount = 0; uint16 *charOffsetTablePtr = nullptr; byte *rawDataTablePtr = nullptr; uint16 curCharNr = 0; uint16 curCharRawOffset = 0; uint16 curCharDestOffset = 0; uint16 curRow = 0; byte *fontData = nullptr; if (!fontFile.open(fontFilename)) { // Continue, if file not found warning("Could not open file 'agi-font-atarist-system.bin' for Atari ST 8x8 system font"); return; } // Atari ST font header fontFile.skip(2); // face identifier fontFile.skip(2); // point size fontFile.skip(32); // font name header_FirstChar = fontFile.readUint16BE(); header_LastChar = fontFile.readUint16BE(); fontFile.skip(10); // aligntment of cells header_MaxWidth = fontFile.readUint16BE(); header_MaxHeight = fontFile.readUint16BE(); fontFile.skip(2); // left offset cel fontFile.skip(2); // right offset cel fontFile.skip(2); // number of pixels to thicken pixels fontFile.skip(2); // underline width fontFile.skip(2); // lightning mask fontFile.skip(2); // skewing mask header_Flags = fontFile.readUint16BE(); // bit 0 - default system font // bit 1 - horizontal offset table (not supported) // bit 2 - byte orientation word is high->low // bit 3 - mono spaced font fontFile.skip(4); // horizontal table offset fontFile.skip(4); // header_OffsetOfCharOffsets = fontFile.readUint32BE(); fontFile.skip(4); // header_OffsetOfFontData = fontFile.readUint32BE(); header_FormWidth = fontFile.readUint16BE(); header_FormHeight = fontFile.readUint16BE(); fontFile.skip(4); // pointer to next font totalCharacterCount = header_LastChar - header_FirstChar + 1; // security-checks if (header_MaxWidth > 8) error("AtariST-font: not a 8x8 font"); if (header_MaxHeight != 8) error("AtariST-font: not a 8x8 font"); if (header_FormHeight != 8) error("AtariST-font: not a 8x8 font"); if ((header_FirstChar != 0) || (header_LastChar != 255)) error("AtariST-font: unexpected characters"); if (header_FormWidth != totalCharacterCount) error("AtariST-font: header inconsistency"); if (!(header_Flags & 0x04)) error("AtariST-font: font data not in high->low order"); if (!(header_Flags & 0x08)) error("AtariST-font: not a mono-spaced font"); // Now we should normally use the offsets, but they don't make sense to me // So I just read the data directly. For the 8x8 system font that works fontFile.skip(2); // extra bytes // Allocate memory for tables charOffsetTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // 1 word per character rawDataTablePtr = (byte *)calloc(header_FormWidth, header_FormHeight); // Char-Offset Table (2 * total number of characters) for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { charOffsetTablePtr[curCharNr] = fontFile.readUint16BE(); } // Followed by actual font data // Attention: Atari ST fonts contain every same row of all characters after each other. // So it's basically like this: // [character data of first row of first character] // [character data of first row of second character] // ... // [character data of first row of last character] // [character data of second row of first character] fontFile.skip(2); // extra bytes fontFile.read(rawDataTablePtr, header_FormWidth * header_FormHeight); fontFile.close(); // allocate space for font bitmap data fontData = (uint8 *)calloc(256, 8); _fontData = fontData; _fontDataAllocated = fontData; // extract font bitmap data for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { // Figure out base offset from char offset table curCharRawOffset = charOffsetTablePtr[curCharNr] >> 3; curCharDestOffset = curCharNr * 8; // destination offset into our font data // now copy over every row of the character for (curRow = 0; curRow < header_FormHeight; curRow++) { fontData[curCharDestOffset] = rawDataTablePtr[curCharRawOffset]; curCharDestOffset++; curCharRawOffset += header_FormWidth; } } free(rawDataTablePtr); free(charOffsetTablePtr); overwriteSaveRestoreDialogCharacter(); debug("AGI: Using Atari ST 8x8 system font"); } // Loads a Sierra Hercules font file void GfxFont::loadFontHercules() { if (_vm->getLanguage() == Common::RU_RUS) { warning("Hercules font does not contain Russian characters, switching to default"); return; } Common::File fontFile; int32 fontFileSize = 0; byte *fontData = nullptr; byte *rawData = nullptr; uint16 rawDataPos = 0; uint16 curCharNr = 0; uint16 curCharLine = 0; if (fontFile.open("hgc_font")) { // hgc_font file found, this is interleaved font data 16x12, should be 3072 bytes // 24 bytes per character, 128 characters fontFileSize = fontFile.size(); if (fontFileSize == (128 * 24)) { // size seems to be fine fontData = (uint8 *)calloc(256, 32); _fontDataAllocated = fontData; rawData = (byte *)calloc(128, 24); fontFile.read(rawData, 128 * 24); // convert interleaved 16x12 -> non-interleaved 16x16 for (curCharNr = 0; curCharNr < 128; curCharNr++) { fontData += 4; // skip the first 2 lines for (curCharLine = 0; curCharLine < 6; curCharLine++) { fontData[0] = rawData[rawDataPos + 2 + 0]; fontData[1] = rawData[rawDataPos + 2 + 1]; fontData[2] = rawData[rawDataPos + 0 + 0]; fontData[3] = rawData[rawDataPos + 0 + 1]; rawDataPos += 4; fontData += 4; } fontData += 4; // skip the last 2 lines } free(rawData); } else { warning("Fontfile 'hgc_font': unexpected file size"); } fontFile.close(); } // It seems hgc_graf.ovl holds a low-res font. It makes no real sense to use it. // This was only done to AGI3 games and those rendered differently (2 pixel lines -> 3 pixel lines instead of 4) // User could copy hgc_font from another AGI game over to get the hires font working. #if 0 if (!_fontDataAllocated) { if (fontFile.open("hgc_graf.ovl")) { // hgc_graf.ovl file found, this is font data + code. non-interleaved font data, should be 3075 bytes // 16 bytes per character, 128 characters, 2048 bytes of font data, starting offset 21 fontFileSize = fontFile.size(); if (fontFileSize == 3075) { // size seems to be fine fontData = (uint8 *)calloc(256, 32); _fontDataAllocated = fontData; fontFile.seek(21); rawData = (byte *)calloc(128, 16); fontFile.read(rawData, 128 * 16); // repeat every line 2 times to get 16x16 pixels for (curCharNr = 0; curCharNr < 128; curCharNr++) { for (curCharLine = 0; curCharLine < 8; curCharLine++) { fontData[0] = rawData[rawDataPos + 0]; fontData[1] = rawData[rawDataPos + 1]; fontData[2] = rawData[rawDataPos + 0]; fontData[3] = rawData[rawDataPos + 1]; rawDataPos += 2; fontData += 4; } } free(rawData); } else { warning("Fontfile 'hgc_graf.ovl': unexpected file size"); } fontFile.close(); } } #endif if (_fontDataAllocated) { // font loaded _fontData = _fontDataAllocated; _fontIsHires = true; debug("AGI: Using Hercules hires font"); } else { // Continue, if no file was not found warning("Could not open/use file 'hgc_font' for Hercules hires font"); if (GUI::GuiManager::hasInstance()) { GUI::MessageDialog dialog(_("Could not open/use file 'hgc_font' for Hercules hires font.\nIf you have such file in other AGI (Sierra) game, you can copy it to the game directory")); dialog.runModal(); } } } } // End of namespace Agi