mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 09:45:24 +02:00
792 lines
25 KiB
C++
792 lines
25 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// 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, version 2.0 or later versions.
|
|
|
|
// 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
// ============== NOTE!!!!
|
|
|
|
// Thanks to the JPCSP project! This sceFont implementation is basically a C++ take on JPCSP's font code.
|
|
// Some parts, especially in this file, were simply copied, so I guess this really makes this file GPL3.
|
|
|
|
#include <algorithm>
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Font/PGF.h"
|
|
|
|
#include "GPU/GPUCommon.h"
|
|
|
|
// These fonts, created by ttf2pgf, don't have complete glyph info and need to be identified.
|
|
static bool isJPCSPFont(const char *fontName) {
|
|
return !strcmp(fontName, "Liberation Sans") || !strcmp(fontName, "Liberation Serif") || !strcmp(fontName, "Sazanami") || !strcmp(fontName, "UnDotum") || !strcmp(fontName, "Microsoft YaHei");
|
|
}
|
|
|
|
// Gets a number of bits from an offset.
|
|
static int getBits(int numBits, const u8 *buf, size_t pos) {
|
|
_dbg_assert_msg_(numBits <= 32, "Unable to return more than 32 bits, %d requested", numBits);
|
|
|
|
const size_t wordpos = pos >> 5;
|
|
const u32_le *wordbuf = (const u32_le *)buf;
|
|
const u8 bitoff = pos & 31;
|
|
|
|
// Might just be in one, has to be within two.
|
|
if (bitoff + numBits < 32) {
|
|
const u32 mask = (1 << numBits) - 1;
|
|
return (wordbuf[wordpos] >> bitoff) & mask;
|
|
} else {
|
|
int v = wordbuf[wordpos] >> bitoff;
|
|
|
|
const u8 done = 32 - bitoff;
|
|
const u8 remaining = numBits - done;
|
|
if (remaining > 0) {
|
|
const u32 mask = (1 << remaining) - 1;
|
|
v |= (wordbuf[wordpos + 1] & mask) << done;
|
|
}
|
|
return v;
|
|
}
|
|
}
|
|
|
|
static inline int consumeBits(int numBits, const u8 *buf, size_t &pos) {
|
|
int v = getBits(numBits, buf, pos);
|
|
pos += numBits;
|
|
return v;
|
|
}
|
|
|
|
static std::vector<int> getTable(const u8 *buf, int bpe, size_t length) {
|
|
std::vector<int> vec;
|
|
vec.resize(length);
|
|
for (size_t i = 0; i < length; i++) {
|
|
vec[i] = getBits(bpe, buf, bpe * i);
|
|
}
|
|
return vec;
|
|
}
|
|
|
|
PGF::PGF()
|
|
: fontData(0) {
|
|
|
|
}
|
|
|
|
PGF::~PGF() {
|
|
delete [] fontData;
|
|
}
|
|
|
|
struct GlyphFromPGF1State {
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
int left;
|
|
int top;
|
|
int flags;
|
|
int shadowID;
|
|
int advanceH;
|
|
int advanceV;
|
|
int dimensionWidth, dimensionHeight;
|
|
int xAdjustH, xAdjustV;
|
|
int yAdjustH, yAdjustV;
|
|
u32 ptr;
|
|
|
|
operator Glyph() {
|
|
Glyph ret;
|
|
ret.w = w;
|
|
ret.h = h;
|
|
ret.left = left;
|
|
ret.top = top;
|
|
ret.flags = flags;
|
|
// Wasn't read before.
|
|
ret.shadowFlags = 0;
|
|
ret.shadowID = shadowID;
|
|
ret.advanceH = advanceH;
|
|
ret.advanceV = advanceV;
|
|
ret.dimensionWidth = dimensionWidth;
|
|
ret.dimensionHeight = dimensionHeight;
|
|
ret.xAdjustH = xAdjustH;
|
|
ret.xAdjustV = xAdjustV;
|
|
ret.yAdjustH = yAdjustH;
|
|
ret.yAdjustV = yAdjustV;
|
|
ret.ptr = ptr;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
void PGF::DoState(PointerWrap &p) {
|
|
auto s = p.Section("PGF", 1, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, header);
|
|
Do(p, rev3extra);
|
|
|
|
// Don't savestate size_t directly, 32-bit and 64-bit are different.
|
|
u32 fontDataSizeTemp = (u32)fontDataSize;
|
|
Do(p, fontDataSizeTemp);
|
|
fontDataSize = (size_t)fontDataSizeTemp;
|
|
if (p.mode == p.MODE_READ) {
|
|
delete [] fontData;
|
|
if (fontDataSize) {
|
|
fontData = new u8[fontDataSize];
|
|
DoArray(p, fontData, (int)fontDataSize);
|
|
}
|
|
} else if (fontDataSize) {
|
|
DoArray(p, fontData, (int)fontDataSize);
|
|
}
|
|
Do(p, fileName);
|
|
|
|
DoArray(p, dimensionTable, ARRAY_SIZE(dimensionTable));
|
|
DoArray(p, xAdjustTable, ARRAY_SIZE(xAdjustTable));
|
|
DoArray(p, yAdjustTable, ARRAY_SIZE(yAdjustTable));
|
|
DoArray(p, advanceTable, ARRAY_SIZE(advanceTable));
|
|
DoArray(p, charmapCompressionTable1, ARRAY_SIZE(charmapCompressionTable1));
|
|
DoArray(p, charmapCompressionTable2, ARRAY_SIZE(charmapCompressionTable2));
|
|
|
|
Do(p, charmap_compr);
|
|
Do(p, charmap);
|
|
if (s == 1) {
|
|
std::vector<GlyphFromPGF1State> oldGlyphs;
|
|
Do(p, oldGlyphs);
|
|
glyphs.resize(oldGlyphs.size());
|
|
for (size_t i = 0; i < oldGlyphs.size(); ++i) {
|
|
glyphs[i] = oldGlyphs[i];
|
|
}
|
|
Do(p, oldGlyphs);
|
|
shadowGlyphs.resize(oldGlyphs.size());
|
|
for (size_t i = 0; i < oldGlyphs.size(); ++i) {
|
|
shadowGlyphs[i] = oldGlyphs[i];
|
|
}
|
|
} else {
|
|
Do(p, glyphs);
|
|
Do(p, shadowGlyphs);
|
|
}
|
|
Do(p, firstGlyph);
|
|
}
|
|
|
|
bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
|
|
const u8 *const startPtr = ptr;
|
|
|
|
if (dataSize < sizeof(header)) {
|
|
return false;
|
|
}
|
|
|
|
DEBUG_LOG(Log::sceFont, "Reading %d bytes of PGF header", (int)sizeof(header));
|
|
memcpy(&header, ptr, sizeof(header));
|
|
ptr += sizeof(header);
|
|
|
|
fileName = header.fontName;
|
|
|
|
if (header.revision == 3) {
|
|
if (dataSize < sizeof(header) + sizeof(rev3extra)) {
|
|
return false;
|
|
}
|
|
memcpy(&rev3extra, ptr, sizeof(rev3extra));
|
|
rev3extra.compCharMapLength1 &= 0xFFFF;
|
|
rev3extra.compCharMapLength2 &= 0xFFFF;
|
|
ptr += sizeof(rev3extra);
|
|
}
|
|
|
|
// Validate that all tables fit in the input buffer before reading any of
|
|
// them. Use 64-bit arithmetic: the original 32-bit signed size math could
|
|
// overflow for crafted lengths.
|
|
const u64 headerSize = (u64)(ptr - startPtr);
|
|
const u64 tablesSize = ((u64)header.dimTableLength + header.xAdjustTableLength + header.yAdjustTableLength + header.advanceTableLength) * 8;
|
|
const u64 shadowCharMapSize = (((u64)header.shadowMapLength * header.shadowMapBpe + 31) & ~31ull) / 8;
|
|
const u64 compTableSize = header.revision == 3 ? ((u64)rev3extra.compCharMapLength1 + rev3extra.compCharMapLength2) * 4 : 0;
|
|
const u64 charMapSize = (((u64)header.charMapLength * header.charMapBpe + 31) & ~31ull) / 8;
|
|
const u64 charPointerSize = (((u64)header.charPointerLength * header.charPointerBpe + 31) & ~31ull) / 8;
|
|
|
|
// Also cap the lengths so a crafted font can't force absurd allocations
|
|
// or loops downstream. Real PGF fonts are tiny.
|
|
if (header.charPointerLength < 0 || header.charPointerLength > 0x100000 ||
|
|
header.charMapLength < 0 || header.charMapLength > 0x100000 ||
|
|
header.shadowMapLength < 0 || header.shadowMapLength > 0x10000) {
|
|
return false;
|
|
}
|
|
|
|
if (headerSize + tablesSize + shadowCharMapSize + compTableSize + charMapSize + charPointerSize > dataSize) {
|
|
return false;
|
|
}
|
|
|
|
const u32_le *wptr = (const u32_le *)ptr;
|
|
dimensionTable[0].resize(header.dimTableLength);
|
|
dimensionTable[1].resize(header.dimTableLength);
|
|
for (int i = 0; i < header.dimTableLength; i++) {
|
|
dimensionTable[0][i] = *wptr++;
|
|
dimensionTable[1][i] = *wptr++;
|
|
}
|
|
|
|
xAdjustTable[0].resize(header.xAdjustTableLength);
|
|
xAdjustTable[1].resize(header.xAdjustTableLength);
|
|
for (int i = 0; i < header.xAdjustTableLength; i++) {
|
|
xAdjustTable[0][i] = *wptr++;
|
|
xAdjustTable[1][i] = *wptr++;
|
|
}
|
|
|
|
yAdjustTable[0].resize(header.yAdjustTableLength);
|
|
yAdjustTable[1].resize(header.yAdjustTableLength);
|
|
for (int i = 0; i < header.yAdjustTableLength; i++) {
|
|
yAdjustTable[0][i] = *wptr++;
|
|
yAdjustTable[1][i] = *wptr++;
|
|
}
|
|
|
|
advanceTable[0].resize(header.advanceTableLength);
|
|
advanceTable[1].resize(header.advanceTableLength);
|
|
for (int i = 0; i < header.advanceTableLength; i++) {
|
|
advanceTable[0][i] = *wptr++;
|
|
advanceTable[1][i] = *wptr++;
|
|
}
|
|
|
|
const u8 *uptr = (const u8 *)wptr;
|
|
|
|
const u8 *shadowCharMap = uptr;
|
|
uptr += shadowCharMapSize;
|
|
|
|
if (uptr < startPtr || uptr >= startPtr + dataSize) {
|
|
return false;
|
|
}
|
|
|
|
const u16_le *sptr = (const u16_le *)uptr;
|
|
if (header.revision == 3) {
|
|
charmapCompressionTable1[0].resize(rev3extra.compCharMapLength1);
|
|
charmapCompressionTable1[1].resize(rev3extra.compCharMapLength1);
|
|
for (int i = 0; i < rev3extra.compCharMapLength1; i++) {
|
|
charmapCompressionTable1[0][i] = *sptr++;
|
|
charmapCompressionTable1[1][i] = *sptr++;
|
|
}
|
|
|
|
charmapCompressionTable2[0].resize(rev3extra.compCharMapLength2);
|
|
charmapCompressionTable2[1].resize(rev3extra.compCharMapLength2);
|
|
for (int i = 0; i < rev3extra.compCharMapLength2; i++) {
|
|
charmapCompressionTable2[0][i] = *sptr++;
|
|
charmapCompressionTable2[1][i] = *sptr++;
|
|
}
|
|
}
|
|
|
|
uptr = (const u8 *)sptr;
|
|
|
|
const u8 *charMap = uptr;
|
|
uptr += charMapSize;
|
|
|
|
const u8 *charPointerTable = uptr;
|
|
uptr += charPointerSize;
|
|
|
|
if (uptr < startPtr || uptr >= startPtr + dataSize) {
|
|
return false;
|
|
}
|
|
|
|
// PGF Fontdata.
|
|
u32 fontDataOffset = (u32)(uptr - startPtr);
|
|
|
|
fontDataSize = dataSize - fontDataOffset;
|
|
fontData = new u8[fontDataSize];
|
|
memcpy(fontData, uptr, fontDataSize);
|
|
|
|
// charmap.resize();
|
|
charmap.resize(header.charMapLength);
|
|
int charmap_compr_len = header.revision == 3 ? 7 : 1;
|
|
charmap_compr.resize(charmap_compr_len * 4);
|
|
glyphs.resize(header.charPointerLength);
|
|
shadowGlyphs.resize(header.charPointerLength);
|
|
firstGlyph = header.firstGlyph;
|
|
|
|
// Parse out the char map (array where each entry is an irregular number of bits)
|
|
// BPE = bits per entry, I think.
|
|
for (int i = 0; i < header.charMapLength; i++) {
|
|
charmap[i] = getBits(header.charMapBpe, charMap, i * header.charMapBpe);
|
|
// This check seems a little odd.
|
|
if ((size_t)charmap[i] >= glyphs.size())
|
|
charmap[i] = 65535;
|
|
}
|
|
|
|
std::vector<int> charPointers = getTable(charPointerTable, header.charPointerBpe, glyphs.size());
|
|
std::vector<int> shadowMap = getTable(shadowCharMap, header.shadowMapBpe, (s32)header.shadowMapLength);
|
|
|
|
// Pregenerate glyphs. charPointers come from the (attacker-controlled)
|
|
// char pointer table, so their offsets into fontData must be validated.
|
|
// ReadCharGlyph/ReadShadowGlyph bounds-check charPtr internally.
|
|
for (size_t i = 0; i < glyphs.size(); i++) {
|
|
if (charPointers[i] < 0)
|
|
continue;
|
|
size_t charPtr = (size_t)charPointers[i] * 4 * 8;
|
|
ReadCharGlyph(fontData, charPtr, glyphs[i]);
|
|
}
|
|
|
|
// And shadow glyphs.
|
|
for (size_t i = 0; i < glyphs.size(); i++) {
|
|
size_t shadowId = glyphs[i].shadowID;
|
|
if (shadowId < shadowMap.size()) {
|
|
size_t charId = shadowMap[shadowId];
|
|
if (charId < shadowGlyphs.size() && charPointers[charId] >= 0) {
|
|
size_t charPtr = (size_t)charPointers[charId] * 4 * 8;
|
|
// TODO: check for pre existing shadow glyph
|
|
ReadShadowGlyph(fontData, charPtr, shadowGlyphs[charId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int PGF::GetCharIndex(int charCode, const std::vector<int> &charmapCompressed) {
|
|
int charIndex = 0;
|
|
for (size_t i = 0; i < charmapCompressed.size(); i += 2) {
|
|
if (charCode >= charmapCompressed[i] && charCode < charmapCompressed[i] + charmapCompressed[i + 1]) {
|
|
charIndex += charCode - charmapCompressed[i];
|
|
return charIndex;
|
|
}
|
|
charIndex += charmapCompressed[i + 1];
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool PGF::GetCharInfo(int charCode, PGFCharInfo *charInfo, int altCharCode, int glyphType) const {
|
|
Glyph glyph;
|
|
memset(charInfo, 0, sizeof(*charInfo));
|
|
|
|
if (!GetCharGlyph(charCode, glyphType, glyph)) {
|
|
if (charCode < firstGlyph) {
|
|
// Character not in font, return zeroed charInfo as on real PSP.
|
|
return false;
|
|
}
|
|
if (!GetCharGlyph(altCharCode, glyphType, glyph)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
charInfo->bitmapWidth = glyph.w;
|
|
charInfo->bitmapHeight = glyph.h;
|
|
charInfo->bitmapLeft = glyph.left;
|
|
charInfo->bitmapTop = glyph.top;
|
|
charInfo->sfp26Width = glyph.dimensionWidth;
|
|
charInfo->sfp26Height = glyph.dimensionHeight;
|
|
charInfo->sfp26Ascender = glyph.yAdjustH;
|
|
// Font y goes upwards. If top is 10 and height is 11, the descender is approx. -1 (below 0.)
|
|
charInfo->sfp26Descender = charInfo->sfp26Ascender - (s32)charInfo->sfp26Height;
|
|
charInfo->sfp26BearingHX = glyph.xAdjustH;
|
|
charInfo->sfp26BearingHY = glyph.yAdjustH;
|
|
charInfo->sfp26BearingVX = glyph.xAdjustV;
|
|
charInfo->sfp26BearingVY = glyph.yAdjustV;
|
|
charInfo->sfp26AdvanceH = glyph.advanceH;
|
|
charInfo->sfp26AdvanceV = glyph.advanceV;
|
|
charInfo->shadowFlags = glyph.shadowFlags;
|
|
charInfo->shadowId = glyph.shadowID;
|
|
return true;
|
|
}
|
|
|
|
void PGF::GetFontInfo(PGFFontInfo *fi) const {
|
|
fi->maxGlyphWidthI = header.maxSize[0];
|
|
fi->maxGlyphHeightI = header.maxSize[1];
|
|
fi->maxGlyphAscenderI = header.maxAscender;
|
|
fi->maxGlyphDescenderI = header.maxDescender;
|
|
fi->maxGlyphLeftXI = header.maxLeftXAdjust;
|
|
fi->maxGlyphBaseYI = header.maxBaseYAdjust;
|
|
fi->minGlyphCenterXI = header.minCenterXAdjust;
|
|
fi->maxGlyphTopYI = header.maxTopYAdjust;
|
|
fi->maxGlyphAdvanceXI = header.maxAdvance[0];
|
|
fi->maxGlyphAdvanceYI = header.maxAdvance[1];
|
|
fi->maxGlyphWidthF = (float)header.maxSize[0] / 64.0f;
|
|
fi->maxGlyphHeightF = (float)header.maxSize[1] / 64.0f;
|
|
fi->maxGlyphAscenderF = (float)header.maxAscender / 64.0f;
|
|
fi->maxGlyphDescenderF = (float)header.maxDescender / 64.0f;
|
|
fi->maxGlyphLeftXF = (float)header.maxLeftXAdjust / 64.0f;
|
|
fi->maxGlyphBaseYF = (float)header.maxBaseYAdjust / 64.0f;
|
|
fi->minGlyphCenterXF = (float)header.minCenterXAdjust / 64.0f;
|
|
fi->maxGlyphTopYF = (float)header.maxTopYAdjust / 64.0f;
|
|
fi->maxGlyphAdvanceXF = (float)header.maxAdvance[0] / 64.0f;
|
|
fi->maxGlyphAdvanceYF = (float)header.maxAdvance[1] / 64.0f;
|
|
|
|
fi->maxGlyphWidth = header.maxGlyphWidth;
|
|
fi->maxGlyphHeight = header.maxGlyphHeight;
|
|
fi->numGlyphs = header.charPointerLength;
|
|
fi->shadowMapLength = 0; // header.shadowMapLength; TODO
|
|
|
|
fi->BPP = header.bpp;
|
|
}
|
|
|
|
bool PGF::ReadShadowGlyph(const u8 *fontdata, size_t charPtr, Glyph &glyph) {
|
|
// Most of the glyph info is from the char data.
|
|
if (!ReadCharGlyph(fontdata, charPtr, glyph))
|
|
return false;
|
|
|
|
// Skip over the char data.
|
|
if (charPtr + 96 > fontDataSize * 8)
|
|
return false;
|
|
charPtr += getBits(14, fontdata, charPtr) * 8;
|
|
if (charPtr + 96 > fontDataSize * 8)
|
|
return false;
|
|
|
|
// Skip size.
|
|
charPtr += 14;
|
|
|
|
glyph.w = consumeBits(7, fontdata, charPtr);
|
|
glyph.h = consumeBits(7, fontdata, charPtr);
|
|
|
|
glyph.left = consumeBits(7, fontdata, charPtr);
|
|
if (glyph.left >= 64) {
|
|
glyph.left -= 128;
|
|
}
|
|
|
|
glyph.top = consumeBits(7, fontdata, charPtr);
|
|
if (glyph.top >= 64) {
|
|
glyph.top -= 128;
|
|
}
|
|
|
|
glyph.ptr = (u32)(charPtr / 8);
|
|
return true;
|
|
}
|
|
|
|
bool PGF::ReadCharGlyph(const u8 *fontdata, size_t charPtr, Glyph &glyph) {
|
|
// The glyph header reads below stay within a few hundred bits, but
|
|
// validate the offset here so this function is safe regardless of caller.
|
|
// charPtr is a bit offset; fontDataSize is in bytes.
|
|
if (charPtr + 1024 > (size_t)fontDataSize * 8) {
|
|
return false;
|
|
}
|
|
|
|
// Skip size.
|
|
charPtr += 14;
|
|
|
|
glyph.w = consumeBits(7, fontdata, charPtr);
|
|
glyph.h = consumeBits(7, fontdata, charPtr);
|
|
|
|
glyph.left = consumeBits(7, fontdata, charPtr);
|
|
if (glyph.left >= 64) {
|
|
glyph.left -= 128;
|
|
}
|
|
|
|
glyph.top = consumeBits(7, fontdata, charPtr);
|
|
if (glyph.top >= 64) {
|
|
glyph.top -= 128;
|
|
}
|
|
|
|
glyph.flags = consumeBits(6, fontdata, charPtr);
|
|
|
|
glyph.shadowFlags = consumeBits(2, fontdata, charPtr) << (2 + 3);
|
|
glyph.shadowFlags |= consumeBits(2, fontdata, charPtr) << 3;
|
|
glyph.shadowFlags |= consumeBits(3, fontdata, charPtr);
|
|
|
|
glyph.shadowID = consumeBits(9, fontdata, charPtr);
|
|
|
|
if ((glyph.flags & FONT_PGF_METRIC_DIMENSION_INDEX) == FONT_PGF_METRIC_DIMENSION_INDEX)
|
|
{
|
|
int dimensionIndex = consumeBits(8, fontdata, charPtr);
|
|
|
|
if (dimensionIndex < header.dimTableLength) {
|
|
glyph.dimensionWidth = dimensionTable[0][dimensionIndex];
|
|
glyph.dimensionHeight = dimensionTable[1][dimensionIndex];
|
|
}
|
|
|
|
if (dimensionIndex == 0 && isJPCSPFont(fileName.c_str())) {
|
|
// Fonts created by ttf2pgf do not contain complete Glyph information.
|
|
// Provide default values.
|
|
glyph.dimensionWidth = glyph.w << 6;
|
|
glyph.dimensionHeight = glyph.h << 6;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyph.dimensionWidth = consumeBits(32, fontdata, charPtr);
|
|
glyph.dimensionHeight = consumeBits(32, fontdata, charPtr);
|
|
}
|
|
|
|
if ((glyph.flags & FONT_PGF_METRIC_BEARING_X_INDEX) == FONT_PGF_METRIC_BEARING_X_INDEX)
|
|
{
|
|
int xAdjustIndex = consumeBits(8, fontdata, charPtr);
|
|
|
|
if (xAdjustIndex < header.xAdjustTableLength) {
|
|
glyph.xAdjustH = xAdjustTable[0][xAdjustIndex];
|
|
glyph.xAdjustV = xAdjustTable[1][xAdjustIndex];
|
|
}
|
|
|
|
if (xAdjustIndex == 0 && isJPCSPFont(fileName.c_str()))
|
|
{
|
|
// Fonts created by ttf2pgf do not contain complete Glyph information.
|
|
// Provide default values.
|
|
glyph.xAdjustH = glyph.left << 6;
|
|
glyph.xAdjustV = glyph.left << 6;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyph.xAdjustH = consumeBits(32, fontdata, charPtr);
|
|
glyph.xAdjustV = consumeBits(32, fontdata, charPtr);
|
|
}
|
|
|
|
if ((glyph.flags & FONT_PGF_METRIC_BEARING_Y_INDEX) == FONT_PGF_METRIC_BEARING_Y_INDEX)
|
|
{
|
|
int yAdjustIndex = consumeBits(8, fontdata, charPtr);
|
|
|
|
if (yAdjustIndex < header.yAdjustTableLength) {
|
|
glyph.yAdjustH = yAdjustTable[0][yAdjustIndex];
|
|
glyph.yAdjustV = yAdjustTable[1][yAdjustIndex];
|
|
}
|
|
|
|
if (yAdjustIndex == 0 && isJPCSPFont(fileName.c_str()))
|
|
{
|
|
// Fonts created by ttf2pgf do not contain complete Glyph information.
|
|
// Provide default values.
|
|
glyph.yAdjustH = glyph.top << 6;
|
|
glyph.yAdjustV = glyph.top << 6;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyph.yAdjustH = consumeBits(32, fontdata, charPtr);
|
|
glyph.yAdjustV = consumeBits(32, fontdata, charPtr);
|
|
}
|
|
|
|
if ((glyph.flags & FONT_PGF_METRIC_ADVANCE_INDEX) == FONT_PGF_METRIC_ADVANCE_INDEX)
|
|
{
|
|
int advanceIndex = consumeBits(8, fontdata, charPtr);
|
|
|
|
if (advanceIndex < header.advanceTableLength) {
|
|
glyph.advanceH = advanceTable[0][advanceIndex];
|
|
glyph.advanceV = advanceTable[1][advanceIndex];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyph.advanceH = consumeBits(32, fontdata, charPtr);
|
|
glyph.advanceV = consumeBits(32, fontdata, charPtr);
|
|
}
|
|
|
|
glyph.ptr = (u32)(charPtr / 8);
|
|
return true;
|
|
}
|
|
|
|
bool PGF::GetCharGlyph(int charCode, int glyphType, Glyph &glyph) const {
|
|
if (charCode < firstGlyph)
|
|
return false;
|
|
charCode -= firstGlyph;
|
|
if (charCode < (int)charmap.size()) {
|
|
charCode = charmap[charCode];
|
|
}
|
|
if (glyphType == FONT_PGF_CHARGLYPH) {
|
|
if (charCode >= (int)glyphs.size())
|
|
return false;
|
|
glyph = glyphs[charCode];
|
|
} else {
|
|
if (charCode >= (int)shadowGlyphs.size())
|
|
return false;
|
|
glyph = shadowGlyphs[charCode];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PGF::DrawCharacter(const GlyphImage *image, int clipX, int clipY, int clipWidth, int clipHeight, int charCode, int altCharCode, int glyphType) const {
|
|
Glyph glyph;
|
|
if (!GetCharGlyph(charCode, glyphType, glyph)) {
|
|
if (charCode < firstGlyph) {
|
|
// Don't draw anything if the character is before the first available glyph.
|
|
return;
|
|
}
|
|
// No Glyph available for this charCode, try to use the alternate char.
|
|
charCode = altCharCode;
|
|
if (!GetCharGlyph(charCode, glyphType, glyph)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (glyph.w <= 0 || glyph.h <= 0) {
|
|
DEBUG_LOG(Log::sceFont, "Glyph with negative size, not rendering");
|
|
return;
|
|
}
|
|
|
|
if (((glyph.flags & FONT_PGF_BMP_OVERLAY) != FONT_PGF_BMP_H_ROWS) &&
|
|
((glyph.flags & FONT_PGF_BMP_OVERLAY) != FONT_PGF_BMP_V_ROWS)) {
|
|
ERROR_LOG_REPORT(Log::sceFont, "Nonsense glyph bitmap direction flag");
|
|
return;
|
|
}
|
|
|
|
const FontPixelFormat pixelFormat = (FontPixelFormat)(u32)image->pixelFormat;
|
|
|
|
// Hardware only ever renders into 4bpp and 8bpp buffers.
|
|
// `_4_REV`, `_24` and `_32` are accepted and the call returns 0, but not one byte of the buffer is touched.
|
|
//
|
|
// This was measured on a PSP-1000: 32 cases per format, every pixel left at its pre-fill value.
|
|
if (pixelFormat != PSP_FONT_PIXELFORMAT_4 && pixelFormat != PSP_FONT_PIXELFORMAT_8) {
|
|
WARN_LOG_REPORT_ONCE(pgfunsupportedfmt, Log::sceFont, "Font pixel format %d draws nothing on hardware", (int)pixelFormat);
|
|
return;
|
|
}
|
|
|
|
size_t bitPtr = glyph.ptr * 8;
|
|
int numberPixels = glyph.w * glyph.h;
|
|
int pixelIndex = 0;
|
|
|
|
int x = image->xPos64 >> 6;
|
|
int y = image->yPos64 >> 6;
|
|
int xFrac = image->xPos64 & 0x3F;
|
|
// Note: there is deliberately no `yFrac`.
|
|
// Hardware discards the fractional part of yPos64 entirely;
|
|
// it neither blends vertically nor grows the rectangle downwards.
|
|
//
|
|
// This was verified on a PSP-1000 by sweeping yPos64's fraction: the output is bit for bit identical for every value of it.
|
|
|
|
// Negative means don't clip on that side.
|
|
if (clipX < 0)
|
|
clipX = 0;
|
|
if (clipY < 0)
|
|
clipY = 0;
|
|
if (clipWidth < 0)
|
|
clipWidth = 8192;
|
|
if (clipHeight < 0)
|
|
clipHeight = 8192;
|
|
|
|
// Use a buffer so we can apply subpixel rendering.
|
|
// TODO: Cache this buffer per glyph? Maybe even transpose it first?
|
|
std::vector<u8> decodedPixels;
|
|
decodedPixels.resize(numberPixels);
|
|
|
|
while (pixelIndex < numberPixels && bitPtr + 8 < fontDataSize * 8) {
|
|
// This is some kind of nibble based RLE compression.
|
|
int nibble = consumeBits(4, fontData, bitPtr);
|
|
|
|
int count;
|
|
int value = 0;
|
|
if (nibble < 8) {
|
|
value = consumeBits(4, fontData, bitPtr);
|
|
count = nibble + 1;
|
|
} else {
|
|
count = 16 - nibble;
|
|
}
|
|
|
|
for (int i = 0; i < count && pixelIndex < numberPixels; i++) {
|
|
if (nibble >= 8) {
|
|
value = consumeBits(4, fontData, bitPtr);
|
|
}
|
|
|
|
// Kept as the raw 4 bit value.
|
|
// Hardware blends at the precision of the destination format, so the widening to 8 bit shouldn't be one here.
|
|
// See the scale computed in the render loop below.
|
|
decodedPixels[pixelIndex++] = value;
|
|
}
|
|
}
|
|
|
|
auto samplePixel = [&](int xx, int yy) -> u8 {
|
|
if (xx < 0 || yy < 0 || xx >= glyph.w || yy >= glyph.h) {
|
|
return 0;
|
|
}
|
|
|
|
int index;
|
|
if ((glyph.flags & FONT_PGF_BMP_OVERLAY) == FONT_PGF_BMP_H_ROWS) {
|
|
index = yy * glyph.w + xx;
|
|
} else {
|
|
index = xx * glyph.h + yy;
|
|
}
|
|
|
|
return decodedPixels[index];
|
|
};
|
|
|
|
// 4bpp blends the raw nibble, 8bpp blends the value swizzled to 8 bit (v | v << 4, i.e. v * 17).
|
|
// These are not the same thing as blending at 8 bit and narrowing afterwards.
|
|
const int scale = pixelFormat == PSP_FONT_PIXELFORMAT_4 ? 1 : 17;
|
|
|
|
int renderX1 = std::max(clipX, x) - x;
|
|
int renderY1 = std::max(clipY, y) - y;
|
|
// A non-zero horizontal fraction bleeds one column past the glyph, so the rectangle grows by 1px there.
|
|
// Vertically it never does.
|
|
int renderX2 = std::min(clipX + clipWidth - x, glyph.w + (xFrac > 0 ? 1 : 0));
|
|
int renderY2 = std::min(clipY + clipHeight - y, glyph.h);
|
|
|
|
if (gpu && renderX1 < renderX2 && renderY1 < renderY2) {
|
|
// The game may reuse this glyph buffer as a texture immediately after drawing it.
|
|
gpu->Flush();
|
|
}
|
|
|
|
for (int yy = renderY1; yy < renderY2; ++yy) {
|
|
for (int xx = renderX1; xx < renderX2; ++xx) {
|
|
const int a = samplePixel(xx - 1, yy) * scale;
|
|
const int b = samplePixel(xx + 0, yy) * scale;
|
|
|
|
// The two weights are rounded in opposite directions; down for the left neighbour, up for the pixel itself.
|
|
//
|
|
// This blending equation was inferred from test runs on a PSP-1000 with different prefill values when calling `sceFontGetCharGlyphImage_Clip`.
|
|
//
|
|
// The rounding choice makes the pair sum to exactly the max value of the pixel format (15 for 4bpp, 255 for 8bpp) whenever both samples are full ink, for every fraction,
|
|
// which may be the intention.
|
|
const int blended = (a * xFrac) / 64 + (b * (64 - xFrac) + 63) / 64;
|
|
|
|
// xFrac == 0 needs no special case; the first term vanishes and the second collapses to exactly b.
|
|
SetFontPixel(image->bufferPtr, image->bytesPerLine, image->bufWidth, image->bufHeight, x + xx, y + yy, blended, pixelFormat);
|
|
}
|
|
}
|
|
|
|
gpu->InvalidateCache(image->bufferPtr, image->bytesPerLine * image->bufHeight, GPU_INVALIDATE_SAFE);
|
|
}
|
|
|
|
// pixelColor arrives already scaled to `pixelformat`'s range, and is *added* to what is in the buffer with saturation; it does not replace it.
|
|
// A glyph drawn over existing content therefore never erases it; the transparent parts contribute zero.
|
|
//
|
|
// This was verified against a PSP-1000 for `PSP_FONT_PIXELFORMAT_4` and `_8` across 196608 pixels with no exceptions.
|
|
void PGF::SetFontPixel(u32 base, int bpl, int bufWidth, int bufHeight, int x, int y, int pixelColor, FontPixelFormat pixelformat) const {
|
|
if (x < 0 || x >= bufWidth || y < 0 || y >= bufHeight) {
|
|
return;
|
|
}
|
|
|
|
static const u8 fontPixelSizeInBytes[] = { 0, 0, 1, 3, 4 }; // 0 means 2 pixels per byte
|
|
if (pixelformat < 0 || pixelformat > PSP_FONT_PIXELFORMAT_32) {
|
|
ERROR_LOG_REPORT_ONCE(pfgbadformat, Log::sceFont, "Invalid image format in image: %d", (int)pixelformat);
|
|
return;
|
|
}
|
|
int pixelBytes = fontPixelSizeInBytes[pixelformat];
|
|
int bufMaxWidth = (pixelBytes == 0 ? bpl * 2 : bpl / pixelBytes);
|
|
if (x >= bufMaxWidth) {
|
|
return;
|
|
}
|
|
|
|
int framebufferAddr = base + (y * bpl) + (pixelBytes == 0 ? x / 2 : x * pixelBytes);
|
|
if (!Memory::IsValidAddress(framebufferAddr)) {
|
|
return;
|
|
}
|
|
|
|
switch (pixelformat) {
|
|
case PSP_FONT_PIXELFORMAT_4:
|
|
case PSP_FONT_PIXELFORMAT_4_REV:
|
|
{
|
|
// The two pixels share a byte, so the neighbour's nibble is left alone.
|
|
const int shift = ((x & 1) != pixelformat) ? 4 : 0;
|
|
const int oldColor = Memory::ReadUnchecked_U8(framebufferAddr);
|
|
const int newPix = std::min(((oldColor >> shift) & 0xF) + pixelColor, 15);
|
|
Memory::WriteUnchecked_U8((u8)((oldColor & ~(0xF << shift)) | (newPix << shift)), framebufferAddr);
|
|
break;
|
|
}
|
|
case PSP_FONT_PIXELFORMAT_8:
|
|
{
|
|
const int newPix = std::min((int)Memory::ReadUnchecked_U8(framebufferAddr) + pixelColor, 255);
|
|
Memory::WriteUnchecked_U8((u8)newPix, framebufferAddr);
|
|
break;
|
|
}
|
|
case PSP_FONT_PIXELFORMAT_24:
|
|
{
|
|
// Each channel gets the same value.
|
|
for (int i = 0; i < 3; ++i) {
|
|
const int newPix = std::min((int)Memory::ReadUnchecked_U8(framebufferAddr + i) + pixelColor, 255);
|
|
Memory::WriteUnchecked_U8((u8)newPix, framebufferAddr + i);
|
|
}
|
|
break;
|
|
}
|
|
case PSP_FONT_PIXELFORMAT_32:
|
|
{
|
|
const u32 oldColor = Memory::ReadUnchecked_U32(framebufferAddr);
|
|
u32 pix32 = 0;
|
|
for (int i = 0; i < 4; ++i) {
|
|
const int newPix = std::min((int)((oldColor >> (i * 8)) & 0xFF) + pixelColor, 255);
|
|
pix32 |= (u32)newPix << (i * 8);
|
|
}
|
|
Memory::WriteUnchecked_U32(pix32, framebufferAddr);
|
|
break;
|
|
}
|
|
}
|
|
}
|