From 5ca3c41045636b3fd7b0392dfc4a6850e675787a Mon Sep 17 00:00:00 2001 From: Roman Fomin Date: Tue, 12 Mar 2024 09:11:21 +0700 Subject: [PATCH] implement support for DBIGFONT lump in FON2 format (#1575) * add SLADE copyright * adapt V_LinearToTransPatch from Eternity Engine This will be useful for PNG support. * nit-pick: sort copyright holders in README.md --------- Co-authored-by: Fabian Greffrath --- README.md | 1 + src/CMakeLists.txt | 1 + src/mn_font.c | 223 +++++++++++++++++++++++++++++++++++++++++++++ src/mn_font.h | 30 ++++++ src/mn_menu.c | 22 +++-- src/mn_setup.c | 23 +++-- src/v_video.c | 209 +++++++++++++++++++++++++++++++++++++++++- src/v_video.h | 3 + 8 files changed, 496 insertions(+), 16 deletions(-) create mode 100644 src/mn_font.c create mode 100644 src/mn_font.h diff --git a/README.md b/README.md index bb7f8964..ebe8205b 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ Copyright: © 2005-2006 by Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko; © 2005-2018 Simon Howard; © 2006 Ben Ryves; + © 2008-2019 Simon Judd; © 2017 Christoph Oelckers; © 2019 Fernando Carmona Varo; © 2020 Alex Mayfield; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14614703..05482ec6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ set(WOOF_SOURCES m_fixed.h m_input.c m_input.h m_io.c m_io.h + mn_font.c mn_font.h mn_menu.c mn_menu.h mn_setup.c mn_setup.h m_misc.c m_misc.h diff --git a/src/mn_font.c b/src/mn_font.c new file mode 100644 index 00000000..e77792c3 --- /dev/null +++ b/src/mn_font.c @@ -0,0 +1,223 @@ +// +// SLADE - It's a Doom Editor +// Copyright(C) 2008 - 2019 Simon Judd +// Copyright(C) 2024 Roman Fomin +// +// 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 2 +// 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. +// +// DESCRIPTION: +// Load and draw ZDoom FON2 fonts + +#include +#include +#include + +#include "doomtype.h" +#include "i_video.h" +#include "m_swap.h" +#include "r_defs.h" +#include "v_video.h" +#include "w_wad.h" + +typedef struct +{ + uint16_t width; + patch_t *patch; +} fon2_char_t; + +typedef struct +{ + byte magic[4]; + uint16_t charheight; + byte firstc; + byte lastc; + byte constantw; + byte shading; + byte palsize; + byte kerning; // flag field, but with only one flag +} fon2_header_t; + +static fon2_char_t *chars = NULL; +static int numchars; +static int height; +static int firstc; +static int kerning; + +#define FON2_SPACE 12 + +boolean MN_LoadFon2(const byte *gfx_data, int size) +{ + if (size < (int)sizeof(fon2_header_t)) + { + return false; + } + + const fon2_header_t *header = (fon2_header_t *)gfx_data; + if (memcmp(header->magic, "FON2", 4)) + { + return false; + } + + height = SHORT(header->charheight); + if (height == 0) + { + return false; + } + + const byte *p = gfx_data + sizeof(fon2_header_t); + + if (header->kerning) + { + kerning = SHORT(*(int16_t *)p); + p += 2; + } + + firstc = header->firstc; + numchars = header->lastc - header->firstc + 1; + chars = malloc(numchars * sizeof(*chars)); + + for (int i = 0; i < numchars; ++i) + { + chars[i].width = SHORT(*(uint16_t *)p); + // The width information is enumerated for each character only if they + // are not constant width. Regardless, move the read pointer away after + // the last. + if (!(header->constantw) || (i == numchars - 1)) + { + p += 2; + } + } + + // Build translation table for palette. + byte *playpal = W_CacheLumpName("PLAYPAL", PU_CACHE); + byte *translate = malloc(header->palsize + 1); + for (int i = 0; i < header->palsize + 1; ++i) + { + int r = *p++; + int g = *p++; + int b = *p++; + translate[i] = I_GetPaletteIndex(playpal, r, g, b); + } + + // 0 is transparent, last is border color + byte color_key = translate[0]; + + // The picture data follows, using the same RLE as FON1 and IMGZ. + for (int i = 0; i < numchars; ++i) + { + // A big font is not necessarily continuous; several characters + // may be skipped; they are given a width of 0. + if (!chars[i].width) + { + continue; + } + + int numpixels = chars[i].width * height; + byte *data = malloc(numpixels); + byte *d = data; + byte code = 0; + int length = 0; + + while (numpixels) + { + code = *p++; + if (code < 0x80) + { + length = code + 1; + for (int k = 0; k < length; ++k) + { + d[k] = translate[p[k]]; + } + d += length; + p += length; + numpixels -= length; + } + else if (code > 0x80) + { + length = 0x101 - code; + code = *p++; + memset(d, translate[code], length); + d += length; + numpixels -= length; + } + } + + chars[i].patch = V_LinearToTransPatch(data, chars[i].width, height, + color_key); + free(data); + } + + free(translate); + return true; +} + +boolean MN_DrawFon2String(int x, int y, byte *cr, const char *str) +{ + if (!numchars) + { + return false; + } + + int c, cx; + + cx = x; + + while (*str) + { + c = *str++; + + c = toupper(c) - firstc; + if (c < 0 || c >= numchars) + { + cx += FON2_SPACE; + continue; + } + + if (chars[c].width) + { + V_DrawPatchTranslated(cx, y, chars[c].patch, cr); + cx += chars[c].width + kerning; + } + else + { + cx += FON2_SPACE + kerning; + } + } + + return true; +} + +int MN_GetFon2PixelWidth(const char *str) +{ + if (!numchars) + { + return 0; + } + + int len = 0; + int c; + + while (*str) + { + c = *str++; + + c = toupper(c) - firstc; + if (c < 0 || c > numchars) + { + len += FON2_SPACE; // space + continue; + } + + len += chars[c].width + kerning; + } + len -= kerning; + return len; +} diff --git a/src/mn_font.h b/src/mn_font.h new file mode 100644 index 00000000..8292de86 --- /dev/null +++ b/src/mn_font.h @@ -0,0 +1,30 @@ +// +// SLADE - It's a Doom Editor +// Copyright(C) 2008 - 2019 Simon Judd +// Copyright(C) 2024 Roman Fomin +// +// 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 2 +// 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. +// +// DESCRIPTION: +// Load and draw ZDoom FON2 fonts + +#ifndef MN_FONT +#define MN_FONT + +#include "doomtype.h" + +boolean MN_LoadFon2(const byte *gfx_data, int size); + +boolean MN_DrawFon2String(int x, int y, byte *cr, const char *str); + +int MN_GetFon2PixelWidth(const char *str); + +#endif diff --git a/src/mn_menu.c b/src/mn_menu.c index bdabe7ee..bb41607f 100644 --- a/src/mn_menu.c +++ b/src/mn_menu.c @@ -44,10 +44,11 @@ #include "m_input.h" #include "m_io.h" #include "m_misc.h" -#include "mn_snapshot.h" #include "m_swap.h" +#include "mn_font.h" #include "mn_menu.h" #include "mn_setup.h" +#include "mn_snapshot.h" #include "p_saveg.h" #include "r_defs.h" #include "r_draw.h" @@ -1727,8 +1728,8 @@ static menuitem_t SetupMenu[] = { // [FG] alternative text for missing menu graphics lumps {1, "M_GENERL", MN_General, 'g', "GENERAL", SETUP_MENU_RECT(0)}, {1, "M_KEYBND", MN_KeyBindings, 'k', "KEY BINDINGS", SETUP_MENU_RECT(1)}, - {1, "M_COMPAT", MN_Compat, 'd', "DOOM COMPATIBILITY", SETUP_MENU_RECT(2)}, - {1, "M_STAT", MN_StatusBar, 's', "STATUS BAR / HUD", SETUP_MENU_RECT(3)}, + {1, "M_COMPAT", MN_Compat, 'c', "COMPATIBILITY", SETUP_MENU_RECT(2)}, + {1, "M_STAT", MN_StatusBar, 's', "STATUS BAR/HUD", SETUP_MENU_RECT(3)}, {1, "M_AUTO", MN_Automap, 'a', "AUTOMAP", SETUP_MENU_RECT(4)}, {1, "M_WEAP", MN_Weapons, 'w', "WEAPONS", SETUP_MENU_RECT(5)}, {1, "M_ENEM", MN_Enemy, 'e', "ENEMIES", SETUP_MENU_RECT(6)}, @@ -1871,7 +1872,7 @@ void MN_SetNextMenuAlt(ss_types type) static void M_DrawSetup(void) { - MN_DrawTitle(124, 15, "M_OPTTTL", "OPTIONS"); + MN_DrawTitle(108, 15, "M_OPTTTL", "OPTIONS"); } ///////////////////////////// @@ -1936,6 +1937,12 @@ void M_Init(void) messageLastMenuActive = menuactive; quickSaveSlot = -1; + int lumpnum = W_CheckNumForName("DBIGFONT"); + if (lumpnum > 0) + { + MN_LoadFon2(W_CacheLumpNum(lumpnum, PU_CACHE), W_LumpLength(lumpnum)); + } + // Here we could catch other version dependencies, // like HELP1/2, and four episodes. @@ -3126,8 +3133,11 @@ void M_Drawer(void) { if (alttext) { - MN_DrawStringCR(x, y + 8 - MN_StringHeight(alttext) / 2, cr, - NULL, alttext); + if (!MN_DrawFon2String(x, y, cr, alttext)) + { + MN_DrawStringCR(x, y + 8 - MN_StringHeight(alttext) / 2, cr, + NULL, alttext); + } } } else if (name[0]) diff --git a/src/mn_setup.c b/src/mn_setup.c index 6adb8c2d..eace67fc 100644 --- a/src/mn_setup.c +++ b/src/mn_setup.c @@ -33,6 +33,7 @@ #include "m_input.h" #include "m_misc.h" #include "m_swap.h" +#include "mn_font.h" #include "mn_menu.h" #include "p_mobj.h" #include "r_bmaps.h" @@ -47,6 +48,7 @@ #include "sounds.h" #include "v_video.h" #include "w_wad.h" +#include "z_zone.h" static int M_GetKeyString(int c, int offset); static void DrawMenuString(int cx, int cy, int color); @@ -1533,7 +1535,7 @@ void MN_DrawStatusHUD(void) inhelpscreens = true; // killough 4/6/98: Force status bar redraw DrawBackground("FLOOR4_6"); // Draw background - MN_DrawTitle(59, 2, "M_STAT", "STATUS BAR / HUD"); + MN_DrawTitle(59, 2, "M_STAT", "STATUS BAR/HUD"); DrawTabs(); DrawInstructions(); DrawScreenItems(current_menu); @@ -1730,8 +1732,6 @@ static const char *default_complevel_strings[] = { setup_menu_t comp_settings1[] = { - {"Compatibility", S_SKIP | S_TITLE, M_X, M_SPC}, - {"Default Compatibility Level", S_CHOICE | S_LEVWARN, M_X, M_SPC, {"default_complevel"}, m_null, input_null, str_default_complevel}, @@ -1792,7 +1792,7 @@ void MN_DrawCompat(void) inhelpscreens = true; DrawBackground("FLOOR4_6"); // Draw background - MN_DrawTitle(52, 2, "M_COMPAT", "DOOM COMPATIBILITY"); + MN_DrawTitle(52, 2, "M_COMPAT", "COMPATIBILITY"); DrawInstructions(); DrawScreenItems(current_menu); @@ -3712,11 +3712,16 @@ void MN_DrawTitle(int x, int y, const char *patch, const char *alttext) else { // patch doesn't exist, draw some text in place of it - M_snprintf(menu_buffer, sizeof(menu_buffer), "%s", alttext); - DrawMenuString( - SCREENWIDTH / 2 - MN_StringWidth(alttext) / 2, - y + 8 - MN_StringHeight(alttext) / 2, // assumes patch height 16 - CR_TITLE); + if (!MN_DrawFon2String( + SCREENWIDTH / 2 - MN_GetFon2PixelWidth(alttext) / 2, + y, NULL, alttext)) + { + M_snprintf(menu_buffer, sizeof(menu_buffer), "%s", alttext); + DrawMenuString( + SCREENWIDTH / 2 - MN_StringWidth(alttext) / 2, + y + 8 - MN_StringHeight(alttext) / 2, // assumes patch height 16 + CR_TITLE); + } } } diff --git a/src/v_video.c b/src/v_video.c index 2443cd96..18dcadc0 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -1065,9 +1065,216 @@ void V_RestoreBuffer(void) dest_screen = I_VideoBuffer; } +typedef struct +{ + byte row_off; + byte *pixels; +} vpost_t; + +typedef struct +{ + vpost_t *posts; +} vcolumn_t; + +#define M_ARRAY_INIT_CAPACITY 16 +#include "m_array.h" + +#define PUTBYTE(r, v) \ + *r = (uint8_t)(v); \ + ++r + +#define PUTSHORT(r, v) \ + *(r + 0) = (byte)(((uint16_t)(v) >> 0) & 0xff); \ + *(r + 1) = (byte)(((uint16_t)(v) >> 8) & 0xff); \ + r += 2 + +#define PUTLONG(r, v) \ + *(r + 0) = (byte)(((uint32_t)(v) >> 0) & 0xff); \ + *(r + 1) = (byte)(((uint32_t)(v) >> 8) & 0xff); \ + *(r + 2) = (byte)(((uint32_t)(v) >> 16) & 0xff); \ + *(r + 3) = (byte)(((uint32_t)(v) >> 24) & 0xff); \ + r += 4 + // -// SCREEN SHOTS +// Converts a linear graphic to a patch with transparency. Mostly straight +// from psxwadgen, which is mostly straight from SLADE. // +patch_t *V_LinearToTransPatch(const byte *data, int width, int height, + int color_key) +{ + vcolumn_t *columns = NULL; + + // Go through columns + uint32_t offset = 0; + for (int c = 0; c < width; c++) + { + vcolumn_t col = {0}; + vpost_t post = {0}; + post.row_off = 0; + boolean ispost = false; + boolean first_254 = true; // first 254 pixels use absolute offsets + + offset = c; + byte row_off = 0; + for (int r = 0; r < height; r++) + { + // if we're at offset 254, create a dummy post for tall doom gfx + // support + if (row_off == 254) + { + // Finish current post if any + if (ispost) + { + array_push(col.posts, post); + post.pixels = NULL; + ispost = false; + } + + // Begin relative offsets + first_254 = false; + + // Create dummy post + post.row_off = 254; + array_push(col.posts, post); + + // Clear post + row_off = 0; + ispost = false; + } + + // If the current pixel is not transparent, add it to the current + // post + if (data[offset] != color_key) + { + // If we're not currently building a post, begin one and set its + // offset + if (!ispost) + { + // Set offset + post.row_off = row_off; + + // Reset offset if we're in relative offsets mode + if (!first_254) + { + row_off = 0; + } + + // Start post + ispost = true; + } + + // Add the pixel to the post + array_push(post.pixels, data[offset]); + } + else if (ispost) + { + // If the current pixel is transparent and we are currently + // building a post, add the current post to the list and clear + // it + array_push(col.posts, post); + post.pixels = NULL; + ispost = false; + } + + // Go to next row + offset += width; + ++row_off; + } + + // If the column ended with a post, add it + if (ispost) + { + array_push(col.posts, post); + } + + // Add the column data + array_push(columns, col); + + // Go to next column + ++offset; + } + + size_t size = 0; + + // Calculate needed memory size to allocate patch buffer + size += 4 * sizeof(int16_t); // 4 header shorts + size += array_size(columns) * sizeof(int32_t); // offsets table + + for (int c = 0; c < array_size(columns); c++) + { + for (int p = 0; p < array_size(columns[c].posts); p++) + { + size_t post_len = 0; + + post_len += 2; // two bytes for post header + post_len += 1; // dummy byte + post_len += array_size(columns[c].posts[p].pixels); // pixels + post_len += 1; // dummy byte + + size += post_len; + } + + size += 1; // room for 0xff cap byte + } + + byte *output = malloc(size); + byte *rover = output; + + // write header fields + PUTSHORT(rover, width); + PUTSHORT(rover, height); + // This is written to afterwards + PUTSHORT(rover, 0); + PUTSHORT(rover, 0); + + // set starting position of column offsets table, and skip over it + byte *col_offsets = rover; + rover += array_size(columns) * 4; + + for (int c = 0; c < array_size(columns); c++) + { + // write column offset to offset table + uint32_t offs = (uint32_t)(rover - output); + PUTLONG(col_offsets, offs); + + // write column posts + for (int p = 0; p < array_size(columns[c].posts); p++) + { + // Write row offset + PUTBYTE(rover, columns[c].posts[p].row_off); + + // Write number of pixels + int numpixels = array_size(columns[c].posts[p].pixels); + PUTBYTE(rover, numpixels); + + // Write pad byte + byte lastval = numpixels ? columns[c].posts[p].pixels[0] : 0; + PUTBYTE(rover, lastval); + + // Write pixels + for (int a = 0; a < numpixels; a++) + { + lastval = columns[c].posts[p].pixels[a]; + PUTBYTE(rover, lastval); + } + + // Write pad byte + PUTBYTE(rover, lastval); + + array_free(columns[c].posts[p].pixels); + } + + // Write 255 cap byte + PUTBYTE(rover, 0xff); + + array_free(columns[c].posts); + } + + array_free(columns); + + // Done! + return (patch_t *)output; +} // // V_ScreenShot diff --git a/src/v_video.h b/src/v_video.h index b629936b..4823b148 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -182,6 +182,9 @@ void V_DrawBackground(const char *patchname); int V_BloodColor(int blood); +struct patch_s *V_LinearToTransPatch(const byte *data, int width, int height, + int color_key); + void V_ScreenShot(void); #endif