Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_AGS_FONT_RENDERER_H
#define AGS_SHARED_FONT_AGS_FONT_RENDERER_H
#include "common/std/utility.h"
#include "ags/shared/core/types.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
class BITMAP;
class IAGSFontRenderer {
public:
virtual bool LoadFromDisk(int fontNumber, int fontSize) = 0;
virtual void FreeMemory(int fontNumber) = 0;
virtual bool SupportsExtendedCharacters(int fontNumber) = 0;
virtual int GetTextWidth(const char *text, int fontNumber) = 0;
// Get actual height of the given line of text
virtual int GetTextHeight(const char *text, int fontNumber) = 0;
virtual void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) = 0;
virtual void AdjustYCoordinateForFont(int *ycoord, int fontNumber) = 0;
virtual void EnsureTextValidForFont(char *text, int fontNumber) = 0;
protected:
IAGSFontRenderer() {}
~IAGSFontRenderer() {}
};
// Extended font renderer interface.
// WARNING: this interface is exposed for plugins and declared for the second time in agsplugin.h
class IAGSFontRenderer2 : public IAGSFontRenderer {
public:
// Returns engine API version this font renderer complies to.
// Must not be lower than 26 (this interface was added at API v26).
virtual int GetVersion() = 0;
// Returns an arbitrary renderer name; this is for informational
// purposes only.
virtual const char *GetRendererName() = 0;
// Returns given font's name (if available).
virtual const char *GetFontName(int fontNumber) = 0;
// Returns the given font's height: that is the maximal vertical size
// that the font glyphs may occupy.
virtual int GetFontHeight(int fontNumber) = 0;
// Returns the given font's linespacing;
// is allowed to return 0, telling that no specific linespacing
// is assigned for this font.
virtual int GetLineSpacing(int fontNumber) = 0;
protected:
IAGSFontRenderer2() {}
~IAGSFontRenderer2() {}
};
// Font render params, mainly for dealing with various compatibility issues.
struct FontRenderParams {
// Font's render multiplier
int SizeMultiplier = 1;
int LoadMode = 0; // contains font flags from FFLG_LOADMODEMASK
};
// Describes loaded font's properties
struct FontMetrics {
// Nominal font's height, equals to the game-requested size of the font.
// This may or not be equal to font's face height; sometimes a font cannot
// be scaled exactly to particular size, and then nominal height appears different
// (usually - smaller) than the real one.
int NominalHeight = 0;
// Real font's height, equals to reported ascender + descender.
// This is what you normally think as a font's height.
int RealHeight = 0;
// Compatible height, equals to either NominalHeight or RealHeight,
// selected depending on the game settings.
// This property is used in calculating linespace, etc.
int CompatHeight = 0;
// Maximal vertical extent of a font (top; bottom), this tells the actual
// graphical bounds that may be occupied by font's glyphs.
// In a "proper" font this extent is (0; RealHeight-1), but "bad" fonts may
// have individual glyphs exceeding these bounds, in both directions.
// Note that "top" may be negative!
std::pair<int, int> VExtent;
inline int ExtentHeight() const { return VExtent.second - VExtent.first; }
};
// The strictly internal font renderer interface, not to use in plugin API.
// Contains methods necessary for built-in font renderers.
class IAGSFontRendererInternal : public IAGSFontRenderer2 {
public:
// Tells if this is a bitmap font (otherwise it's a vector font)
virtual bool IsBitmapFont() = 0;
// Load font, applying extended font rendering parameters
virtual bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) = 0;
// Fill FontMetrics struct; note that it may be left cleared if this is not supported
virtual void GetFontMetrics(int fontNumber, FontMetrics *metrics) = 0;
// Perform any necessary adjustments when the AA mode is toggled
virtual void AdjustFontForAntiAlias(int fontNumber, bool aa_mode) = 0;
protected:
IAGSFontRendererInternal() {}
~IAGSFontRendererInternal() {}
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,568 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/lib/alfont/alfont.h"
#include "common/std/vector.h"
#include "ags/shared/ac/common.h" // set_our_eip
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/font/ttf_font_renderer.h"
#include "ags/shared/font/wfn_font_renderer.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/gui/gui_defines.h" // MAXLINE
#include "ags/shared/util/string_utils.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
FontInfo::FontInfo()
: Flags(0)
, Size(0)
, SizeMultiplier(1)
, Outline(FONT_OUTLINE_NONE)
, YOffset(0)
, LineSpacing(0)
, AutoOutlineStyle(kSquared)
, AutoOutlineThickness(0) {
}
void init_font_renderer() {
alfont_init();
alfont_text_mode(-1);
}
void shutdown_font_renderer() {
set_our_eip(9919);
alfont_exit();
}
void adjust_y_coordinate_for_text(int *ypos, size_t fontnum) {
if (fontnum >= _GP(fonts).size() || !_GP(fonts)[fontnum].Renderer)
return;
_GP(fonts)[fontnum].Renderer->AdjustYCoordinateForFont(ypos, fontnum);
}
bool font_first_renderer_loaded() {
return _GP(fonts).size() > 0 && _GP(fonts)[0].Renderer != nullptr;
}
bool is_font_loaded(size_t fontNumber) {
return fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer != nullptr;
}
// Finish font's initialization
static void font_post_init(size_t fontNumber) {
Font &font = _GP(fonts)[fontNumber];
// If no font height property was provided, then try several methods,
// depending on which interface is available
if (font.Metrics.NominalHeight == 0 && font.Renderer) {
int height = 0;
if (font.Renderer2)
height = font.Renderer2->GetFontHeight(fontNumber);
if (height <= 0) {
// With the old renderer we have to rely on GetTextHeight;
// the implementations of GetTextHeight are allowed to return varied
// results depending on the text parameter.
// We use special line of text to get more or less reliable font height.
const char *height_test_string = "ZHwypgfjqhkilIK";
height = font.Renderer->GetTextHeight(height_test_string, fontNumber);
}
font.Metrics.NominalHeight = std::max(0, height);
font.Metrics.RealHeight = font.Metrics.NominalHeight;
font.Metrics.VExtent = std::make_pair(0, font.Metrics.RealHeight);
}
// Use either nominal or real pixel height to define font's logical height
// and default linespacing; logical height = nominal height is compatible with the old games
font.Metrics.CompatHeight = (font.Info.Flags & FFLG_REPORTNOMINALHEIGHT) != 0 ?
font.Metrics.NominalHeight : font.Metrics.RealHeight;
if (font.Info.Outline != FONT_OUTLINE_AUTO) {
font.Info.AutoOutlineThickness = 0;
}
// If no linespacing property was provided, then try several methods,
// depending on which interface is available
font.LineSpacingCalc = font.Info.LineSpacing;
if (font.Info.LineSpacing == 0) {
int linespacing = 0;
if (font.Renderer2)
linespacing = font.Renderer2->GetLineSpacing(fontNumber);
if (linespacing > 0) {
font.LineSpacingCalc = linespacing;
} else {
// Calculate default linespacing from the font height + outline thickness.
font.Info.Flags |= FFLG_DEFLINESPACING;
font.LineSpacingCalc = font.Metrics.CompatHeight + 2 * font.Info.AutoOutlineThickness;
}
}
}
static void font_replace_renderer(size_t fontNumber, IAGSFontRenderer* renderer, IAGSFontRenderer2* renderer2) {
_GP(fonts)[fontNumber].Renderer = renderer;
_GP(fonts)[fontNumber].Renderer2 = renderer2;
// If this is one of our built-in font renderers, then correctly
// reinitialize interfaces and font metrics
if ((renderer == &_GP(ttfRenderer)) || (renderer == &_GP(wfnRenderer))) {
_GP(fonts)[fontNumber].RendererInt = static_cast<IAGSFontRendererInternal*>(renderer);
_GP(fonts)[fontNumber].RendererInt->GetFontMetrics(fontNumber, &_GP(fonts)[fontNumber].Metrics);
} else {
// Otherwise, this is probably coming from plugin
_GP(fonts)[fontNumber].RendererInt = nullptr;
_GP(fonts)[fontNumber].Metrics = FontMetrics(); // reset to defaults
}
font_post_init(fontNumber);
}
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer *renderer) {
if (fontNumber >= _GP(fonts).size())
return nullptr;
IAGSFontRenderer* old_render = _GP(fonts)[fontNumber].Renderer;
font_replace_renderer(fontNumber, renderer, nullptr);
return old_render;
}
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer2 *renderer) {
if (fontNumber >= _GP(fonts).size())
return nullptr;
IAGSFontRenderer* old_render = _GP(fonts)[fontNumber].Renderer;
font_replace_renderer(fontNumber, renderer, renderer);
return old_render;
}
void font_recalc_metrics(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return;
_GP(fonts)[fontNumber].Metrics = FontMetrics();
font_post_init(fontNumber);
}
bool is_bitmap_font(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].RendererInt)
return false;
return _GP(fonts)[fontNumber].RendererInt->IsBitmapFont();
}
bool font_supports_extended_characters(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return false;
return _GP(fonts)[fontNumber].Renderer->SupportsExtendedCharacters(fontNumber);
}
const char *get_font_name(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer2)
return "";
const char *name = _GP(fonts)[fontNumber].Renderer2->GetFontName(fontNumber);
return name ? name : "";
}
int get_font_flags(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return 0;
return _GP(fonts)[fontNumber].Info.Flags;
}
void ensure_text_valid_for_font(char *text, size_t fontnum) {
if (fontnum >= _GP(fonts).size() || !_GP(fonts)[fontnum].Renderer)
return;
_GP(fonts)[fontnum].Renderer->EnsureTextValidForFont(text, fontnum);
}
int get_font_scaling_mul(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Info.SizeMultiplier;
}
int get_text_width(const char *texx, size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Renderer->GetTextWidth(texx, fontNumber);
}
int get_text_width_outlined(const char *text, size_t font_number) {
if (font_number >= _GP(fonts).size() || !_GP(fonts)[font_number].Renderer)
return 0;
if (text == nullptr || text[0] == 0) // we ignore outline width since the text is empty
return 0;
int self_width = _GP(fonts)[font_number].Renderer->GetTextWidth(text, font_number);
int outline = _GP(fonts)[font_number].Info.Outline;
if (outline < 0 || static_cast<size_t>(outline) > _GP(fonts).size()) { // FONT_OUTLINE_AUTO or FONT_OUTLINE_NONE
return self_width + 2 * _GP(fonts)[font_number].Info.AutoOutlineThickness;
}
int outline_width = _GP(fonts)[outline].Renderer->GetTextWidth(text, outline);
return MAX(self_width, outline_width);
}
int get_text_height(const char *text, size_t font_number) {
if (font_number >= _GP(fonts).size() || !_GP(fonts)[font_number].Renderer)
return 0;
return _GP(fonts)[font_number].Renderer->GetTextHeight(text, font_number);
}
int get_font_outline(size_t font_number) {
if (font_number >= _GP(fonts).size())
return FONT_OUTLINE_NONE;
return _GP(fonts)[font_number].Info.Outline;
}
int get_font_outline_thickness(size_t font_number) {
if (font_number >= _GP(fonts).size())
return 0;
return _GP(fonts)[font_number].Info.AutoOutlineThickness;
}
void set_font_outline(size_t font_number, int outline_type,
enum FontInfo::AutoOutlineStyle style, int thickness) {
if (font_number >= _GP(fonts).size())
return;
_GP(fonts)[font_number].Info.Outline = outline_type;
_GP(fonts)[font_number].Info.AutoOutlineStyle = style;
_GP(fonts)[font_number].Info.AutoOutlineThickness = thickness;
}
bool is_font_antialiased(size_t font_number) {
if (font_number >= _GP(fonts).size())
return false;
return ShouldAntiAliasText() && !is_bitmap_font(font_number);
}
int get_font_height(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Metrics.CompatHeight;
}
int get_font_height_outlined(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
int self_height = _GP(fonts)[fontNumber].Metrics.CompatHeight;
int outline = _GP(fonts)[fontNumber].Info.Outline;
if (outline < 0 || static_cast<size_t>(outline) > _GP(fonts).size()) { // FONT_OUTLINE_AUTO or FONT_OUTLINE_NONE
return self_height + 2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness;
}
int outline_height = _GP(fonts)[outline].Metrics.CompatHeight;
return MAX(self_height, outline_height);
}
int get_font_surface_height(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return 0;
return _GP(fonts)[fontNumber].Metrics.ExtentHeight();
}
std::pair<int, int> get_font_surface_extent(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size() || !_GP(fonts)[fontNumber].Renderer)
return std::make_pair(0, 0);
return _GP(fonts)[fontNumber].Metrics.VExtent;
}
int get_font_linespacing(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc;
}
void set_font_linespacing(size_t fontNumber, int spacing) {
if (fontNumber < _GP(fonts).size()) {
_GP(fonts)[fontNumber].Info.Flags &= ~FFLG_DEFLINESPACING;
_GP(fonts)[fontNumber].Info.LineSpacing = spacing;
_GP(fonts)[fontNumber].LineSpacingCalc = spacing;
}
}
int get_text_lines_height(size_t fontNumber, size_t numlines) {
if (fontNumber >= _GP(fonts).size() || numlines == 0)
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc * (numlines - 1) +
(_GP(fonts)[fontNumber].Metrics.CompatHeight +
2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness);
}
int get_text_lines_surf_height(size_t fontNumber, size_t numlines) {
if (fontNumber >= _GP(fonts).size() || numlines == 0)
return 0;
return _GP(fonts)[fontNumber].LineSpacingCalc * (numlines - 1) +
(_GP(fonts)[fontNumber].Metrics.RealHeight +
2 * _GP(fonts)[fontNumber].Info.AutoOutlineThickness);
}
// Replaces AGS-specific linebreak tags with common '\n'
void unescape_script_string(const char *cstr, std::vector<char> &out) {
out.clear();
// Handle the special case of the first char
if (cstr[0] == '[') {
out.push_back('\n');
cstr++;
}
// Replace all other occurrences as they're found
// NOTE: we do not need to decode utf8 here, because
// we are only searching for low-code ascii chars.
const char *off;
for (off = cstr; *off; ++off) {
if (*off != '[') continue;
if (*(off - 1) == '\\') {
// convert \[ into [
out.insert(out.end(), cstr, off - 1);
out.push_back('[');
} else {
// convert [ into \n
out.insert(out.end(), cstr, off);
out.push_back('\n');
}
cstr = off + 1;
}
out.insert(out.end(), cstr, off + 1);
}
// Break up the text into lines
size_t split_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) {
// NOTE: following hack accommodates for the legacy math mistake in split_lines.
// It's hard to tell how crucial it is for the game looks, so research may be needed.
// TODO: IMHO this should rely not on game format, but script API level, because it
// defines necessary adjustments to game scripts. If you want to fix this, find a way to
// pass this flag here all the way from game.options[OPT_BASESCRIPTAPI] (or game format).
//
// if (game.options[OPT_BASESCRIPTAPI] < $Your current version$)
wii -= 1;
lines.Reset();
unescape_script_string(todis, lines.LineBuf);
char *theline = &lines.LineBuf.front();
char *scan_ptr = theline;
char *prev_ptr = theline;
char *last_whitespace = nullptr;
while (1) {
char *split_at = nullptr;
if (*scan_ptr == 0) {
// end of the text, add the last line if necessary
if (scan_ptr > theline) {
lines.Add(theline);
}
break;
}
if (*scan_ptr == ' ')
last_whitespace = scan_ptr;
// force end of line with the \n character
if (*scan_ptr == '\n') {
split_at = scan_ptr;
// otherwise, see if we are too wide
} else {
// temporarily terminate the line in the *next* char and test its width
char *next_ptr = scan_ptr;
ugetx(&next_ptr);
const int next_chwas = ugetc(next_ptr);
*next_ptr = 0;
if (get_text_width_outlined(theline, fonnt) > wii) {
// line is too wide, order the split
if (last_whitespace)
// revert to the last whitespace
split_at = last_whitespace;
else
// single very wide word, display as much as possible
split_at = prev_ptr;
}
// restore the character that was there before
usetc(next_ptr, next_chwas);
}
if (split_at == nullptr) {
prev_ptr = scan_ptr;
ugetx(&scan_ptr);
} else {
// check if even one char cannot fit...
if (split_at == theline && !((*theline == ' ') || (*theline == '\n'))) {
// cannot split with current width restriction
lines.Reset();
break;
}
// add this line; do the temporary terminator trick again
const int next_chwas = ugetc(split_at);
*split_at = 0;
lines.Add(theline);
usetc(split_at, next_chwas);
// check if too many lines
if (lines.Count() >= max_lines) {
lines[lines.Count() - 1].Append("...");
break;
}
// the next line starts from the split point
theline = split_at;
// skip the space or new line that caused the line break
if ((*theline == ' ') || (*theline == '\n'))
theline++;
scan_ptr = theline;
prev_ptr = theline;
last_whitespace = nullptr;
}
}
return lines.Count();
}
void wouttextxy(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx) {
if (fontNumber >= _GP(fonts).size())
return;
yyy += _GP(fonts)[fontNumber].Info.YOffset;
if (yyy > ds->GetClip().Bottom)
return; // each char is clipped but this speeds it up
if (_GP(fonts)[fontNumber].Renderer != nullptr) {
if (text_color == makeacol32(255, 0, 255, 255)) { // transparent color (magenta)
// WORKAROUND: Some Allegro routines are not implemented and alfont treats some magenta texts as invisible
// even if the alpha channel is fully opaque
// Slightly change the value if the game uses that color for fonts, so that they don't turn invisible
debug(0, "Overriding transparent text color!");
text_color--;
}
_GP(fonts)[fontNumber].Renderer->RenderText(texx, fontNumber, (BITMAP *)ds->GetAllegroBitmap(), xxx, yyy, text_color);
}
}
void set_fontinfo(size_t fontNumber, const FontInfo &finfo) {
if (fontNumber < _GP(fonts).size() && _GP(fonts)[fontNumber].Renderer) {
_GP(fonts)[fontNumber].Info = finfo;
font_post_init(fontNumber);
}
}
FontInfo get_fontinfo(size_t font_number) {
if (font_number < _GP(fonts).size())
return _GP(fonts)[font_number].Info;
return FontInfo();
}
// Loads a font from disk
bool load_font_size(size_t fontNumber, const FontInfo &font_info) {
if (_GP(fonts).size() <= fontNumber)
_GP(fonts).resize(fontNumber + 1);
else
wfreefont(fontNumber);
FontRenderParams params;
params.SizeMultiplier = font_info.SizeMultiplier;
params.LoadMode = (font_info.Flags & FFLG_LOADMODEMASK);
FontMetrics metrics;
Font &font = _GP(fonts)[fontNumber];
String src_filename;
if (_GP(ttfRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
font.Renderer = &_GP(ttfRenderer);
font.Renderer2 = &_GP(ttfRenderer);
font.RendererInt = &_GP(ttfRenderer);
} else if (_GP(wfnRenderer).LoadFromDiskEx(fontNumber, font_info.Size, &src_filename, &params, &metrics)) {
font.Renderer = &_GP(wfnRenderer);
font.Renderer2 = &_GP(wfnRenderer);
font.RendererInt = &_GP(wfnRenderer);
}
if (!font.Renderer)
return false;
font.Info = font_info;
font.Metrics = metrics;
font_post_init(fontNumber);
Debug::Printf("Loaded font %d: %s, req size: %d; nominal h: %d, real h: %d, extent: %d,%d",
fontNumber, src_filename.GetCStr(), font_info.Size, font.Metrics.NominalHeight, font.Metrics.RealHeight,
font.Metrics.VExtent.first, font.Metrics.VExtent.second);
return true;
}
void wgtprintf(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...) {
if (fontNumber >= _GP(fonts).size())
return;
char tbuffer[2000];
va_list ap;
va_start(ap, fmt);
vsnprintf(tbuffer, sizeof(tbuffer), fmt, ap);
va_end(ap);
wouttextxy(ds, xxx, yyy, fontNumber, text_color, tbuffer);
}
void alloc_font_outline_buffers(size_t font_number,
Bitmap **text_stencil, Bitmap **outline_stencil,
int text_width, int text_height, int color_depth) {
if (font_number >= _GP(fonts).size())
return;
Font &f = _GP(fonts)[font_number];
const int thick = 2 * f.Info.AutoOutlineThickness;
if (f.TextStencil.IsNull() || (f.TextStencil.GetColorDepth() != color_depth) ||
(f.TextStencil.GetWidth() < text_width) || (f.TextStencil.GetHeight() < text_height)) {
int sw = f.TextStencil.IsNull() ? 0 : f.TextStencil.GetWidth();
int sh = f.TextStencil.IsNull() ? 0 : f.TextStencil.GetHeight();
sw = MAX(text_width, sw);
sh = MAX(text_height, sh);
f.TextStencil.Create(sw, sh, color_depth);
f.OutlineStencil.Create(sw, sh + thick, color_depth);
f.TextStencilSub.CreateSubBitmap(&f.TextStencil, RectWH(Size(text_width, text_height)));
f.OutlineStencilSub.CreateSubBitmap(&f.OutlineStencil, RectWH(Size(text_width, text_height + thick)));
} else {
f.TextStencilSub.ResizeSubBitmap(text_width, text_height);
f.OutlineStencilSub.ResizeSubBitmap(text_width, text_height + thick);
}
*text_stencil = &f.TextStencilSub;
*outline_stencil = &f.OutlineStencilSub;
}
void adjust_fonts_for_render_mode(bool aa_mode) {
for (size_t i = 0; i < _GP(fonts).size(); ++i) {
if (_GP(fonts)[i].RendererInt != nullptr)
_GP(fonts)[i].RendererInt->AdjustFontForAntiAlias(i, aa_mode);
}
}
void wfreefont(size_t fontNumber) {
if (fontNumber >= _GP(fonts).size())
return;
_GP(fonts)[fontNumber].TextStencilSub.Destroy();
_GP(fonts)[fontNumber].OutlineStencilSub.Destroy();
_GP(fonts)[fontNumber].TextStencil.Destroy();
_GP(fonts)[fontNumber].OutlineStencil.Destroy();
if (_GP(fonts)[fontNumber].Renderer != nullptr)
_GP(fonts)[fontNumber].Renderer->FreeMemory(fontNumber);
_GP(fonts)[fontNumber].Renderer = nullptr;
}
void free_all_fonts() {
for (size_t i = 0; i < _GP(fonts).size(); ++i) {
if (_GP(fonts)[i].Renderer != nullptr)
_GP(fonts)[i].Renderer->FreeMemory(i);
}
_GP(fonts).clear();
}
} // namespace AGS3

View File

@@ -0,0 +1,194 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_FONTS_H
#define AGS_SHARED_FONT_FONTS_H
#include "common/std/vector.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/util/string.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/font/ags_font_renderer.h"
#include "ags/shared/gfx/allegro_bitmap.h"
namespace AGS3 {
class IAGSFontRenderer;
class IAGSFontRenderer2;
class IAGSFontRendererInternal;
struct FontInfo;
struct FontRenderParams;
namespace AGS {
namespace Shared {
struct Font {
// Classic font renderer interface
IAGSFontRenderer *Renderer = nullptr;
// Extended font renderer interface (optional)
IAGSFontRenderer2 *Renderer2 = nullptr;
// Internal interface (only for built-in renderers)
IAGSFontRendererInternal *RendererInt = nullptr;
FontInfo Info;
// Values received from the renderer and saved for the reference
FontMetrics Metrics;
// Precalculated linespacing, based on font properties and compat settings
int LineSpacingCalc = 0;
// Outline buffers
Bitmap TextStencil, TextStencilSub;
Bitmap OutlineStencil, OutlineStencilSub;
Font() {}
};
} // namespace Shared
} // namespace AGS
using namespace AGS;
void init_font_renderer();
void shutdown_font_renderer();
void adjust_y_coordinate_for_text(int *ypos, size_t fontnum);
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer *renderer);
IAGSFontRenderer *font_replace_renderer(size_t fontNumber, IAGSFontRenderer2 *renderer);
void font_recalc_metrics(size_t fontNumber);
bool font_first_renderer_loaded();
bool is_font_loaded(size_t fontNumber);
bool is_bitmap_font(size_t fontNumber);
bool font_supports_extended_characters(size_t fontNumber);
// Get font's name, if it's available, otherwise returns empty string
const char *get_font_name(size_t fontNumber);
// Get a collection of FFLG_* flags corresponding to this font
int get_font_flags(size_t fontNumber);
// TODO: with changes to WFN font renderer that implemented safe rendering of
// strings containing invalid chars (since 3.3.1) this function is not
// important, except for (maybe) few particular cases.
// Furthermore, its use complicated things, because AGS could modify some texts
// at random times (usually - drawing routines).
// Need to check whether it is safe to completely remove it.
void ensure_text_valid_for_font(char *text, size_t fontnum);
// Get font's scaling multiplier
int get_font_scaling_mul(size_t fontNumber);
// Calculate actual width of a line of text
int get_text_width(const char *texx, size_t fontNumber);
// Get the maximal width of the line of text, with corresponding outlining
int get_text_width_outlined(const char *text, size_t font_number);
// Get the maximal height of the line of text;
// note that this won't be a nominal font's height, but the max of each met glyph's graphical height.
int get_text_height(const char *text, size_t font_number);
// Get font's height; this value is used for logical arrangement of UI elements;
// note that this is a "formal" font height, that may have different value
// depending on compatibility mode (used when running old games);
int get_font_height(size_t fontNumber);
// Get the maximal height of the given font, with corresponding outlining
int get_font_height_outlined(size_t fontNumber);
// Get font's surface height: this always returns the height enough to accommodate
// font letters on a bitmap or a texture; the distinction is needed for compatibility reasons
int get_font_surface_height(size_t fontNumber);
// Get font's maximal graphical extent: this means the farthest vertical positions of glyphs,
// relative to the "pen" position. Besides letting to calculate the surface height,
// this information also lets to detect if some of the glyphs may appear above y0.
std::pair<int, int> get_font_surface_extent(size_t fontNumber);
// Get font's line spacing
int get_font_linespacing(size_t fontNumber);
// Set font's line spacing
void set_font_linespacing(size_t fontNumber, int spacing);
// Get font's outline type
int get_font_outline(size_t font_number);
// Get font's automatic outline thickness (if set)
int get_font_outline_thickness(size_t font_number);
// Gets the total maximal height of the given number of lines printed with the given font;
// note that this uses formal font height, for compatibility purposes
int get_text_lines_height(size_t fontNumber, size_t numlines);
// Gets the height of a graphic surface enough to accommodate this number of text lines;
// note this accounts for the real pixel font height
int get_text_lines_surf_height(size_t fontNumber, size_t numlines);
// Set font's outline type
void set_font_outline(size_t font_number, int outline_type,
enum FontInfo::AutoOutlineStyle style = FontInfo::kSquared, int thickness = 1);
bool is_font_antialiased(size_t font_number);
// Outputs a single line of text on the defined position on bitmap, using defined font, color and parameters
void wouttextxy(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx);
// Assigns FontInfo to the font
void set_fontinfo(size_t fontNumber, const FontInfo &finfo);
// Gets full information about the font
FontInfo get_fontinfo(size_t font_number);
// Loads a font from disk
bool load_font_size(size_t fontNumber, const FontInfo &font_info); void wgtprintf(Shared::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...);
// Allocates two outline stencil buffers, or returns previously creates ones;
// these buffers are owned by the font, they should not be deleted by the caller.
void alloc_font_outline_buffers(size_t font_number,
Shared::Bitmap **text_stencil, Shared::Bitmap **outline_stencil,
int text_width, int text_height, int color_depth);
// Perform necessary adjustments on all fonts in case the text render mode changed (anti-aliasing etc)
void adjust_fonts_for_render_mode(bool aa_mode);
// Free particular font's data
void wfreefont(size_t fontNumber);
// Free all fonts data
void free_all_fonts();
// Tells if the text should be antialiased when possible
bool ShouldAntiAliasText();
// SplitLines class represents a list of lines and is meant to reduce
// subsequent memory (de)allocations if used often during game loops
// and drawing. For that reason it is not equivalent to std::vector,
// but keeps constructed String buffers intact for most time.
// TODO: implement proper strings pool.
class SplitLines {
public:
inline size_t Count() const {
return _count;
}
inline const Shared::String &operator[](size_t i) const {
return _pool[i];
}
inline Shared::String &operator[](size_t i) {
return _pool[i];
}
inline void Clear() {
_pool.clear();
_count = 0;
}
inline void Reset() {
_count = 0;
}
inline void Add(const char *cstr) {
if (_pool.size() == _count) _pool.resize(_count + 1);
_pool[_count++].SetString(cstr);
}
// An auxiliary line processing buffer
std::vector<char> LineBuf;
private:
std::vector<Shared::String> _pool;
size_t _count; // actual number of lines in use
};
// Break up the text into lines restricted by the given width;
// returns number of lines, or 0 if text cannot be split well to fit in this width
size_t split_lines(const char *texx, SplitLines &lines, int width, int fontNumber, size_t max_lines = -1);
} // namespace AGS3
#endif

View File

@@ -0,0 +1,187 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/font/ttf_font_renderer.h"
#include "ags/lib/alfont/alfont.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/ac/game_version.h"
#include "ags/globals.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/ac/game_struct_defines.h"
#include "ags/shared/font/fonts.h"
namespace AGS3 {
using namespace AGS::Shared;
// ***** TTF RENDERER *****
void TTFFontRenderer::AdjustYCoordinateForFont(int *ycoord, int /*fontNumber*/) {
// TTF fonts already have space at the top, so try to remove the gap
// TODO: adding -1 was here before (check the comment above),
// but how universal is this "space at the top"?
// Also, why is this function used only in one case of text rendering?
// Review this after we upgrade the font library.
ycoord[0]--;
}
void TTFFontRenderer::EnsureTextValidForFont(char * /*text*/, int /*fontNumber*/) {
// do nothing, TTF can handle all characters
}
int TTFFontRenderer::GetTextWidth(const char *text, int fontNumber) {
return alfont_text_length(_fontData[fontNumber].AlFont, text);
}
int TTFFontRenderer::GetTextHeight(const char * /*text*/, int fontNumber) {
return alfont_get_font_real_height(_fontData[fontNumber].AlFont);
}
void TTFFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) {
if (y > destination->cb) // optimisation
return;
// Y - 1 because it seems to get drawn down a bit
if ((ShouldAntiAliasText()) && (bitmap_color_depth(destination) > 8))
alfont_textout_aa(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour);
else
alfont_textout(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour);
}
bool TTFFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
}
bool TTFFontRenderer::IsBitmapFont() {
return false;
}
static int GetAlfontFlags(int load_mode) {
int flags = ALFONT_FLG_FORCE_RESIZE | ALFONT_FLG_SELECT_NOMINAL_SZ;
// Compatibility: font ascender is always adjusted to the formal font's height;
// EXCEPTION: not if it's a game made before AGS 3.4.1 with TTF anti-aliasing
// (the reason is uncertain, but this is to emulate old engine's behavior).
if (((load_mode & FFLG_ASCENDERFIXUP) != 0) &&
!(ShouldAntiAliasText() && (_G(loaded_game_file_version) < kGameVersion_341)))
flags |= ALFONT_FLG_ASCENDER_EQ_HEIGHT;
// Precalculate real glyphs extent (will make loading fonts relatively slower)
flags |= ALFONT_FLG_PRECALC_MAX_CBOX;
return flags;
}
// Loads a TTF font of a certain size
static ALFONT_FONT *LoadTTF(const String &filename, int fontSize, int alfont_flags) {
std::unique_ptr<Stream> reader(_GP(AssetMgr)->OpenAsset(filename));
if (!reader)
return nullptr;
const size_t lenof = reader->GetLength();
std::vector<char> buf; buf.resize(lenof);
reader->Read(&buf.front(), lenof);
reader.reset();
ALFONT_FONT *alfptr = alfont_load_font_from_mem(&buf.front(), lenof);
if (!alfptr)
return nullptr;
alfont_set_font_size_ex(alfptr, fontSize, alfont_flags);
return alfptr;
}
// Fill the FontMetrics struct from the given ALFONT
static void FillMetrics(ALFONT_FONT *alfptr, FontMetrics *metrics) {
metrics->NominalHeight = alfont_get_font_height(alfptr);
metrics->RealHeight = alfont_get_font_real_height(alfptr);
metrics->CompatHeight = metrics->NominalHeight; // just set to default here
alfont_get_font_real_vextent(alfptr, &metrics->VExtent.first, &metrics->VExtent.second);
// fixup vextent to be *not less* than realheight
metrics->VExtent.first = std::min(0, metrics->VExtent.first);
metrics->VExtent.second = std::max(metrics->RealHeight, metrics->VExtent.second);
}
bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize, String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) {
String filename = String::FromFormat("agsfnt%d.ttf", fontNumber);
if (fontSize <= 0)
fontSize = 8; // compatibility fix
assert(params);
FontRenderParams f_params = params ? *params : FontRenderParams();
if (f_params.SizeMultiplier > 1)
fontSize *= f_params.SizeMultiplier;
ALFONT_FONT *alfptr = LoadTTF(filename, fontSize,
GetAlfontFlags(f_params.LoadMode));
if (!alfptr)
return false;
_fontData[fontNumber].AlFont = alfptr;
_fontData[fontNumber].Params = f_params;
if (src_filename)
*src_filename = filename;
if (metrics)
FillMetrics(alfptr, metrics);
return true;
}
const char *TTFFontRenderer::GetFontName(int fontNumber) {
return alfont_get_name(_fontData[fontNumber].AlFont);
}
int TTFFontRenderer::GetFontHeight(int fontNumber) {
return alfont_get_font_real_height(_fontData[fontNumber].AlFont);
}
void TTFFontRenderer::GetFontMetrics(int fontNumber, FontMetrics *metrics) {
FillMetrics(_fontData[fontNumber].AlFont, metrics);
}
void TTFFontRenderer::AdjustFontForAntiAlias(int fontNumber, bool /*aa_mode*/) {
if (_G(loaded_game_file_version) < kGameVersion_341) {
ALFONT_FONT *alfptr = _fontData[fontNumber].AlFont;
const FontRenderParams &params = _fontData[fontNumber].Params;
int old_height = alfont_get_font_height(alfptr);
alfont_set_font_size_ex(alfptr, old_height, GetAlfontFlags(params.LoadMode));
}
}
void TTFFontRenderer::FreeMemory(int fontNumber) {
alfont_destroy_font(_fontData[fontNumber].AlFont);
_fontData.erase(fontNumber);
}
bool TTFFontRenderer::MeasureFontOfPointSize(const String &filename, int size_pt, FontMetrics *metrics) {
ALFONT_FONT *alfptr = LoadTTF(filename, size_pt, ALFONT_FLG_FORCE_RESIZE | ALFONT_FLG_SELECT_NOMINAL_SZ);
if (!alfptr)
return false;
FillMetrics(alfptr, metrics);
alfont_destroy_font(alfptr);
return true;
}
bool TTFFontRenderer::MeasureFontOfPixelHeight(const String &filename, int pixel_height, FontMetrics *metrics) {
ALFONT_FONT *alfptr = LoadTTF(filename, pixel_height, ALFONT_FLG_FORCE_RESIZE);
if (!alfptr)
return false;
FillMetrics(alfptr, metrics);
alfont_destroy_font(alfptr);
return true;
}
} // namespace AGS3

View File

@@ -0,0 +1,82 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_TTF_FONT_RENDERER_H
#define AGS_SHARED_FONT_TTF_FONT_RENDERER_H
#include "common/std/map.h"
#include "ags/shared/font/ags_font_renderer.h"
#include "ags/shared/util/string.h"
namespace AGS3 {
struct ALFONT_FONT;
class TTFFontRenderer : public IAGSFontRendererInternal {
public:
virtual ~TTFFontRenderer() {}
// IAGSFontRenderer implementation
bool LoadFromDisk(int fontNumber, int fontSize) override;
void FreeMemory(int fontNumber) override;
bool SupportsExtendedCharacters(int /*fontNumber*/) override {
return true;
}
int GetTextWidth(const char *text, int fontNumber) override;
int GetTextHeight(const char *text, int fontNumber) override;
void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override;
void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override;
void EnsureTextValidForFont(char *text, int fontNumber) override;
// IAGSFontRenderer2 implementation
int GetVersion() override { return 26; /* first compatible engine API version */ }
const char *GetRendererName() override { return "TTFFontRenderer"; }
const char *GetFontName(int fontNumber) override;
int GetFontHeight(int fontNumber) override;
int GetLineSpacing(int fontNumber) override { return 0; /* no specific spacing */ }
// IAGSFontRendererInternal implementation
bool IsBitmapFont() override;
bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) override;
void GetFontMetrics(int fontNumber, FontMetrics *metrics) override;
void AdjustFontForAntiAlias(int fontNumber, bool aa_mode) override;
//
// Utility functions
//
// Try load the TTF font using provided point size, and report its metrics
static bool MeasureFontOfPointSize(const AGS::Shared::String &filename, int size_pt, FontMetrics *metrics);
// Try load the TTF font, find the point size which results in pixel height
// as close to the requested as possible; report its metrics
static bool MeasureFontOfPixelHeight(const AGS::Shared::String &filename, int pixel_height, FontMetrics *metrics);
private:
struct FontData {
ALFONT_FONT *AlFont;
FontRenderParams Params;
};
std::map<int, FontData> _fontData;
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,196 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/algorithm.h"
#include "ags/shared/font/wfn_font.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/util/memory.h"
#include "ags/shared/util/stream.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
static const char *WFN_FILE_SIGNATURE = "WGT Font File ";
static const size_t WFN_FILE_SIG_LENGTH = 15;
static const size_t MinCharDataSize = sizeof(uint16_t) * 2;
WFNChar::WFNChar()
: Width(0)
, Height(0)
, Data(nullptr) {
}
void WFNChar::RestrictToBytes(size_t bytes) {
if (bytes < GetRequiredPixelSize())
Height = static_cast<uint16_t>(bytes / GetRowByteCount());
}
const WFNChar &WFNFont::GetChar(uint16_t code) const {
return code < _refs.size() ? *_refs[code] : _G(emptyChar);
}
void WFNFont::Clear() {
_refs.clear();
_items.clear();
_pixelData.clear();
}
WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) {
Clear();
const soff_t used_data_size = data_size > 0 ? data_size : in->GetLength();
// Read font header
char sig[WFN_FILE_SIG_LENGTH];
in->Read(sig, WFN_FILE_SIG_LENGTH);
if (strncmp(sig, WFN_FILE_SIGNATURE, WFN_FILE_SIG_LENGTH) != 0) {
Debug::Printf(kDbgMsg_Error, "\tWFN: bad format signature");
return kWFNErr_BadSignature; // bad format
}
const soff_t table_addr = static_cast<uint16_t>(in->ReadInt16()); // offset table relative address
if (table_addr < (soff_t)(WFN_FILE_SIG_LENGTH + sizeof(uint16_t)) || table_addr >= used_data_size) {
Debug::Printf(kDbgMsg_Error, "\tWFN: bad table address: %llu (%llu - %llu)", static_cast<int64>(table_addr),
static_cast<int64>(WFN_FILE_SIG_LENGTH + sizeof(uint16_t)), static_cast<int64>(used_data_size));
return kWFNErr_BadTableAddress; // bad table address
}
const soff_t offset_table_size = used_data_size - table_addr;
const soff_t raw_data_offset = WFN_FILE_SIG_LENGTH + sizeof(uint16_t);
const size_t total_char_data = static_cast<size_t>(table_addr - raw_data_offset);
const size_t char_count = static_cast<size_t>(offset_table_size / sizeof(uint16_t));
// We process character data in three steps:
// 1. For every character store offset of character item, excluding
// duplicates.
// 2. Allocate memory for character items and pixel array and copy
// appropriate data; test for possible format corruption.
// 3. Create array of references from characters to items; same item may be
// referenced by many characters.
WFNError err = kWFNErr_NoError;
if (total_char_data == 0u || char_count == 0u)
return kWFNErr_NoError; // no items
// Read character data array
std::vector<uint8_t> raw_data; raw_data.resize(total_char_data);
in->Read(&raw_data.front(), total_char_data);
// Read offset table
std::vector<uint16_t> offset_table; offset_table.resize(char_count);
in->ReadArrayOfInt16(reinterpret_cast<int16_t *>(&offset_table.front()), char_count);
// Read all referenced offsets in an unsorted vector
std::vector<uint16_t> offs;
offs.reserve(char_count); // reserve max possible offsets
for (size_t i = 0; i < char_count; ++i) {
const uint16_t off = offset_table[i];
if (off < raw_data_offset || (soff_t)(off + MinCharDataSize) > table_addr) {
Debug::Printf("\tWFN: character %d -- bad item offset: %d (%d - %d, +%d)",
i, off, raw_data_offset, table_addr, MinCharDataSize);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
continue; // bad character offset
}
offs.push_back(off);
}
// sort offsets vector and remove any duplicates
std::sort(offs.begin(), offs.end());
#if AGS_PLATFORM_SCUMMVM
// TODO: See if this works correctly
std::unique(offs.begin(), offs.end());
#else
std::vector<uint16_t>(offs.begin(), std::unique(offs.begin(), offs.end())).swap(offs);
#endif
// Now that we know number of valid character items, parse and store character data
WFNChar init_ch;
_items.resize(offs.size());
size_t total_pixel_size = 0;
for (size_t i = 0; i < _items.size(); ++i) {
const uint8_t *p_data = &raw_data[offs[i] - raw_data_offset];
init_ch.Width = Memory::ReadInt16LE(p_data);
init_ch.Height = Memory::ReadInt16LE(p_data + sizeof(uint16_t));
total_pixel_size += init_ch.GetRequiredPixelSize();
_items[i] = init_ch;
}
// Now that we know actual size of pixels in use, create pixel data array;
// since the items are sorted, the pixel data will be stored sequentially as well.
// At this point offs and _items have related elements in the same order.
_pixelData.resize(total_pixel_size);
std::vector<uint8_t>::iterator pixel_it = _pixelData.begin(); // write ptr
for (size_t i = 0; i < _items.size(); ++i) {
const size_t pixel_data_size = _items[i].GetRequiredPixelSize();
if (pixel_data_size == 0) {
Debug::Printf("\tWFN: item at off %d -- null size", offs[i]);
err = kWFNErr_HasBadCharacters;
continue; // just an empty character
}
const uint16_t raw_off = offs[i] - raw_data_offset + MinCharDataSize; // offset in raw array
size_t src_size = pixel_data_size;
if (i + 1 != _items.size() && (soff_t)(raw_off + src_size) > offs[i + 1] - raw_data_offset) { // character pixel data overlaps next character
Debug::Printf("\tWFN: item at off %d -- pixel data overlaps next known item (at %d, +%d)",
offs[i], offs[i + 1], MinCharDataSize + src_size);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
src_size = offs[i + 1] - offs[i] - MinCharDataSize;
}
if (raw_off + src_size > total_char_data) { // character pixel data overflow buffer
Debug::Printf("\tWFN: item at off %d -- pixel data exceeds available data (at %d, +%d)",
offs[i], table_addr, MinCharDataSize + src_size);
err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format
src_size = total_char_data - raw_off;
}
_items[i].RestrictToBytes(src_size);
assert(pixel_it + pixel_data_size <= _pixelData.end()); // should not normally fail
Common::copy(raw_data.begin() + raw_off, raw_data.begin() + (raw_off + src_size), pixel_it);
_items[i].Data = &(*pixel_it);
pixel_it += pixel_data_size;
}
// Create final reference array
_refs.resize(char_count);
for (size_t i = 0; i < char_count; ++i) {
const uint16_t off = offset_table[i];
// if bad character offset - reference empty character
if (off < raw_data_offset || (soff_t)(off + MinCharDataSize) > table_addr) {
_refs[i] = &_G(emptyChar);
} else {
// in usual case the offset table references items in strict order
if (i < _items.size() && offs[i] == off)
_refs[i] = &_items[i];
else {
// we know beforehand that such item must exist
std::vector<uint16_t>::const_iterator at = std::lower_bound(offs.begin(), offs.end(), off);
assert(at != offs.end() && *at == off && // should not normally fail
at - offs.begin() >= 0 && static_cast<size_t>(at - offs.begin()) < _items.size());
_refs[i] = &_items[at - offs.begin()]; // set up reference to item
}
}
}
return err;
}
} // namespace AGS3

View File

@@ -0,0 +1,108 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
//=============================================================================
//
// WFNFont - an immutable AGS font object.
//
//-----------------------------------------------------------------------------
//
// WFN format:
// - signature ( 15 )
// - offsets table offset ( 2 )
// - characters table (for unknown number of char items):
// - width ( 2 )
// - height ( 2 )
// - pixel bits ( (width / 8 + 1) * height )
// - any unknown data
// - offsets table (for X chars):
// - character offset ( 2 )
//
// NOTE: unfortunately, at the moment the format does not provide means to
// know the number of supported characters for certain, and the size of the
// data (file) is used to determine that.
//
//=============================================================================
#ifndef AGS_SHARED_FONT_WFN_FONT_H
#define AGS_SHARED_FONT_WFN_FONT_H
#include "common/std/vector.h"
#include "ags/shared/core/types.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
class Stream;
} // namespace Shared
} // namespace AGS
enum WFNError {
kWFNErr_NoError,
kWFNErr_BadSignature,
kWFNErr_BadTableAddress,
kWFNErr_HasBadCharacters
};
struct WFNChar {
uint16_t Width;
uint16_t Height;
const uint8_t *Data;
WFNChar();
inline size_t GetRowByteCount() const {
return (Width + 7) / 8;
}
inline size_t GetRequiredPixelSize() const {
return GetRowByteCount() * Height;
}
// Ensure character's width & height fit in given number of pixel bytes
void RestrictToBytes(size_t bytes);
};
class WFNFont {
public:
inline uint16_t GetCharCount() const {
return static_cast<uint16_t>(_refs.size());
}
// Get WFN character for the given code; if the character is missing, returns empty character
const WFNChar &GetChar(uint16_t code) const;
void Clear();
// Reads WFNFont object, using data_size bytes from stream; if data_size = 0,
// the available stream's length is used instead. Returns error code.
WFNError ReadFromFile(AGS::Shared::Stream *in, const soff_t data_size = 0);
protected:
std::vector<const WFNChar *> _refs; // reference array, contains pointers to elements of _items
std::vector<WFNChar> _items; // actual character items
std::vector<uint8_t> _pixelData; // pixel data array
};
} // namespace AGS3
#endif

View File

@@ -0,0 +1,163 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "ags/shared/font/wfn_font_renderer.h"
#include "ags/shared/ac/common.h" // our_eip
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/debugging/out.h"
#include "ags/shared/font/wfn_font.h"
#include "ags/shared/gfx/bitmap.h"
#include "ags/shared/util/stream.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
void WFNFontRenderer::AdjustYCoordinateForFont(int *ycoord, int fontNumber) {
// Do nothing
}
void WFNFontRenderer::EnsureTextValidForFont(char *text, int fontNumber) {
// Do nothing
}
int WFNFontRenderer::GetTextWidth(const char *text, int fontNumber) {
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
int text_width = 0;
for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
text_width += font->GetChar(code).Width;
}
return text_width * params.SizeMultiplier;
}
int WFNFontRenderer::GetTextHeight(const char *text, int fontNumber) {
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
int max_height = 0;
for (int code = ugetxc(&text); code; code = ugetxc(&text)) {
const uint16_t height = font->GetChar(code).Height;
max_height = std::max(max_height, static_cast<int>(height));
}
return max_height * params.SizeMultiplier;
}
static int RenderChar(Bitmap *ds, const int at_x, const int at_y, Rect clip,
const WFNChar &wfn_char, const int scale, const color_t text_color);
void WFNFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) {
int oldeip = get_our_eip();
set_our_eip(415);
const WFNFont *font = _fontData[fontNumber].Font;
const FontRenderParams &params = _fontData[fontNumber].Params;
Bitmap ds(destination, true);
// NOTE: allegro's putpixel ignores clipping (optimization),
// so we'll have to accommodate for that ourselves
Rect clip = ds.GetClip();
for (int code = ugetxc(&text); code; code = ugetxc(&text))
x += RenderChar(&ds, x, y, clip, font->GetChar(code), params.SizeMultiplier, colour);
set_our_eip(oldeip);
}
static int RenderChar(Bitmap *ds, const int at_x, const int at_y, Rect clip,
const WFNChar &wfn_char, const int scale, const color_t text_color) {
const int width = wfn_char.Width;
const int height = wfn_char.Height;
const unsigned char *actdata = wfn_char.Data;
const int bytewid = wfn_char.GetRowByteCount();
int sx = std::max(at_x, clip.Left), ex = clip.Right + 1;
int sy = std::max(at_y, clip.Top), ey = clip.Bottom + 1;
int sw = std::max(0, clip.Left - at_x);
int sh = std::max(0, clip.Top - at_y);
for (int h = sh, y = sy; h < height && y < ey; ++h, y += scale) {
for (int w = sw, x = sx; w < width && x < ex; ++w, x += scale) {
if (((actdata[h * bytewid + (w / 8)] & (0x80 >> (w % 8))) != 0)) {
if (scale > 1) {
ds->FillRect(RectWH(x, y, scale, scale), text_color);
} else {
ds->PutPixel(x, y, text_color);
}
}
}
}
return width * scale;
}
bool WFNFontRenderer::LoadFromDisk(int fontNumber, int fontSize) {
return LoadFromDiskEx(fontNumber, fontSize, nullptr, nullptr, nullptr);
}
bool WFNFontRenderer::IsBitmapFont() {
return true;
}
bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int /*fontSize*/, String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) {
String file_name;
Stream *ffi = nullptr;
file_name.Format("agsfnt%d.wfn", fontNumber);
ffi = _GP(AssetMgr)->OpenAsset(file_name);
if (ffi == nullptr) {
// actual font not found, try font 0 instead
// FIXME: this should not be done here in this font renderer implementation,
// but somewhere outside, when whoever calls this method
file_name = "agsfnt0.wfn";
ffi = _GP(AssetMgr)->OpenAsset(file_name);
if (ffi == nullptr)
return false;
}
WFNFont *font = new WFNFont();
WFNError err = font->ReadFromFile(ffi);
delete ffi;
if (err == kWFNErr_HasBadCharacters)
Debug::Printf(kDbgMsg_Warn, "WARNING: font '%s' has mistakes in data format, some characters may be displayed incorrectly", file_name.GetCStr());
else if (err != kWFNErr_NoError) {
delete font;
return false;
}
_fontData[fontNumber].Font = font;
_fontData[fontNumber].Params = params ? *params : FontRenderParams();
if (src_filename)
*src_filename = file_name;
if (metrics)
*metrics = FontMetrics();
return true;
}
void WFNFontRenderer::FreeMemory(int fontNumber) {
delete _fontData[fontNumber].Font;
_fontData.erase(fontNumber);
}
bool WFNFontRenderer::SupportsExtendedCharacters(int fontNumber) {
return _fontData[fontNumber].Font->GetCharCount() > 128;
}
} // namespace AGS3

View File

@@ -0,0 +1,71 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGS_SHARED_FONT_WFN_FONT_RENDERER_H
#define AGS_SHARED_FONT_WFN_FONT_RENDERER_H
#include "common/std/map.h"
#include "ags/lib/std.h"
#include "ags/shared/font/ags_font_renderer.h"
namespace AGS3 {
class WFNFont;
class WFNFontRenderer : public IAGSFontRendererInternal {
public:
// IAGSFontRenderer implementation
virtual ~WFNFontRenderer() {}
bool LoadFromDisk(int fontNumber, int fontSize) override;
void FreeMemory(int fontNumber) override;
bool SupportsExtendedCharacters(int fontNumber) override;
int GetTextWidth(const char *text, int fontNumber) override;
int GetTextHeight(const char *text, int fontNumber) override;
void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override;
void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override;
void EnsureTextValidForFont(char *text, int fontNumber) override;
// IAGSFontRenderer2 implementation
int GetVersion() override { return 26; /* first compatible engine API version */ }
const char *GetRendererName() override { return "WFNFontRenderer"; }
const char *GetFontName(int /*fontNumber*/) override { return ""; }
int GetFontHeight(int fontNumber) override { return 0; /* TODO? */ }
int GetLineSpacing(int fontNumber) override { return 0; /* no specific spacing */ }
// IAGSFontRendererInternal implementation
bool IsBitmapFont() override;
bool LoadFromDiskEx(int fontNumber, int fontSize, AGS::Shared::String *src_filename,
const FontRenderParams *params, FontMetrics *metrics) override;
void GetFontMetrics(int fontNumber, FontMetrics *metrics) override { *metrics = FontMetrics(); }
void AdjustFontForAntiAlias(int /*fontNumber*/, bool /*aa_mode*/) override { /* do nothing */ }
private:
struct FontData {
WFNFont *Font;
FontRenderParams Params;
};
std::map<int, FontData> _fontData;
};
} // namespace AGS3
#endif