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 <fabian@greffrath.com>
This commit is contained in:
Roman Fomin 2024-03-12 09:11:21 +07:00 committed by GitHub
parent 80704d11fc
commit 5ca3c41045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 496 additions and 16 deletions

View File

@ -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;

View File

@ -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

223
src/mn_font.c Normal file
View File

@ -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 <ctype.h>
#include <stdlib.h>
#include <string.h>
#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;
}

30
src/mn_font.h Normal file
View File

@ -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

View File

@ -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])

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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