CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Font/PGF.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617// ============== NOTE!!!!1819// Thanks to the JPCSP project! This sceFont implementation is basically a C++ take on JPCSP's font code.20// Some parts, especially in this file, were simply copied, so I guess this really makes this file GPL3.2122#include <algorithm>23#include "Common/Serialize/Serializer.h"24#include "Common/Serialize/SerializeFuncs.h"25#include "Core/MemMap.h"26#include "Core/Reporting.h"27#include "Core/Font/PGF.h"28#include "Core/HLE/HLE.h"2930#include "GPU/GPUInterface.h"31#include "GPU/GPUState.h"3233// These fonts, created by ttf2pgf, don't have complete glyph info and need to be identified.34static bool isJPCSPFont(const char *fontName) {35return !strcmp(fontName, "Liberation Sans") || !strcmp(fontName, "Liberation Serif") || !strcmp(fontName, "Sazanami") || !strcmp(fontName, "UnDotum") || !strcmp(fontName, "Microsoft YaHei");36}3738// Gets a number of bits from an offset.39static int getBits(int numBits, const u8 *buf, size_t pos) {40_dbg_assert_msg_(numBits <= 32, "Unable to return more than 32 bits, %d requested", numBits);4142const size_t wordpos = pos >> 5;43const u32_le *wordbuf = (const u32_le *)buf;44const u8 bitoff = pos & 31;4546// Might just be in one, has to be within two.47if (bitoff + numBits < 32) {48const u32 mask = (1 << numBits) - 1;49return (wordbuf[wordpos] >> bitoff) & mask;50} else {51int v = wordbuf[wordpos] >> bitoff;5253const u8 done = 32 - bitoff;54const u8 remaining = numBits - done;55if (remaining > 0) {56const u32 mask = (1 << remaining) - 1;57v |= (wordbuf[wordpos + 1] & mask) << done;58}59return v;60}61}6263static inline int consumeBits(int numBits, const u8 *buf, size_t &pos) {64int v = getBits(numBits, buf, pos);65pos += numBits;66return v;67}6869static std::vector<int> getTable(const u8 *buf, int bpe, size_t length) {70std::vector<int> vec;71vec.resize(length);72for (size_t i = 0; i < length; i++) {73vec[i] = getBits(bpe, buf, bpe * i);74}75return vec;76}7778PGF::PGF()79: fontData(0) {8081}8283PGF::~PGF() {84delete [] fontData;85}8687struct GlyphFromPGF1State {88int x;89int y;90int w;91int h;92int left;93int top;94int flags;95int shadowID;96int advanceH;97int advanceV;98int dimensionWidth, dimensionHeight;99int xAdjustH, xAdjustV;100int yAdjustH, yAdjustV;101u32 ptr;102103operator Glyph() {104Glyph ret;105ret.w = w;106ret.h = h;107ret.left = left;108ret.top = top;109ret.flags = flags;110// Wasn't read before.111ret.shadowFlags = 0;112ret.shadowID = shadowID;113ret.advanceH = advanceH;114ret.advanceV = advanceV;115ret.dimensionWidth = dimensionWidth;116ret.dimensionHeight = dimensionHeight;117ret.xAdjustH = xAdjustH;118ret.xAdjustV = xAdjustV;119ret.yAdjustH = yAdjustH;120ret.yAdjustV = yAdjustV;121ret.ptr = ptr;122return ret;123}124};125126void PGF::DoState(PointerWrap &p) {127auto s = p.Section("PGF", 1, 2);128if (!s)129return;130131Do(p, header);132Do(p, rev3extra);133134// Don't savestate size_t directly, 32-bit and 64-bit are different.135u32 fontDataSizeTemp = (u32)fontDataSize;136Do(p, fontDataSizeTemp);137fontDataSize = (size_t)fontDataSizeTemp;138if (p.mode == p.MODE_READ) {139delete [] fontData;140if (fontDataSize) {141fontData = new u8[fontDataSize];142DoArray(p, fontData, (int)fontDataSize);143}144} else if (fontDataSize) {145DoArray(p, fontData, (int)fontDataSize);146}147Do(p, fileName);148149DoArray(p, dimensionTable, ARRAY_SIZE(dimensionTable));150DoArray(p, xAdjustTable, ARRAY_SIZE(xAdjustTable));151DoArray(p, yAdjustTable, ARRAY_SIZE(yAdjustTable));152DoArray(p, advanceTable, ARRAY_SIZE(advanceTable));153DoArray(p, charmapCompressionTable1, ARRAY_SIZE(charmapCompressionTable1));154DoArray(p, charmapCompressionTable2, ARRAY_SIZE(charmapCompressionTable2));155156Do(p, charmap_compr);157Do(p, charmap);158if (s == 1) {159std::vector<GlyphFromPGF1State> oldGlyphs;160Do(p, oldGlyphs);161glyphs.resize(oldGlyphs.size());162for (size_t i = 0; i < oldGlyphs.size(); ++i) {163glyphs[i] = oldGlyphs[i];164}165Do(p, oldGlyphs);166shadowGlyphs.resize(oldGlyphs.size());167for (size_t i = 0; i < oldGlyphs.size(); ++i) {168shadowGlyphs[i] = oldGlyphs[i];169}170} else {171Do(p, glyphs);172Do(p, shadowGlyphs);173}174Do(p, firstGlyph);175}176177bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {178const u8 *const startPtr = ptr;179180if (dataSize < sizeof(header)) {181return false;182}183184DEBUG_LOG(Log::sceFont, "Reading %d bytes of PGF header", (int)sizeof(header));185memcpy(&header, ptr, sizeof(header));186ptr += sizeof(header);187188fileName = header.fontName;189190if (header.revision == 3) {191memcpy(&rev3extra, ptr, sizeof(rev3extra));192rev3extra.compCharMapLength1 &= 0xFFFF;193rev3extra.compCharMapLength2 &= 0xFFFF;194ptr += sizeof(rev3extra);195}196197const u32_le *wptr = (const u32_le *)ptr;198dimensionTable[0].resize(header.dimTableLength);199dimensionTable[1].resize(header.dimTableLength);200for (int i = 0; i < header.dimTableLength; i++) {201dimensionTable[0][i] = *wptr++;202dimensionTable[1][i] = *wptr++;203}204205xAdjustTable[0].resize(header.xAdjustTableLength);206xAdjustTable[1].resize(header.xAdjustTableLength);207for (int i = 0; i < header.xAdjustTableLength; i++) {208xAdjustTable[0][i] = *wptr++;209xAdjustTable[1][i] = *wptr++;210}211212yAdjustTable[0].resize(header.yAdjustTableLength);213yAdjustTable[1].resize(header.yAdjustTableLength);214for (int i = 0; i < header.yAdjustTableLength; i++) {215yAdjustTable[0][i] = *wptr++;216yAdjustTable[1][i] = *wptr++;217}218219advanceTable[0].resize(header.advanceTableLength);220advanceTable[1].resize(header.advanceTableLength);221for (int i = 0; i < header.advanceTableLength; i++) {222advanceTable[0][i] = *wptr++;223advanceTable[1][i] = *wptr++;224}225226const u8 *uptr = (const u8 *)wptr;227228int shadowCharMapSize = ((header.shadowMapLength * header.shadowMapBpe + 31) & ~31) / 8;229const u8 *shadowCharMap = uptr;230uptr += shadowCharMapSize;231232if (uptr < startPtr || uptr >= startPtr + dataSize) {233return false;234}235236const u16_le *sptr = (const u16_le *)uptr;237if (header.revision == 3) {238charmapCompressionTable1[0].resize(rev3extra.compCharMapLength1);239charmapCompressionTable1[1].resize(rev3extra.compCharMapLength1);240for (int i = 0; i < rev3extra.compCharMapLength1; i++) {241charmapCompressionTable1[0][i] = *sptr++;242charmapCompressionTable1[1][i] = *sptr++;243}244245charmapCompressionTable2[0].resize(rev3extra.compCharMapLength2);246charmapCompressionTable2[1].resize(rev3extra.compCharMapLength2);247for (int i = 0; i < rev3extra.compCharMapLength2; i++) {248charmapCompressionTable2[0][i] = *sptr++;249charmapCompressionTable2[1][i] = *sptr++;250}251}252253uptr = (const u8 *)sptr;254255int charMapSize = ((header.charMapLength * header.charMapBpe + 31) & ~31) / 8;256const u8 *charMap = uptr;257uptr += charMapSize;258259int charPointerSize = (((header.charPointerLength * header.charPointerBpe + 31) & ~31) / 8);260const u8 *charPointerTable = uptr;261uptr += charPointerSize;262263if (uptr < startPtr || uptr >= startPtr + dataSize) {264return false;265}266267// PGF Fontdata.268u32 fontDataOffset = (u32)(uptr - startPtr);269270fontDataSize = dataSize - fontDataOffset;271fontData = new u8[fontDataSize];272memcpy(fontData, uptr, fontDataSize);273274// charmap.resize();275charmap.resize(header.charMapLength);276int charmap_compr_len = header.revision == 3 ? 7 : 1;277charmap_compr.resize(charmap_compr_len * 4);278glyphs.resize(header.charPointerLength);279shadowGlyphs.resize(header.charPointerLength);280firstGlyph = header.firstGlyph;281282// Parse out the char map (array where each entry is an irregular number of bits)283// BPE = bits per entry, I think.284for (int i = 0; i < header.charMapLength; i++) {285charmap[i] = getBits(header.charMapBpe, charMap, i * header.charMapBpe);286// This check seems a little odd.287if ((size_t)charmap[i] >= glyphs.size())288charmap[i] = 65535;289}290291std::vector<int> charPointers = getTable(charPointerTable, header.charPointerBpe, glyphs.size());292std::vector<int> shadowMap = getTable(shadowCharMap, header.shadowMapBpe, (s32)header.shadowMapLength);293294// Pregenerate glyphs.295for (size_t i = 0; i < glyphs.size(); i++) {296ReadCharGlyph(fontData, charPointers[i] * 4 * 8 /* ??? */, glyphs[i]);297}298299// And shadow glyphs.300for (size_t i = 0; i < glyphs.size(); i++) {301size_t shadowId = glyphs[i].shadowID;302if (shadowId < shadowMap.size()) {303size_t charId = shadowMap[shadowId];304if (charId < shadowGlyphs.size()) {305// TODO: check for pre existing shadow glyph306ReadShadowGlyph(fontData, charPointers[charId] * 4 * 8 /* ??? */, shadowGlyphs[charId]);307}308}309}310311return true;312}313314int PGF::GetCharIndex(int charCode, const std::vector<int> &charmapCompressed) {315int charIndex = 0;316for (size_t i = 0; i < charmapCompressed.size(); i += 2) {317if (charCode >= charmapCompressed[i] && charCode < charmapCompressed[i] + charmapCompressed[i + 1]) {318charIndex += charCode - charmapCompressed[i];319return charIndex;320}321charIndex += charmapCompressed[i + 1];322}323return -1;324}325326bool PGF::GetCharInfo(int charCode, PGFCharInfo *charInfo, int altCharCode, int glyphType) const {327Glyph glyph;328memset(charInfo, 0, sizeof(*charInfo));329330if (!GetCharGlyph(charCode, glyphType, glyph)) {331if (charCode < firstGlyph) {332// Character not in font, return zeroed charInfo as on real PSP.333return false;334}335if (!GetCharGlyph(altCharCode, glyphType, glyph)) {336return false;337}338}339340charInfo->bitmapWidth = glyph.w;341charInfo->bitmapHeight = glyph.h;342charInfo->bitmapLeft = glyph.left;343charInfo->bitmapTop = glyph.top;344charInfo->sfp26Width = glyph.dimensionWidth;345charInfo->sfp26Height = glyph.dimensionHeight;346charInfo->sfp26Ascender = glyph.yAdjustH;347// Font y goes upwards. If top is 10 and height is 11, the descender is approx. -1 (below 0.)348charInfo->sfp26Descender = charInfo->sfp26Ascender - (s32)charInfo->sfp26Height;349charInfo->sfp26BearingHX = glyph.xAdjustH;350charInfo->sfp26BearingHY = glyph.yAdjustH;351charInfo->sfp26BearingVX = glyph.xAdjustV;352charInfo->sfp26BearingVY = glyph.yAdjustV;353charInfo->sfp26AdvanceH = glyph.advanceH;354charInfo->sfp26AdvanceV = glyph.advanceV;355charInfo->shadowFlags = glyph.shadowFlags;356charInfo->shadowId = glyph.shadowID;357return true;358}359360void PGF::GetFontInfo(PGFFontInfo *fi) const {361fi->maxGlyphWidthI = header.maxSize[0];362fi->maxGlyphHeightI = header.maxSize[1];363fi->maxGlyphAscenderI = header.maxAscender;364fi->maxGlyphDescenderI = header.maxDescender;365fi->maxGlyphLeftXI = header.maxLeftXAdjust;366fi->maxGlyphBaseYI = header.maxBaseYAdjust;367fi->minGlyphCenterXI = header.minCenterXAdjust;368fi->maxGlyphTopYI = header.maxTopYAdjust;369fi->maxGlyphAdvanceXI = header.maxAdvance[0];370fi->maxGlyphAdvanceYI = header.maxAdvance[1];371fi->maxGlyphWidthF = (float)header.maxSize[0] / 64.0f;372fi->maxGlyphHeightF = (float)header.maxSize[1] / 64.0f;373fi->maxGlyphAscenderF = (float)header.maxAscender / 64.0f;374fi->maxGlyphDescenderF = (float)header.maxDescender / 64.0f;375fi->maxGlyphLeftXF = (float)header.maxLeftXAdjust / 64.0f;376fi->maxGlyphBaseYF = (float)header.maxBaseYAdjust / 64.0f;377fi->minGlyphCenterXF = (float)header.minCenterXAdjust / 64.0f;378fi->maxGlyphTopYF = (float)header.maxTopYAdjust / 64.0f;379fi->maxGlyphAdvanceXF = (float)header.maxAdvance[0] / 64.0f;380fi->maxGlyphAdvanceYF = (float)header.maxAdvance[1] / 64.0f;381382fi->maxGlyphWidth = header.maxGlyphWidth;383fi->maxGlyphHeight = header.maxGlyphHeight;384fi->numGlyphs = header.charPointerLength;385fi->shadowMapLength = 0; // header.shadowMapLength; TODO386387fi->BPP = header.bpp;388}389390bool PGF::ReadShadowGlyph(const u8 *fontdata, size_t charPtr, Glyph &glyph) {391// Most of the glyph info is from the char data.392if (!ReadCharGlyph(fontdata, charPtr, glyph))393return false;394395// Skip over the char data.396if (charPtr + 96 > fontDataSize * 8)397return false;398charPtr += getBits(14, fontdata, charPtr) * 8;399if (charPtr + 96 > fontDataSize * 8)400return false;401402// Skip size.403charPtr += 14;404405glyph.w = consumeBits(7, fontdata, charPtr);406glyph.h = consumeBits(7, fontdata, charPtr);407408glyph.left = consumeBits(7, fontdata, charPtr);409if (glyph.left >= 64) {410glyph.left -= 128;411}412413glyph.top = consumeBits(7, fontdata, charPtr);414if (glyph.top >= 64) {415glyph.top -= 128;416}417418glyph.ptr = (u32)(charPtr / 8);419return true;420}421422bool PGF::ReadCharGlyph(const u8 *fontdata, size_t charPtr, Glyph &glyph) {423// Skip size.424charPtr += 14;425426glyph.w = consumeBits(7, fontdata, charPtr);427glyph.h = consumeBits(7, fontdata, charPtr);428429glyph.left = consumeBits(7, fontdata, charPtr);430if (glyph.left >= 64) {431glyph.left -= 128;432}433434glyph.top = consumeBits(7, fontdata, charPtr);435if (glyph.top >= 64) {436glyph.top -= 128;437}438439glyph.flags = consumeBits(6, fontdata, charPtr);440441glyph.shadowFlags = consumeBits(2, fontdata, charPtr) << (2 + 3);442glyph.shadowFlags |= consumeBits(2, fontdata, charPtr) << 3;443glyph.shadowFlags |= consumeBits(3, fontdata, charPtr);444445glyph.shadowID = consumeBits(9, fontdata, charPtr);446447if ((glyph.flags & FONT_PGF_METRIC_DIMENSION_INDEX) == FONT_PGF_METRIC_DIMENSION_INDEX)448{449int dimensionIndex = consumeBits(8, fontdata, charPtr);450451if (dimensionIndex < header.dimTableLength) {452glyph.dimensionWidth = dimensionTable[0][dimensionIndex];453glyph.dimensionHeight = dimensionTable[1][dimensionIndex];454}455456if (dimensionIndex == 0 && isJPCSPFont(fileName.c_str())) {457// Fonts created by ttf2pgf do not contain complete Glyph information.458// Provide default values.459glyph.dimensionWidth = glyph.w << 6;460glyph.dimensionHeight = glyph.h << 6;461}462}463else464{465glyph.dimensionWidth = consumeBits(32, fontdata, charPtr);466glyph.dimensionHeight = consumeBits(32, fontdata, charPtr);467}468469if ((glyph.flags & FONT_PGF_METRIC_BEARING_X_INDEX) == FONT_PGF_METRIC_BEARING_X_INDEX)470{471int xAdjustIndex = consumeBits(8, fontdata, charPtr);472473if (xAdjustIndex < header.xAdjustTableLength) {474glyph.xAdjustH = xAdjustTable[0][xAdjustIndex];475glyph.xAdjustV = xAdjustTable[1][xAdjustIndex];476}477478if (xAdjustIndex == 0 && isJPCSPFont(fileName.c_str()))479{480// Fonts created by ttf2pgf do not contain complete Glyph information.481// Provide default values.482glyph.xAdjustH = glyph.left << 6;483glyph.xAdjustV = glyph.left << 6;484}485}486else487{488glyph.xAdjustH = consumeBits(32, fontdata, charPtr);489glyph.xAdjustV = consumeBits(32, fontdata, charPtr);490}491492if ((glyph.flags & FONT_PGF_METRIC_BEARING_Y_INDEX) == FONT_PGF_METRIC_BEARING_Y_INDEX)493{494int yAdjustIndex = consumeBits(8, fontdata, charPtr);495496if (yAdjustIndex < header.yAdjustTableLength) {497glyph.yAdjustH = yAdjustTable[0][yAdjustIndex];498glyph.yAdjustV = yAdjustTable[1][yAdjustIndex];499}500501if (yAdjustIndex == 0 && isJPCSPFont(fileName.c_str()))502{503// Fonts created by ttf2pgf do not contain complete Glyph information.504// Provide default values.505glyph.yAdjustH = glyph.top << 6;506glyph.yAdjustV = glyph.top << 6;507}508}509else510{511glyph.yAdjustH = consumeBits(32, fontdata, charPtr);512glyph.yAdjustV = consumeBits(32, fontdata, charPtr);513}514515if ((glyph.flags & FONT_PGF_METRIC_ADVANCE_INDEX) == FONT_PGF_METRIC_ADVANCE_INDEX)516{517int advanceIndex = consumeBits(8, fontdata, charPtr);518519if (advanceIndex < header.advanceTableLength) {520glyph.advanceH = advanceTable[0][advanceIndex];521glyph.advanceV = advanceTable[1][advanceIndex];522}523}524else525{526glyph.advanceH = consumeBits(32, fontdata, charPtr);527glyph.advanceV = consumeBits(32, fontdata, charPtr);528}529530glyph.ptr = (u32)(charPtr / 8);531return true;532}533534bool PGF::GetCharGlyph(int charCode, int glyphType, Glyph &glyph) const {535if (charCode < firstGlyph)536return false;537charCode -= firstGlyph;538if (charCode < (int)charmap.size()) {539charCode = charmap[charCode];540}541if (glyphType == FONT_PGF_CHARGLYPH) {542if (charCode >= (int)glyphs.size())543return false;544glyph = glyphs[charCode];545} else {546if (charCode >= (int)shadowGlyphs.size())547return false;548glyph = shadowGlyphs[charCode];549}550return true;551}552553void PGF::DrawCharacter(const GlyphImage *image, int clipX, int clipY, int clipWidth, int clipHeight, int charCode, int altCharCode, int glyphType) const {554Glyph glyph;555if (!GetCharGlyph(charCode, glyphType, glyph)) {556if (charCode < firstGlyph) {557// Don't draw anything if the character is before the first available glyph.558return;559}560// No Glyph available for this charCode, try to use the alternate char.561charCode = altCharCode;562if (!GetCharGlyph(charCode, glyphType, glyph)) {563return;564}565}566567if (glyph.w <= 0 || glyph.h <= 0) {568DEBUG_LOG(Log::sceFont, "Glyph with negative size, not rendering");569return;570}571572if (((glyph.flags & FONT_PGF_BMP_OVERLAY) != FONT_PGF_BMP_H_ROWS) &&573((glyph.flags & FONT_PGF_BMP_OVERLAY) != FONT_PGF_BMP_V_ROWS)) {574ERROR_LOG_REPORT(Log::sceFont, "Nonsense glyph bitmap direction flag");575return;576}577578size_t bitPtr = glyph.ptr * 8;579int numberPixels = glyph.w * glyph.h;580int pixelIndex = 0;581582int x = image->xPos64 >> 6;583int y = image->yPos64 >> 6;584u8 xFrac = image->xPos64 & 0x3F;585u8 yFrac = image->yPos64 & 0x3F;586587// Negative means don't clip on that side.588if (clipX < 0)589clipX = 0;590if (clipY < 0)591clipY = 0;592if (clipWidth < 0)593clipWidth = 8192;594if (clipHeight < 0)595clipHeight = 8192;596597// Use a buffer so we can apply subpixel rendering.598// TODO: Cache this buffer per glyph? Maybe even transpose it first?599std::vector<u8> decodedPixels;600decodedPixels.resize(numberPixels);601602while (pixelIndex < numberPixels && bitPtr + 8 < fontDataSize * 8) {603// This is some kind of nibble based RLE compression.604int nibble = consumeBits(4, fontData, bitPtr);605606int count;607int value = 0;608if (nibble < 8) {609value = consumeBits(4, fontData, bitPtr);610count = nibble + 1;611} else {612count = 16 - nibble;613}614615for (int i = 0; i < count && pixelIndex < numberPixels; i++) {616if (nibble >= 8) {617value = consumeBits(4, fontData, bitPtr);618}619620decodedPixels[pixelIndex++] = value | (value << 4);621}622}623624auto samplePixel = [&](int xx, int yy) -> u8 {625if (xx < 0 || yy < 0 || xx >= glyph.w || yy >= glyph.h) {626return 0;627}628629int index;630if ((glyph.flags & FONT_PGF_BMP_OVERLAY) == FONT_PGF_BMP_H_ROWS) {631index = yy * glyph.w + xx;632} else {633index = xx * glyph.h + yy;634}635636return decodedPixels[index];637};638639int renderX1 = std::max(clipX, x) - x;640int renderY1 = std::max(clipY, y) - y;641// We can render up to frac beyond the glyph w/h, so add 1px if necessary.642int renderX2 = std::min(clipX + clipWidth - x, glyph.w + (xFrac > 0 ? 1 : 0));643int renderY2 = std::min(clipY + clipHeight - y, glyph.h + (yFrac > 0 ? 1 : 0));644645if (xFrac == 0 && yFrac == 0) {646for (int yy = renderY1; yy < renderY2; ++yy) {647for (int xx = renderX1; xx < renderX2; ++xx) {648u8 pixelColor = samplePixel(xx, yy);649SetFontPixel(image->bufferPtr, image->bytesPerLine, image->bufWidth, image->bufHeight, x + xx, y + yy, pixelColor, (FontPixelFormat)(u32)image->pixelFormat);650}651}652} else {653for (int yy = renderY1; yy < renderY2; ++yy) {654for (int xx = renderX1; xx < renderX2; ++xx) {655// First, blend horizontally. Tests show we blend swizzled to 8 bit.656u32 horiz1 = samplePixel(xx - 1, yy - 1) * xFrac + samplePixel(xx, yy - 1) * (64 - xFrac);657u32 horiz2 = samplePixel(xx - 1, yy + 0) * xFrac + samplePixel(xx, yy + 0) * (64 - xFrac);658// Now blend those together vertically.659u32 blended = horiz1 * yFrac + horiz2 * (64 - yFrac);660661// We multiplied an 8 bit value by 64 twice, so now we have a 20 bit value.662u8 pixelColor = blended >> 12;663SetFontPixel(image->bufferPtr, image->bytesPerLine, image->bufWidth, image->bufHeight, x + xx, y + yy, pixelColor, (FontPixelFormat)(u32)image->pixelFormat);664}665}666}667668gpu->InvalidateCache(image->bufferPtr, image->bytesPerLine * image->bufHeight, GPU_INVALIDATE_SAFE);669}670671void PGF::SetFontPixel(u32 base, int bpl, int bufWidth, int bufHeight, int x, int y, u8 pixelColor, FontPixelFormat pixelformat) const {672if (x < 0 || x >= bufWidth || y < 0 || y >= bufHeight) {673return;674}675676static const u8 fontPixelSizeInBytes[] = { 0, 0, 1, 3, 4 }; // 0 means 2 pixels per byte677if (pixelformat < 0 || pixelformat > PSP_FONT_PIXELFORMAT_32) {678ERROR_LOG_REPORT_ONCE(pfgbadformat, Log::sceFont, "Invalid image format in image: %d", (int)pixelformat);679return;680}681int pixelBytes = fontPixelSizeInBytes[pixelformat];682int bufMaxWidth = (pixelBytes == 0 ? bpl * 2 : bpl / pixelBytes);683if (x >= bufMaxWidth) {684return;685}686687int framebufferAddr = base + (y * bpl) + (pixelBytes == 0 ? x / 2 : x * pixelBytes);688689switch (pixelformat) {690case PSP_FONT_PIXELFORMAT_4:691case PSP_FONT_PIXELFORMAT_4_REV:692{693// We always get a 8-bit value, so take only the top 4 bits.694const u8 pix4 = pixelColor >> 4;695696int oldColor = Memory::Read_U8(framebufferAddr);697int newColor;698if ((x & 1) != pixelformat) {699newColor = (pix4 << 4) | (oldColor & 0xF);700} else {701newColor = (oldColor & 0xF0) | pix4;702}703Memory::Write_U8(newColor, framebufferAddr);704break;705}706case PSP_FONT_PIXELFORMAT_8:707{708Memory::Write_U8(pixelColor, framebufferAddr);709break;710}711case PSP_FONT_PIXELFORMAT_24:712{713// Each channel has the same value.714Memory::Write_U8(pixelColor, framebufferAddr + 0);715Memory::Write_U8(pixelColor, framebufferAddr + 1);716Memory::Write_U8(pixelColor, framebufferAddr + 2);717break;718}719case PSP_FONT_PIXELFORMAT_32:720{721// Spread the 8 bits out into one write of 32 bits.722u32 pix32 = pixelColor;723pix32 |= pix32 << 8;724pix32 |= pix32 << 16;725Memory::Write_U32(pix32, framebufferAddr);726break;727}728}729}730731732