mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
257 lines
7.5 KiB
C++
257 lines
7.5 KiB
C++
#include "ppsspp_config.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/Data/Hash/Hash.h"
|
|
#include "Common/Data/Text/WrapText.h"
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Render/Text/draw_text.h"
|
|
#include "Common/Render/Text/draw_text_win.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/StringUtils.h"
|
|
|
|
#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP)
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
|
|
enum {
|
|
MAX_TEXT_WIDTH = 4096,
|
|
MAX_TEXT_HEIGHT = 512
|
|
};
|
|
|
|
class TextDrawerFontContext {
|
|
public:
|
|
~TextDrawerFontContext() {
|
|
Destroy();
|
|
}
|
|
|
|
void Create() {
|
|
if (hFont) {
|
|
Destroy();
|
|
}
|
|
// We apparently specify all font sizes in pts (1pt = 1.33px), so divide by only 72 for pixels.
|
|
int nHeight = -MulDiv(height, (int)(96.0f * (1.0f / dpiScale)), 72);
|
|
hFont = CreateFont(nHeight, 0, 0, 0, bold, 0,
|
|
FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, PROOF_QUALITY,
|
|
VARIABLE_PITCH, fname.c_str());
|
|
}
|
|
void Destroy() {
|
|
DeleteObject(hFont);
|
|
hFont = 0;
|
|
}
|
|
|
|
HFONT hFont;
|
|
std::wstring fname;
|
|
int height;
|
|
int bold;
|
|
float dpiScale;
|
|
};
|
|
|
|
struct TextDrawerContext {
|
|
HDC hDC;
|
|
HBITMAP hbmBitmap;
|
|
int *pBitmapBits;
|
|
};
|
|
|
|
TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) {
|
|
ctx_ = new TextDrawerContext();
|
|
ctx_->hDC = CreateCompatibleDC(NULL);
|
|
|
|
BITMAPINFO bmi;
|
|
ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = MAX_TEXT_WIDTH;
|
|
bmi.bmiHeader.biHeight = -MAX_TEXT_HEIGHT;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
|
|
ctx_->hbmBitmap = CreateDIBSection(ctx_->hDC, &bmi, DIB_RGB_COLORS, (VOID**)&ctx_->pBitmapBits, NULL, 0);
|
|
_assert_(ctx_->hbmBitmap != nullptr);
|
|
SetMapMode(ctx_->hDC, MM_TEXT);
|
|
|
|
SelectObject(ctx_->hDC, ctx_->hbmBitmap);
|
|
}
|
|
|
|
TextDrawerWin32::~TextDrawerWin32() {
|
|
ClearCache();
|
|
ClearFonts();
|
|
|
|
DeleteObject(ctx_->hbmBitmap);
|
|
DeleteDC(ctx_->hDC);
|
|
delete ctx_;
|
|
}
|
|
|
|
uint32_t TextDrawerWin32::SetFont(const char *fontName, int size, int flags) {
|
|
uint32_t fontHash = fontName ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 0;
|
|
fontHash ^= size;
|
|
fontHash ^= flags << 10;
|
|
|
|
auto iter = fontMap_.find(fontHash);
|
|
if (iter != fontMap_.end()) {
|
|
fontHash_ = fontHash;
|
|
return fontHash;
|
|
}
|
|
|
|
std::wstring fname;
|
|
if (fontName)
|
|
fname = ConvertUTF8ToWString(fontName);
|
|
else
|
|
fname = L"Tahoma";
|
|
|
|
TextDrawerFontContext *font = new TextDrawerFontContext();
|
|
font->bold = FW_LIGHT;
|
|
font->height = size;
|
|
font->fname = fname;
|
|
font->dpiScale = dpiScale_;
|
|
font->Create();
|
|
|
|
fontMap_[fontHash] = std::unique_ptr<TextDrawerFontContext>(font);
|
|
fontHash_ = fontHash;
|
|
return fontHash;
|
|
}
|
|
|
|
void TextDrawerWin32::SetFont(uint32_t fontHandle) {
|
|
auto iter = fontMap_.find(fontHandle);
|
|
if (iter != fontMap_.end()) {
|
|
fontHash_ = fontHandle;
|
|
}
|
|
}
|
|
|
|
void TextDrawerWin32::MeasureStringInternal(std::string_view str, float *w, float *h) {
|
|
auto iter = fontMap_.find(fontHash_);
|
|
if (iter != fontMap_.end()) {
|
|
SelectObject(ctx_->hDC, iter->second->hFont);
|
|
}
|
|
|
|
#if 0 && defined(_DEBUG)
|
|
if (str.find('\r') != std::string_view::npos) {
|
|
_dbg_assert_msg_(false, "carriage return found in string to measure");
|
|
}
|
|
#endif
|
|
|
|
std::string toMeasure(str);
|
|
|
|
std::vector<std::string_view> lines;
|
|
SplitString(toMeasure, '\n', lines);
|
|
|
|
int extW = 0, extH = 0;
|
|
for (auto &line : lines) {
|
|
SIZE size;
|
|
std::wstring wstr = ConvertUTF8ToWString(line);
|
|
if (wstr.empty() && lines.size() > 1) {
|
|
// Measure empty lines as if it was a space.
|
|
wstr = L" ";
|
|
}
|
|
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
|
|
if (size.cx > extW)
|
|
extW = size.cx;
|
|
extH += size.cy;
|
|
}
|
|
|
|
*w = extW;
|
|
*h = extH;
|
|
}
|
|
|
|
bool TextDrawerWin32::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {
|
|
if (str.empty()) {
|
|
bitmapData.clear();
|
|
return false;
|
|
}
|
|
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
|
|
|
|
auto iter = fontMap_.find(fontHash_);
|
|
if (iter != fontMap_.end()) {
|
|
SelectObject(ctx_->hDC, iter->second->hFont);
|
|
}
|
|
// Set text properties
|
|
SetTextColor(ctx_->hDC, 0xFFFFFF);
|
|
SetBkColor(ctx_->hDC, 0);
|
|
SetTextAlign(ctx_->hDC, TA_TOP);
|
|
|
|
// This matters for multi-line text - DT_CENTER is horizontal only.
|
|
UINT dtAlign = (align & ALIGN_HCENTER) == 0 ? DT_LEFT : DT_CENTER;
|
|
|
|
RECT textRect = { 0 };
|
|
DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &textRect, DT_NOPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0);
|
|
SIZE size;
|
|
size.cx = textRect.right;
|
|
size.cy = textRect.bottom;
|
|
|
|
if (size.cx > MAX_TEXT_WIDTH)
|
|
size.cx = MAX_TEXT_WIDTH;
|
|
if (size.cy > MAX_TEXT_HEIGHT)
|
|
size.cy = MAX_TEXT_HEIGHT;
|
|
|
|
if (size.cx == 0 || size.cy == 0) {
|
|
// Don't draw zero-sized textures.
|
|
WARN_LOG(Log::G3D, "Text '%.*s' caused a zero size image", (int)str.length(), str.data());
|
|
return false;
|
|
}
|
|
|
|
entry.texture = nullptr;
|
|
entry.width = size.cx;
|
|
entry.height = size.cy;
|
|
entry.bmWidth = (size.cx + 3) & ~3;
|
|
entry.bmHeight = (size.cy + 3) & ~3;
|
|
entry.lastUsedFrame = frameCount_;
|
|
|
|
RECT rc = { 0 };
|
|
rc.right = entry.bmWidth;
|
|
rc.bottom = entry.bmHeight;
|
|
FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
|
|
DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_NOPREFIX | DT_TOP | dtAlign, 0);
|
|
|
|
// Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format
|
|
// because we need white. Well, we could using swizzle, but not all our backends support that.
|
|
if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) {
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t));
|
|
uint32_t *bitmapData32 = (uint32_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff);
|
|
bitmapData32[entry.bmWidth * y + x] = (bAlpha << 24) | 0x00ffffff;
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
|
|
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4);
|
|
bitmapData16[entry.bmWidth * y + x] = (bAlpha) | 0xfff0;
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) {
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
|
|
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)((ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff) >> 4);
|
|
bitmapData16[entry.bmWidth * y + x] = (bAlpha << 12) | 0x0fff;
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff;
|
|
bitmapData[entry.bmWidth * y + x] = bAlpha;
|
|
}
|
|
}
|
|
} else {
|
|
_assert_msg_(false, "Bad TextDrawer format");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TextDrawerWin32::ClearFonts() {
|
|
for (auto &iter : fontMap_) {
|
|
iter.second->Destroy();
|
|
}
|
|
fontMap_.clear();
|
|
}
|
|
|
|
#endif
|