mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 17:49:29 +01:00
Merge pull request #12702 from unknownbrackets/ppge-text
PPGe: Use TextDrawer for save UI if available
This commit is contained in:
15 files changed
+714
-341
No files matched your search
@@ -49,6 +49,7 @@
|
||||
#include "Core/HLE/sceKernel.h"
|
||||
#include "Core/HLE/sceKernelThread.h"
|
||||
#include "Core/HLE/sceKernelInterrupt.h"
|
||||
#include "Core/Util/PPGeDraw.h"
|
||||
|
||||
#include "GPU/GPU.h"
|
||||
#include "GPU/GPUState.h"
|
||||
@@ -832,6 +833,7 @@ void __DisplayFlip(int cyclesLate) {
|
||||
|
||||
void hleAfterFlip(u64 userdata, int cyclesLate) {
|
||||
gpu->BeginFrame(); // doesn't really matter if begin or end of frame.
|
||||
PPGeNotifyFrame();
|
||||
|
||||
// This seems like as good a time as any to check if the config changed.
|
||||
if (lagSyncScheduled != g_Config.bForceLagSync) {
|
||||
|
||||
+269
-24
@@ -20,6 +20,7 @@
|
||||
#include "base/stringutil.h"
|
||||
#include "file/vfs.h"
|
||||
#include "gfx/texture_atlas.h"
|
||||
#include "gfx_es2/draw_text.h"
|
||||
#include "image/zim_load.h"
|
||||
#include "image/png_load.h"
|
||||
#include "util/text/utf8.h"
|
||||
@@ -103,6 +104,41 @@ static AtlasCharLine char_one_line;
|
||||
static AtlasLineArray char_lines;
|
||||
static AtlasTextMetrics char_lines_metrics;
|
||||
|
||||
static bool textDrawerInited = false;
|
||||
static TextDrawer *textDrawer = nullptr;
|
||||
struct PPGeTextDrawerCacheKey {
|
||||
bool operator < (const PPGeTextDrawerCacheKey &other) const {
|
||||
if (align != other.align)
|
||||
return align < other.align;
|
||||
if (wrapWidth != other.wrapWidth)
|
||||
return wrapWidth < other.wrapWidth;
|
||||
return text < other.text;
|
||||
}
|
||||
std::string text;
|
||||
int align;
|
||||
float wrapWidth;
|
||||
};
|
||||
struct PPGeTextDrawerImage {
|
||||
TextStringEntry entry;
|
||||
u32 ptr;
|
||||
};
|
||||
std::map<PPGeTextDrawerCacheKey, PPGeTextDrawerImage> textDrawerImages;
|
||||
|
||||
// Overwrite the current text lines buffer so it can be drawn later.
|
||||
void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale,
|
||||
int WrapType = PPGE_LINE_NONE, int wrapWidth = 0);
|
||||
|
||||
// Get the metrics of the bounding box of the currently stated text.
|
||||
void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n);
|
||||
|
||||
// These functions must be called between PPGeBegin and PPGeEnd.
|
||||
|
||||
// Draw currently buffered text using the state from PPGeGetTextBoundingBox() call.
|
||||
// Clears the buffer and state when done.
|
||||
void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF);
|
||||
|
||||
void PPGeSetTexture(u32 dataAddr, int width, int height);
|
||||
|
||||
//only 0xFFFFFF of data is used
|
||||
static void WriteCmd(u8 cmd, u32 data) {
|
||||
Memory::Write_U32((cmd << 24) | (data & 0xFFFFFF), dlWritePtr);
|
||||
@@ -129,18 +165,18 @@ static void BeginVertexData() {
|
||||
vertexStart = dataWritePtr;
|
||||
}
|
||||
|
||||
static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF) {
|
||||
static void Vertex(float x, float y, float u, float v, int tw, int th, u32 color = 0xFFFFFFFF, float off = -0.5f) {
|
||||
if (g_RemasterMode) {
|
||||
PPGeRemasterVertex vtx;
|
||||
vtx.x = x - 0.5f; vtx.y = y - 0.5f; vtx.z = 0;
|
||||
vtx.u = u * tw - 0.5f; vtx.v = v * th - 0.5f;
|
||||
vtx.x = x + off; vtx.y = y + off; vtx.z = 0;
|
||||
vtx.u = u * tw + off; vtx.v = v * th + off;
|
||||
vtx.color = color;
|
||||
Memory::WriteStruct(dataWritePtr, &vtx);
|
||||
dataWritePtr += sizeof(vtx);
|
||||
} else {
|
||||
PPGeVertex vtx;
|
||||
vtx.x = x - 0.5f; vtx.y = y - 0.5f; vtx.z = 0;
|
||||
vtx.u = u * tw - 0.5f; vtx.v = v * th - 0.5f;
|
||||
vtx.x = x + off; vtx.y = y + off; vtx.z = 0;
|
||||
vtx.u = u * tw + off; vtx.v = v * th + off;
|
||||
vtx.color = color;
|
||||
Memory::WriteStruct(dataWritePtr, &vtx);
|
||||
dataWritePtr += sizeof(vtx);
|
||||
@@ -232,13 +268,18 @@ void __PPGeInit()
|
||||
|
||||
free(imageData[0]);
|
||||
|
||||
// We can't create it here, because Android needs it on the right thread.
|
||||
textDrawerInited = false;
|
||||
textDrawer = nullptr;
|
||||
textDrawerImages.clear();
|
||||
|
||||
DEBUG_LOG(SCEGE, "PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Args: %08x",
|
||||
dlPtr, dataPtr, atlasPtr, atlasSize, listArgs.ptr);
|
||||
}
|
||||
|
||||
void __PPGeDoState(PointerWrap &p)
|
||||
{
|
||||
auto s = p.Section("PPGeDraw", 1, 2);
|
||||
auto s = p.Section("PPGeDraw", 1, 3);
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
@@ -256,6 +297,30 @@ void __PPGeDoState(PointerWrap &p)
|
||||
p.Do(listArgs);
|
||||
}
|
||||
|
||||
if (s >= 3) {
|
||||
uint32_t sz = (uint32_t)textDrawerImages.size();
|
||||
p.Do(sz);
|
||||
|
||||
switch (p.mode) {
|
||||
case PointerWrap::MODE_READ:
|
||||
textDrawerImages.clear();
|
||||
for (uint32_t i = 0; i < sz; ++i) {
|
||||
// We only care about the pointers, so we can free them. We'll decimate right away.
|
||||
PPGeTextDrawerCacheKey key{ StringFromFormat("__savestate__%d", i), -1, -1 };
|
||||
textDrawerImages[key] = PPGeTextDrawerImage{};
|
||||
p.Do(textDrawerImages[key].ptr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (const auto &im : textDrawerImages) {
|
||||
p.Do(im.second.ptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
textDrawerImages.clear();
|
||||
}
|
||||
|
||||
p.Do(dlPtr);
|
||||
p.Do(dlWritePtr);
|
||||
p.Do(dlSize);
|
||||
@@ -291,6 +356,9 @@ void __PPGeShutdown()
|
||||
dlPtr = 0;
|
||||
savedContextPtr = 0;
|
||||
listArgs = 0;
|
||||
|
||||
delete textDrawer;
|
||||
textDrawer = nullptr;
|
||||
}
|
||||
|
||||
void PPGeBegin()
|
||||
@@ -625,9 +693,51 @@ static AtlasTextMetrics BreakLines(const char *text, const AtlasFont &atlasfont,
|
||||
return metrics;
|
||||
}
|
||||
|
||||
static bool HasTextDrawer() {
|
||||
// We create this on first use so it's on the correct thread.
|
||||
if (textDrawerInited) {
|
||||
return textDrawer != nullptr;
|
||||
}
|
||||
|
||||
// TODO: Should we pass a draw_?
|
||||
textDrawer = TextDrawer::Create(nullptr);
|
||||
if (textDrawer) {
|
||||
textDrawer->SetFontScale(1.0f, 1.0f);
|
||||
textDrawer->SetForcedDPIScale(1.0f);
|
||||
textDrawer->SetFont(g_Config.sFont.c_str(), 20, 0);
|
||||
}
|
||||
textDrawerInited = true;
|
||||
return textDrawer != nullptr;
|
||||
}
|
||||
|
||||
void PPGeMeasureText(float *w, float *h, int *n,
|
||||
const char *text, float scale, int WrapType, int wrapWidth)
|
||||
{
|
||||
if (HasTextDrawer()) {
|
||||
float mw, mh;
|
||||
textDrawer->SetFontScale(scale, scale);
|
||||
int dtalign = (WrapType & PPGE_LINE_WRAP_WORD) ? FLAG_WRAP_TEXT : 0;
|
||||
if (WrapType & PPGE_LINE_USE_ELLIPSIS)
|
||||
dtalign |= FLAG_ELLIPSIZE_TEXT;
|
||||
Bounds b(0, 0, wrapWidth <= 0 ? 480.0f : wrapWidth, 272.0f);
|
||||
textDrawer->MeasureStringRect(text, strlen(text), b, &mw, &mh, dtalign);
|
||||
|
||||
if (w)
|
||||
*w = mw;
|
||||
if (h)
|
||||
*h = mh;
|
||||
if (n) {
|
||||
// Cheap way to get the n.
|
||||
float oneLine, twoLines;
|
||||
textDrawer->MeasureString("|", 1, &mw, &oneLine);
|
||||
textDrawer->MeasureStringRect("|\n|", 3, Bounds(0, 0, 480, 272), &mw, &twoLines);
|
||||
|
||||
float lineHeight = twoLines - oneLine;
|
||||
*n = (int)((mh + (lineHeight - 1)) / lineHeight);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const AtlasFont &atlasfont = g_ppge_atlas.fonts[0];
|
||||
AtlasTextMetrics metrics = BreakLines(text, atlasfont, 0, 0, 0, scale, scale, WrapType, wrapWidth, true);
|
||||
if (w) *w = metrics.maxWidth;
|
||||
@@ -683,8 +793,110 @@ void PPGeDrawCurrentText(u32 color)
|
||||
PPGeResetCurrentText();
|
||||
}
|
||||
|
||||
void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color)
|
||||
{
|
||||
// Return a value such that (1 << value) >= x
|
||||
int GetPow2(int x) {
|
||||
#ifdef __GNUC__
|
||||
int ret = 31 - __builtin_clz(x | 1);
|
||||
if ((1 << ret) < x)
|
||||
#else
|
||||
int ret = 0;
|
||||
while ((1 << ret) < x)
|
||||
#endif
|
||||
ret++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PPGeTextDrawerImage PPGeGetTextImage(const char *text, int align, float scale, float maxWidth, bool wrap) {
|
||||
int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0;
|
||||
tdalign |= FLAG_ELLIPSIZE_TEXT;
|
||||
if (wrap) {
|
||||
tdalign |= FLAG_WRAP_TEXT;
|
||||
}
|
||||
|
||||
PPGeTextDrawerCacheKey key{ text, tdalign, maxWidth / scale };
|
||||
PPGeTextDrawerImage im;
|
||||
|
||||
auto cacheItem = textDrawerImages.find(key);
|
||||
if (cacheItem != textDrawerImages.end()) {
|
||||
im = cacheItem->second;
|
||||
cacheItem->second.entry.lastUsedFrame = gpuStats.numFlips;
|
||||
} else {
|
||||
std::vector<uint8_t> bitmapData;
|
||||
textDrawer->SetFontScale(scale, scale);
|
||||
Bounds b(0, 0, maxWidth, 272.0f);
|
||||
std::string cleaned = ReplaceAll(text, "\r", "");
|
||||
textDrawer->DrawStringBitmapRect(bitmapData, im.entry, Draw::DataFormat::R8_UNORM, cleaned.c_str(), b, tdalign);
|
||||
|
||||
int bufwBytes = ((im.entry.bmWidth + 31) / 32) * 16;
|
||||
u32 sz = bufwBytes * (im.entry.bmHeight + 1);
|
||||
u32 origSz = sz;
|
||||
im.ptr = __PPGeDoAlloc(sz, true, "PPGeText");
|
||||
|
||||
if (bitmapData.size() & 1)
|
||||
bitmapData.resize(bitmapData.size() + 1);
|
||||
|
||||
if (im.ptr) {
|
||||
int wBytes = (im.entry.bmWidth + 1) / 2;
|
||||
u8 *ramPtr = (u8 *)Memory::GetPointer(im.ptr);
|
||||
for (int y = 0; y < im.entry.bmHeight; ++y) {
|
||||
for (int x = 0; x < wBytes; ++x) {
|
||||
uint8_t c1 = bitmapData[y * im.entry.bmWidth + x * 2];
|
||||
uint8_t c2 = bitmapData[y * im.entry.bmWidth + x * 2 + 1];
|
||||
// Convert this to 4-bit palette values.
|
||||
ramPtr[y * bufwBytes + x] = (c2 & 0xF0) | (c1 >> 4);
|
||||
}
|
||||
if (bufwBytes != wBytes) {
|
||||
memset(ramPtr + y * bufwBytes + wBytes, 0, bufwBytes - wBytes);
|
||||
}
|
||||
}
|
||||
memset(ramPtr + im.entry.bmHeight * bufwBytes, 0, bufwBytes + sz - origSz);
|
||||
}
|
||||
|
||||
im.entry.lastUsedFrame = gpuStats.numFlips;
|
||||
textDrawerImages[key] = im;
|
||||
}
|
||||
|
||||
return im;
|
||||
}
|
||||
|
||||
static void PPGeDrawTextImage(PPGeTextDrawerImage im, float x, float y, int align, float scale, u32 color) {
|
||||
int bufw = ((im.entry.bmWidth + 31) / 32) * 32;
|
||||
int wp2 = GetPow2(im.entry.bmWidth);
|
||||
int hp2 = GetPow2(im.entry.bmHeight);
|
||||
WriteCmd(GE_CMD_TEXADDR0, im.ptr & 0xFFFFF0);
|
||||
WriteCmd(GE_CMD_TEXBUFWIDTH0, bufw | ((im.ptr & 0xFF000000) >> 8));
|
||||
WriteCmd(GE_CMD_TEXSIZE0, wp2 | (hp2 << 8));
|
||||
WriteCmd(GE_CMD_TEXFLUSH, 0);
|
||||
|
||||
float w = im.entry.width * scale;
|
||||
float h = im.entry.height * scale;
|
||||
|
||||
if (align & PPGE_ALIGN_HCENTER)
|
||||
x -= w / 2.0f;
|
||||
else if (align & PPGE_ALIGN_RIGHT)
|
||||
x -= w;
|
||||
if (align & PPGE_ALIGN_VCENTER)
|
||||
y -= h / 2.0f;
|
||||
else if (align & PPGE_ALIGN_BOTTOM)
|
||||
y -= h;
|
||||
|
||||
BeginVertexData();
|
||||
float u1 = (float)im.entry.width / (1 << wp2);
|
||||
float v1 = (float)im.entry.height / (1 << hp2);
|
||||
Vertex(x, y, 0, 0, 1 << wp2, 1 << hp2, color, 0.0f);
|
||||
Vertex(x + w, y + h, u1, v1, 1 << wp2, 1 << hp2, color, 0.0f);
|
||||
EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
|
||||
|
||||
PPGeSetDefaultTexture();
|
||||
}
|
||||
|
||||
void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) {
|
||||
if (HasTextDrawer()) {
|
||||
PPGeTextDrawerImage im = PPGeGetTextImage(text, align, scale, 480.0f - x, false);
|
||||
PPGeDrawTextImage(im, x, y, align, scale, color);
|
||||
return;
|
||||
}
|
||||
|
||||
PPGePrepareText(text, x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS);
|
||||
PPGeDrawCurrentText(color);
|
||||
}
|
||||
@@ -718,10 +930,40 @@ void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, fl
|
||||
s = StripTrailingWhite(s);
|
||||
}
|
||||
|
||||
PPGePrepareText(s.c_str(), x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth);
|
||||
|
||||
int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480;
|
||||
float maxScaleDown = zoom == 1 ? 1.3f : 2.0f;
|
||||
|
||||
if (HasTextDrawer()) {
|
||||
float actualWidth, actualHeight;
|
||||
Bounds b(0, 0, wrapWidth <= 0 ? 480.0f - x : wrapWidth, wrapHeight);
|
||||
int tdalign = (align & PPGE_ALIGN_HCENTER) ? ALIGN_HCENTER : 0;
|
||||
textDrawer->SetFontScale(scale, scale);
|
||||
textDrawer->MeasureStringRect(s.c_str(), s.size(), b, &actualWidth, &actualHeight, tdalign | FLAG_WRAP_TEXT);
|
||||
if (wrapHeight != 0.0f && actualHeight > wrapHeight) {
|
||||
// Cheap way to get the line height.
|
||||
float oneLine, twoLines;
|
||||
textDrawer->MeasureString("|", 1, &actualWidth, &oneLine);
|
||||
textDrawer->MeasureStringRect("|\n|", 3, Bounds(0, 0, 480, 272), &actualWidth, &twoLines);
|
||||
|
||||
float lineHeight = twoLines - oneLine;
|
||||
if (actualHeight > wrapHeight * maxScaleDown) {
|
||||
float maxLines = floor(wrapHeight * maxScaleDown / lineHeight);
|
||||
actualHeight = (maxLines + 1) * lineHeight;
|
||||
// Add an ellipsis if it's just too long to be readable.
|
||||
// On a PSP, it does this without scaling it down.
|
||||
s = StripTrailingWhite(CropLinesToCount(s, (int)maxLines)) + "\n...";
|
||||
}
|
||||
|
||||
scale *= wrapHeight / actualHeight;
|
||||
}
|
||||
|
||||
PPGeTextDrawerImage im = PPGeGetTextImage(s.c_str(), align, scale, wrapWidth <= 0 ? 480.0f - x : wrapWidth, true);
|
||||
PPGeDrawTextImage(im, x, y, align, scale, color);
|
||||
return;
|
||||
}
|
||||
|
||||
PPGePrepareText(s.c_str(), x, y, align, scale, scale, PPGE_LINE_USE_ELLIPSIS | PPGE_LINE_WRAP_WORD, wrapWidth);
|
||||
|
||||
float actualHeight = char_lines_metrics.lineHeight * char_lines_metrics.numLines;
|
||||
if (wrapHeight != 0.0f && actualHeight > wrapHeight) {
|
||||
if (actualHeight > wrapHeight * maxScaleDown) {
|
||||
@@ -837,20 +1079,6 @@ void PPGeDrawImage(float x, float y, float w, float h, float u1, float v1, float
|
||||
EndVertexDataAndDraw(GE_PRIM_RECTANGLES);
|
||||
}
|
||||
|
||||
// Return a value such that (1 << value) >= x
|
||||
int GetPow2(int x)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
int ret = 31 - __builtin_clz(x|1);
|
||||
if ((1 << ret) < x)
|
||||
#else
|
||||
int ret = 0;
|
||||
while ((1 << ret) < x)
|
||||
#endif
|
||||
ret++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PPGeSetDefaultTexture()
|
||||
{
|
||||
WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1);
|
||||
@@ -1004,3 +1232,20 @@ void PPGeImage::SetTexture() {
|
||||
}
|
||||
}
|
||||
|
||||
void PPGeNotifyFrame() {
|
||||
if (textDrawer) {
|
||||
textDrawer->OncePerFrame();
|
||||
}
|
||||
|
||||
// Do this always, in case the platform has no TextDrawer but save state did.
|
||||
for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) {
|
||||
if (it->second.entry.lastUsedFrame - gpuStats.numFlips >= 97) {
|
||||
kernelMemory.Free(it->second.ptr);
|
||||
it = textDrawerImages.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
PPGeImage::Decimate();
|
||||
}
|
||||
+4
-24
@@ -45,10 +45,6 @@ void __PPGeShutdown();
|
||||
void PPGeBegin();
|
||||
void PPGeEnd();
|
||||
|
||||
// If you want to draw using this texture but not go through the PSP GE emulation,
|
||||
// jsut call this. Will bind the texture to unit 0.
|
||||
void PPGeBindTexture();
|
||||
|
||||
enum {
|
||||
PPGE_ALIGN_LEFT = 0,
|
||||
PPGE_ALIGN_RIGHT = 16,
|
||||
@@ -65,11 +61,6 @@ enum {
|
||||
PPGE_ALIGN_BOTTOMRIGHT = PPGE_ALIGN_BOTTOM | PPGE_ALIGN_RIGHT,
|
||||
};
|
||||
|
||||
enum {
|
||||
PPGE_ESCAPE_NONE,
|
||||
PPGE_ESCAPE_BACKSLASHED,
|
||||
};
|
||||
|
||||
enum {
|
||||
PPGE_LINE_NONE = 0,
|
||||
PPGE_LINE_USE_ELLIPSIS = 1, // use ellipses in too long words
|
||||
@@ -81,19 +72,6 @@ enum {
|
||||
void PPGeMeasureText(float *w, float *h, int *n,
|
||||
const char *text, float scale, int WrapType = PPGE_LINE_NONE, int wrapWidth = 0);
|
||||
|
||||
// Overwrite the current text lines buffer so it can be drawn later.
|
||||
void PPGePrepareText(const char *text, float x, float y, int align, float scale, float lineHeightScale,
|
||||
int WrapType = PPGE_LINE_NONE, int wrapWidth = 0);
|
||||
|
||||
// Get the metrics of the bounding box of the currently stated text.
|
||||
void PPGeMeasureCurrentText(float *x, float *y, float *w, float *h, int *n);
|
||||
|
||||
// These functions must be called between PPGeBegin and PPGeEnd.
|
||||
|
||||
// Draw currently buffered text using the state from PPGeGetTextBoundingBox() call.
|
||||
// Clears the buffer and state when done.
|
||||
void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF);
|
||||
|
||||
// Draws some text using the one font we have.
|
||||
// Clears the text buffer when done.
|
||||
void PPGeDrawText(const char *text, float x, float y, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF);
|
||||
@@ -107,6 +85,8 @@ void PPGeDrawImage(ImageID atlasImage, float x, float y, int align, u32 color =
|
||||
void PPGeDrawImage(ImageID atlasImage, float x, float y, float w, float h, int align, u32 color = 0xFFFFFFFF);
|
||||
void PPGeDrawImage(float x, float y, float w, float h, float u1, float v1, float u2, float v2, int tw, int th, u32 color);
|
||||
|
||||
void PPGeNotifyFrame();
|
||||
|
||||
class PPGeImage {
|
||||
public:
|
||||
PPGeImage(const std::string &pspFilename);
|
||||
@@ -132,8 +112,9 @@ public:
|
||||
return height_;
|
||||
}
|
||||
|
||||
private:
|
||||
static void Decimate();
|
||||
|
||||
private:
|
||||
static std::vector<PPGeImage *> loadedTextures_;
|
||||
|
||||
std::string filename_;
|
||||
@@ -152,6 +133,5 @@ private:
|
||||
void PPGeDrawRect(float x1, float y1, float x2, float y2, u32 color);
|
||||
|
||||
void PPGeSetDefaultTexture();
|
||||
void PPGeSetTexture(u32 dataAddr, int width, int height);
|
||||
void PPGeDisableTexture();
|
||||
|
||||
@@ -365,7 +365,7 @@ void DrawBuffer::DrawImage2GridH(ImageID atlas_image, float x1, float y1, float
|
||||
class AtlasWordWrapper : public WordWrapper {
|
||||
public:
|
||||
// Note: maxW may be height if rotated.
|
||||
AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW) : WordWrapper(str, maxW), atlasfont_(atlasfont), scale_(scale) {
|
||||
AtlasWordWrapper(const AtlasFont &atlasfont, float scale, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), atlasfont_(atlasfont), scale_(scale) {
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -442,14 +442,15 @@ void DrawBuffer::MeasureTextRect(FontID font_id, const char *text, int count, co
|
||||
}
|
||||
|
||||
std::string toMeasure = std::string(text, count);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
const AtlasFont *font = atlas->getFont(font_id);
|
||||
if (!font) {
|
||||
*w = 0.0f;
|
||||
*h = 0.0f;
|
||||
return;
|
||||
}
|
||||
AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w);
|
||||
AtlasWordWrapper wrapper(*font, fontscalex, toMeasure.c_str(), bounds.w, wrap);
|
||||
toMeasure = wrapper.Wrapped();
|
||||
}
|
||||
MeasureTextCount(font_id, toMeasure.c_str(), (int)toMeasure.length(), w, h);
|
||||
@@ -491,8 +492,9 @@ void DrawBuffer::DrawTextRect(FontID font, const char *text, float x, float y, f
|
||||
}
|
||||
|
||||
std::string toDraw = text;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w);
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
AtlasWordWrapper wrapper(*atlas->getFont(font), fontscalex, toDraw.c_str(), w, wrap);
|
||||
toDraw = wrapper.Wrapped();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ enum {
|
||||
FLAG_DYNAMIC_ASCII = 2048,
|
||||
FLAG_NO_PREFIX = 4096, // means to not process ampersands
|
||||
FLAG_WRAP_TEXT = 8192,
|
||||
FLAG_ELLIPSIZE_TEXT = 16384,
|
||||
};
|
||||
|
||||
namespace Draw {
|
||||
|
||||
@@ -24,8 +24,8 @@ float TextDrawerWordWrapper::MeasureWidth(const char *str, size_t bytes) {
|
||||
return w;
|
||||
}
|
||||
|
||||
void TextDrawer::WrapString(std::string &out, const char *str, float maxW) {
|
||||
TextDrawerWordWrapper wrapper(this, str, maxW);
|
||||
void TextDrawer::WrapString(std::string &out, const char *str, float maxW, int flags) {
|
||||
TextDrawerWordWrapper wrapper(this, str, maxW, flags);
|
||||
out = wrapper.Wrapped();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ void TextDrawer::SetFontScale(float xscale, float yscale) {
|
||||
}
|
||||
|
||||
float TextDrawer::CalculateDPIScale() {
|
||||
if (ignoreGlobalDpi_)
|
||||
return dpiScale_;
|
||||
float scale = g_dpi_scale_y;
|
||||
if (scale >= 1.0f) {
|
||||
scale = 1.0f;
|
||||
@@ -42,6 +44,41 @@ float TextDrawer::CalculateDPIScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
void TextDrawer::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) {
|
||||
float x = bounds.x;
|
||||
float y = bounds.y;
|
||||
if (align & ALIGN_HCENTER) {
|
||||
x = bounds.centerX();
|
||||
} else if (align & ALIGN_RIGHT) {
|
||||
x = bounds.x2();
|
||||
}
|
||||
if (align & ALIGN_VCENTER) {
|
||||
y = bounds.centerY();
|
||||
} else if (align & ALIGN_BOTTOM) {
|
||||
y = bounds.y2();
|
||||
}
|
||||
|
||||
std::string toDraw = str;
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
DrawString(target, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
void TextDrawer::DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align) {
|
||||
std::string toDraw = str;
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
DrawStringBitmap(bitmapData, entry, texFormat, toDraw.c_str(), align);
|
||||
}
|
||||
|
||||
TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) {
|
||||
TextDrawer *drawer = nullptr;
|
||||
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
|
||||
|
||||
@@ -58,11 +58,17 @@ public:
|
||||
virtual void MeasureString(const char *str, size_t len, float *w, float *h) = 0;
|
||||
virtual void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) = 0;
|
||||
virtual void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) = 0;
|
||||
virtual void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) = 0;
|
||||
void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align);
|
||||
virtual void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) = 0;
|
||||
void DrawStringBitmapRect(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, const Bounds &bounds, int align);
|
||||
// Use for housekeeping like throwing out old strings.
|
||||
virtual void OncePerFrame() = 0;
|
||||
|
||||
float CalculateDPIScale();
|
||||
void SetForcedDPIScale(float dpi) {
|
||||
dpiScale_ = dpi;
|
||||
ignoreGlobalDpi_ = true;
|
||||
}
|
||||
|
||||
// Factory function that selects implementation.
|
||||
static TextDrawer *Create(Draw::DrawContext *draw);
|
||||
@@ -72,7 +78,7 @@ protected:
|
||||
|
||||
Draw::DrawContext *draw_;
|
||||
virtual void ClearCache() = 0;
|
||||
void WrapString(std::string &out, const char *str, float maxWidth);
|
||||
void WrapString(std::string &out, const char *str, float maxWidth, int flags);
|
||||
|
||||
struct CacheKey {
|
||||
bool operator < (const CacheKey &other) const {
|
||||
@@ -86,20 +92,21 @@ protected:
|
||||
uint32_t fontHash;
|
||||
};
|
||||
|
||||
int frameCount_;
|
||||
float fontScaleX_;
|
||||
float fontScaleY_;
|
||||
float dpiScale_;
|
||||
int frameCount_ = 0;
|
||||
float fontScaleX_ = 1.0f;
|
||||
float fontScaleY_ = 1.0f;
|
||||
float dpiScale_ = 1.0f;
|
||||
bool ignoreGlobalDpi_ = false;
|
||||
};
|
||||
|
||||
|
||||
class TextDrawerWordWrapper : public WordWrapper {
|
||||
public:
|
||||
TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW) : WordWrapper(str, maxW), drawer_(drawer) {
|
||||
TextDrawerWordWrapper(TextDrawer *drawer, const char *str, float maxW, int flags) : WordWrapper(str, maxW, flags), drawer_(drawer) {
|
||||
}
|
||||
|
||||
protected:
|
||||
float MeasureWidth(const char *str, size_t bytes) override;
|
||||
|
||||
TextDrawer *drawer_;
|
||||
};
|
||||
};
|
||||
@@ -115,9 +115,10 @@ void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bou
|
||||
}
|
||||
|
||||
std::string toMeasure = std::string(str, len);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w);
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
std::vector<std::string> lines;
|
||||
@@ -154,6 +155,68 @@ void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bou
|
||||
*h = total_h * dpiScale_;
|
||||
}
|
||||
|
||||
void TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) {
|
||||
if (!strlen(str)) {
|
||||
bitmapData.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
double size = 0.0;
|
||||
auto iter = fontMap_.find(fontHash_);
|
||||
if (iter != fontMap_.end()) {
|
||||
size = iter->second.size;
|
||||
} else {
|
||||
ELOG("Missing font");
|
||||
}
|
||||
|
||||
jstring jstr = env_->NewStringUTF(str);
|
||||
uint32_t textSize = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, size);
|
||||
int imageWidth = (short)(textSize >> 16);
|
||||
int imageHeight = (short)(textSize & 0xFFFF);
|
||||
if (imageWidth <= 0)
|
||||
imageWidth = 1;
|
||||
if (imageHeight <= 0)
|
||||
imageHeight = 1;
|
||||
|
||||
jintArray imageData = (jintArray)env_->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, size);
|
||||
env_->DeleteLocalRef(jstr);
|
||||
|
||||
entry.texture = nullptr;
|
||||
entry.bmWidth = imageWidth;
|
||||
entry.width = imageWidth;
|
||||
entry.bmHeight = imageHeight;
|
||||
entry.height = imageHeight;
|
||||
entry.lastUsedFrame = frameCount_;
|
||||
|
||||
jint *jimage = env_->GetIntArrayElements(imageData, nullptr);
|
||||
assert(env_->GetArrayLength(imageData) == imageWidth * imageHeight);
|
||||
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 x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
uint32_t v = jimage[imageWidth * y + x];
|
||||
v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel.
|
||||
bitmapData16[entry.bmWidth * y + x] = (uint16_t)v;
|
||||
}
|
||||
}
|
||||
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
||||
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
||||
for (int x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
uint32_t v = jimage[imageWidth * y + x];
|
||||
v = (v >> 12) & 0xF; // Just grab some bits from the green channel.
|
||||
bitmapData[entry.bmWidth * y + x] = (uint8_t)(v | (v << 4));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ELOG("Bad TextDrawer format");
|
||||
assert(false);
|
||||
}
|
||||
env_->ReleaseIntArrayElements(imageData, jimage, 0);
|
||||
env_->DeleteLocalRef(imageData);
|
||||
}
|
||||
|
||||
void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) {
|
||||
using namespace Draw;
|
||||
std::string text(NormalizeString(std::string(str)));
|
||||
@@ -169,66 +232,32 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x,
|
||||
if (iter != cache_.end()) {
|
||||
entry = iter->second.get();
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
if (entry->texture)
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
} else {
|
||||
double size = 0.0;
|
||||
auto iter = fontMap_.find(fontHash_);
|
||||
if (iter != fontMap_.end()) {
|
||||
size = iter->second.size;
|
||||
} else {
|
||||
ELOG("Missing font");
|
||||
}
|
||||
|
||||
jstring jstr = env_->NewStringUTF(text.c_str());
|
||||
uint32_t textSize = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, size);
|
||||
int imageWidth = (short)(textSize >> 16);
|
||||
int imageHeight = (short)(textSize & 0xFFFF);
|
||||
if (imageWidth <= 0)
|
||||
imageWidth = 1;
|
||||
if (imageHeight <= 0)
|
||||
imageHeight = 1;
|
||||
|
||||
jintArray imageData = (jintArray)env_->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, size);
|
||||
env_->DeleteLocalRef(jstr);
|
||||
DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
|
||||
|
||||
entry = new TextStringEntry();
|
||||
entry->bmWidth = imageWidth;
|
||||
entry->width = imageWidth;
|
||||
entry->bmHeight = imageHeight;
|
||||
entry->height = imageHeight;
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
|
||||
TextureDesc desc{};
|
||||
std::vector<uint8_t> bitmapData;
|
||||
DrawStringBitmap(bitmapData, *entry, texFormat, text.c_str(), align);
|
||||
desc.initData.push_back(&bitmapData[0]);
|
||||
|
||||
desc.type = TextureType::LINEAR2D;
|
||||
desc.format = Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
|
||||
desc.format = texFormat;
|
||||
desc.width = entry->bmWidth;
|
||||
desc.height = entry->bmHeight;
|
||||
desc.depth = 1;
|
||||
desc.mipLevels = 1;
|
||||
desc.generateMips = false;
|
||||
desc.tag = "TextDrawer";
|
||||
|
||||
uint16_t *bitmapData = new uint16_t[entry->bmWidth * entry->bmHeight];
|
||||
jint* jimage = env_->GetIntArrayElements(imageData, nullptr);
|
||||
assert(env_->GetArrayLength(imageData) == imageWidth * imageHeight);
|
||||
for (int x = 0; x < entry->bmWidth; x++) {
|
||||
for (int y = 0; y < entry->bmHeight; y++) {
|
||||
uint32_t v = jimage[imageWidth * y + x];
|
||||
v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel.
|
||||
bitmapData[entry->bmWidth * y + x] = (uint16_t)v;
|
||||
}
|
||||
}
|
||||
env_->ReleaseIntArrayElements(imageData, jimage, 0);
|
||||
env_->DeleteLocalRef(imageData);
|
||||
desc.initData.push_back((uint8_t *)bitmapData);
|
||||
entry->texture = draw_->CreateTexture(desc);
|
||||
delete[] bitmapData;
|
||||
cache_[key] = std::unique_ptr<TextStringEntry>(entry);
|
||||
if (entry->texture) {
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->texture) {
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
}
|
||||
|
||||
float w = entry->bmWidth * fontScaleX_ * dpiScale_;
|
||||
float h = entry->bmHeight * fontScaleY_ * dpiScale_;
|
||||
DrawBuffer::DoAlign(align, &x, &y, &w, &h);
|
||||
@@ -247,29 +276,6 @@ void TextDrawerAndroid::ClearCache() {
|
||||
sizeCache_.clear();
|
||||
}
|
||||
|
||||
void TextDrawerAndroid::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) {
|
||||
float x = bounds.x;
|
||||
float y = bounds.y;
|
||||
if (align & ALIGN_HCENTER) {
|
||||
x = bounds.centerX();
|
||||
} else if (align & ALIGN_RIGHT) {
|
||||
x = bounds.x2();
|
||||
}
|
||||
if (align & ALIGN_VCENTER) {
|
||||
y = bounds.centerY();
|
||||
} else if (align & ALIGN_BOTTOM) {
|
||||
y = bounds.y2();
|
||||
}
|
||||
|
||||
std::string toDraw = str;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
DrawString(target, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
void TextDrawerAndroid::OncePerFrame() {
|
||||
frameCount_++;
|
||||
// If DPI changed (small-mode, future proper monitor DPI support), drop everything.
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
void MeasureString(const char *str, size_t len, float *w, float *h) override;
|
||||
void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override;
|
||||
void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override;
|
||||
// Use for housekeeping like throwing out old strings.
|
||||
void OncePerFrame() override;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cassert>
|
||||
#include "base/display.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringutil.h"
|
||||
@@ -33,7 +34,7 @@ uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) {
|
||||
return fontHash;
|
||||
}
|
||||
|
||||
QFont* font = fontName ? new QFont(fontName) : new QFont();
|
||||
QFont *font = fontName ? new QFont(fontName) : new QFont();
|
||||
font->setPixelSize(size + 6);
|
||||
fontMap_[fontHash] = font;
|
||||
fontHash_ = fontHash;
|
||||
@@ -41,22 +42,44 @@ uint32_t TextDrawerQt::SetFont(const char *fontName, int size, int flags) {
|
||||
}
|
||||
|
||||
void TextDrawerQt::SetFont(uint32_t fontHandle) {
|
||||
|
||||
uint32_t fontHash = fontHandle;
|
||||
auto iter = fontMap_.find(fontHash);
|
||||
if (iter != fontMap_.end()) {
|
||||
fontHash_ = fontHandle;
|
||||
} else {
|
||||
ELOG("Invalid font handle %08x", fontHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerQt::MeasureString(const char *str, size_t len, float *w, float *h) {
|
||||
QFont* font = fontMap_.find(fontHash_)->second;
|
||||
QFontMetrics fm(*font);
|
||||
QSize size = fm.size(0, QString::fromUtf8(str, (int)len));
|
||||
*w = (float)size.width() * fontScaleX_;
|
||||
*h = (float)size.height() * fontScaleY_;
|
||||
CacheKey key{ std::string(str, len), fontHash_ };
|
||||
|
||||
TextMeasureEntry *entry;
|
||||
auto iter = sizeCache_.find(key);
|
||||
if (iter != sizeCache_.end()) {
|
||||
entry = iter->second.get();
|
||||
} else {
|
||||
QFont* font = fontMap_.find(fontHash_)->second;
|
||||
QFontMetrics fm(*font);
|
||||
QSize size = fm.size(0, QString::fromUtf8(str, (int)len));
|
||||
|
||||
entry = new TextMeasureEntry();
|
||||
entry->width = size.width();
|
||||
entry->height = size.height();
|
||||
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
|
||||
}
|
||||
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
*w = entry->width * fontScaleX_ * dpiScale_;
|
||||
*h = entry->height * fontScaleY_ * dpiScale_;
|
||||
}
|
||||
|
||||
void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) {
|
||||
std::string toMeasure = std::string(str, len);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w);
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
QFont* font = fontMap_.find(fontHash_)->second;
|
||||
@@ -66,71 +89,102 @@ void TextDrawerQt::MeasureStringRect(const char *str, size_t len, const Bounds &
|
||||
*h = (float)size.height() * fontScaleY_;
|
||||
}
|
||||
|
||||
void TextDrawerQt::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) {
|
||||
if (!strlen(str)) {
|
||||
bitmapData.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
QFont *font = fontMap_.find(fontHash_)->second;
|
||||
QFontMetrics fm(*font);
|
||||
QSize size = fm.size(0, QString::fromUtf8(str));
|
||||
QImage image((size.width() + 3) & ~3, (size.height() + 3) & ~3, QImage::Format_ARGB32_Premultiplied);
|
||||
if (image.isNull()) {
|
||||
bitmapData.clear();
|
||||
return;
|
||||
}
|
||||
image.fill(0);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&image);
|
||||
painter.setFont(*font);
|
||||
painter.setPen(0xFFFFFFFF);
|
||||
// TODO: Involve ALIGN_HCENTER (bounds etc.)
|
||||
painter.drawText(image.rect(), Qt::AlignTop | Qt::AlignLeft, QString::fromUtf8(str).replace("&&", "&"));
|
||||
painter.end();
|
||||
|
||||
entry.texture = nullptr;
|
||||
entry.bmWidth = entry.width = image.width();
|
||||
entry.bmHeight = entry.height = image.height();
|
||||
entry.lastUsedFrame = frameCount_;
|
||||
|
||||
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 x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
bitmapData16[entry.bmWidth * y + x] = 0xfff0 | (image.pixel(x, y) >> 28);
|
||||
}
|
||||
}
|
||||
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
||||
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
||||
for (int x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
bitmapData[entry.bmWidth * y + x] = image.pixel(x, y) >> 24;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ELOG("Bad TextDrawer format");
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerQt::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) {
|
||||
using namespace Draw;
|
||||
if (!strlen(str))
|
||||
return;
|
||||
|
||||
uint32_t stringHash = hash::Adler32((const uint8_t *)str, strlen(str));
|
||||
uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24);
|
||||
|
||||
CacheKey key{ std::string(str), fontHash_ };
|
||||
target.Flush(true);
|
||||
|
||||
TextStringEntry *entry;
|
||||
|
||||
auto iter = cache_.find(entryHash);
|
||||
auto iter = cache_.find(key);
|
||||
if (iter != cache_.end()) {
|
||||
entry = iter->second.get();
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
} else {
|
||||
QFont *font = fontMap_.find(fontHash_)->second;
|
||||
QFontMetrics fm(*font);
|
||||
QSize size = fm.size(0, QString::fromUtf8(str));
|
||||
QImage image((size.width() + 3) & ~3, (size.height() + 3) & ~3, QImage::Format_ARGB32_Premultiplied);
|
||||
if (image.isNull()) {
|
||||
return;
|
||||
}
|
||||
image.fill(0);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&image);
|
||||
painter.setFont(*font);
|
||||
painter.setPen(color);
|
||||
// TODO: Involve ALIGN_HCENTER (bounds etc.)
|
||||
painter.drawText(image.rect(), Qt::AlignTop | Qt::AlignLeft, QString::fromUtf8(str).replace("&&", "&"));
|
||||
painter.end();
|
||||
DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
|
||||
|
||||
entry = new TextStringEntry();
|
||||
entry->bmWidth = entry->width = image.width();
|
||||
entry->bmHeight = entry->height = image.height();
|
||||
|
||||
TextureDesc desc{};
|
||||
std::vector<uint8_t> bitmapData;
|
||||
DrawStringBitmap(bitmapData, *entry, texFormat, str, align);
|
||||
desc.initData.push_back(&bitmapData[0]);
|
||||
|
||||
desc.type = TextureType::LINEAR2D;
|
||||
desc.format = Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
|
||||
desc.format = texFormat;
|
||||
desc.width = entry->bmWidth;
|
||||
desc.height = entry->bmHeight;
|
||||
desc.depth = 1;
|
||||
desc.mipLevels = 1;
|
||||
desc.tag = "TextDrawer";
|
||||
|
||||
uint16_t *bitmapData = new uint16_t[entry->bmWidth * entry->bmHeight];
|
||||
for (int x = 0; x < entry->bmWidth; x++) {
|
||||
for (int y = 0; y < entry->bmHeight; y++) {
|
||||
bitmapData[entry->bmWidth * y + x] = 0xfff0 | image.pixel(x, y) >> 28;
|
||||
}
|
||||
}
|
||||
desc.initData.push_back((uint8_t *)bitmapData);
|
||||
entry->texture = draw_->CreateTexture(desc);
|
||||
delete[] bitmapData;
|
||||
cache_[entryHash] = std::unique_ptr<TextStringEntry>(entry);
|
||||
cache_[key] = std::unique_ptr<TextStringEntry>(entry);
|
||||
}
|
||||
|
||||
float w = entry->bmWidth * fontScaleX_;
|
||||
float h = entry->bmHeight * fontScaleY_;
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
if (entry->texture) {
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
}
|
||||
|
||||
float w = entry->bmWidth * fontScaleX_ * dpiScale_;
|
||||
float h = entry->bmHeight * fontScaleY_ * dpiScale_;
|
||||
DrawBuffer::DoAlign(align, &x, &y, &w, &h);
|
||||
target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color);
|
||||
target.Flush(true);
|
||||
if (entry->texture) {
|
||||
target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color);
|
||||
target.Flush(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerQt::ClearCache() {
|
||||
@@ -148,29 +202,6 @@ void TextDrawerQt::ClearCache() {
|
||||
fontHash_ = 0;
|
||||
}
|
||||
|
||||
void TextDrawerQt::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) {
|
||||
float x = bounds.x;
|
||||
float y = bounds.y;
|
||||
if (align & ALIGN_HCENTER) {
|
||||
x = bounds.centerX();
|
||||
} else if (align & ALIGN_RIGHT) {
|
||||
x = bounds.x2();
|
||||
}
|
||||
if (align & ALIGN_VCENTER) {
|
||||
y = bounds.centerY();
|
||||
} else if (align & ALIGN_BOTTOM) {
|
||||
y = bounds.y2();
|
||||
}
|
||||
|
||||
std::string toDraw = str;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
DrawString(target, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
void TextDrawerQt::OncePerFrame() {
|
||||
frameCount_++;
|
||||
// If DPI changed (small-mode, future proper monitor DPI support), drop everything.
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
void MeasureString(const char *str, size_t len, float *w, float *h) override;
|
||||
void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override;
|
||||
void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override;
|
||||
// Use for housekeeping like throwing out old strings.
|
||||
void OncePerFrame() override;
|
||||
|
||||
@@ -27,9 +27,8 @@ protected:
|
||||
uint32_t fontHash_;
|
||||
std::map<uint32_t, QFont *> fontMap_;
|
||||
|
||||
// The key is the CityHash of the string xor the fontHash_.
|
||||
std::map<uint32_t, std::unique_ptr<TextStringEntry>> cache_;
|
||||
std::map<uint32_t, std::unique_ptr<TextMeasureEntry>> sizeCache_;
|
||||
std::map<CacheKey, std::unique_ptr<TextStringEntry>> cache_;
|
||||
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cassert>
|
||||
#include "base/display.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringutil.h"
|
||||
@@ -153,11 +154,15 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound
|
||||
}
|
||||
|
||||
std::string toMeasure = std::string(str, len);
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w);
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
TEXTMETRIC metrics{};
|
||||
GetTextMetrics(ctx_->hDC, &metrics);
|
||||
|
||||
std::vector<std::string> lines;
|
||||
SplitString(toMeasure, '\n', lines);
|
||||
float total_w = 0.0f;
|
||||
@@ -184,19 +189,114 @@ void TextDrawerWin32::MeasureStringRect(const char *str, size_t len, const Bound
|
||||
if (total_w < entry->width * fontScaleX_) {
|
||||
total_w = entry->width * fontScaleX_;
|
||||
}
|
||||
total_h += entry->height * fontScaleY_;
|
||||
int h = i == lines.size() - 1 ? entry->height : metrics.tmHeight + metrics.tmExternalLeading;
|
||||
total_h += h * fontScaleY_;
|
||||
}
|
||||
|
||||
*w = total_w * dpiScale_;
|
||||
*h = total_h * dpiScale_;
|
||||
}
|
||||
|
||||
void TextDrawerWin32::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) {
|
||||
if (!strlen(str)) {
|
||||
bitmapData.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
|
||||
SIZE size;
|
||||
|
||||
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_HIDEPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0);
|
||||
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;
|
||||
// Prevent zero-sized textures, which can occur. Not worth to avoid
|
||||
// creating the texture altogether in this case. One example is a string
|
||||
// containing only '\r\n', see issue #10764.
|
||||
if (size.cx == 0)
|
||||
size.cx = 1;
|
||||
if (size.cy == 0)
|
||||
size.cy = 1;
|
||||
|
||||
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_HIDEPREFIX | 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 {
|
||||
ELOG("Bad TextDrawer format");
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) {
|
||||
using namespace Draw;
|
||||
if (!strlen(str))
|
||||
return;
|
||||
|
||||
CacheKey key{ std::string(str), fontHash_ };
|
||||
|
||||
target.Flush(true);
|
||||
|
||||
TextStringEntry *entry;
|
||||
@@ -206,52 +306,6 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f
|
||||
entry = iter->second.get();
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
} else {
|
||||
// Render the string to our bitmap and save to a GL texture.
|
||||
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
|
||||
SIZE size;
|
||||
|
||||
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_HIDEPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0);
|
||||
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;
|
||||
// Prevent zero-sized textures, which can occur. Not worth to avoid
|
||||
// creating the texture altogether in this case. One example is a string
|
||||
// containing only '\r\n', see issue #10764.
|
||||
if (size.cx == 0)
|
||||
size.cx = 1;
|
||||
if (size.cy == 0)
|
||||
size.cy = 1;
|
||||
|
||||
entry = new TextStringEntry();
|
||||
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_HIDEPREFIX | DT_TOP | dtAlign, 0);
|
||||
|
||||
DataFormat texFormat;
|
||||
// For our purposes these are equivalent, so just choose the supported one. D3D can emulate them.
|
||||
if (draw_->GetDataFormatSupport(Draw::DataFormat::A4R4G4B4_UNORM_PACK16) & FMT_TEXTURE)
|
||||
@@ -263,39 +317,14 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f
|
||||
else
|
||||
texFormat = Draw::DataFormat::R8G8B8A8_UNORM;
|
||||
|
||||
entry = new TextStringEntry();
|
||||
|
||||
// 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.
|
||||
TextureDesc desc{};
|
||||
uint32_t *bitmapData32 = nullptr;
|
||||
uint16_t *bitmapData16 = nullptr;
|
||||
if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) {
|
||||
bitmapData32 = new uint32_t[entry->bmWidth * entry->bmHeight];
|
||||
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;
|
||||
}
|
||||
}
|
||||
desc.initData.push_back((uint8_t *)bitmapData32);
|
||||
} else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
|
||||
bitmapData16 = new uint16_t[entry->bmWidth * entry->bmHeight];
|
||||
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;
|
||||
}
|
||||
}
|
||||
desc.initData.push_back((uint8_t *)bitmapData16);
|
||||
} else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) {
|
||||
bitmapData16 = new uint16_t[entry->bmWidth * entry->bmHeight];
|
||||
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;
|
||||
}
|
||||
}
|
||||
desc.initData.push_back((uint8_t *)bitmapData16);
|
||||
}
|
||||
std::vector<uint8_t> bitmapData;
|
||||
DrawStringBitmap(bitmapData, *entry, texFormat, str, align);
|
||||
desc.initData.push_back(&bitmapData[0]);
|
||||
|
||||
desc.type = TextureType::LINEAR2D;
|
||||
desc.format = texFormat;
|
||||
@@ -305,14 +334,12 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f
|
||||
desc.mipLevels = 1;
|
||||
desc.tag = "TextDrawer";
|
||||
entry->texture = draw_->CreateTexture(desc);
|
||||
if (bitmapData16)
|
||||
delete[] bitmapData16;
|
||||
if (bitmapData32)
|
||||
delete[] bitmapData32;
|
||||
cache_[key] = std::unique_ptr<TextStringEntry>(entry);
|
||||
}
|
||||
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
if (entry->texture) {
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
}
|
||||
|
||||
// Okay, the texture is bound, let's draw.
|
||||
float w = entry->width * fontScaleX_ * dpiScale_;
|
||||
@@ -320,8 +347,10 @@ void TextDrawerWin32::DrawString(DrawBuffer &target, const char *str, float x, f
|
||||
float u = entry->width / (float)entry->bmWidth;
|
||||
float v = entry->height / (float)entry->bmHeight;
|
||||
DrawBuffer::DoAlign(align, &x, &y, &w, &h);
|
||||
target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color);
|
||||
target.Flush(true);
|
||||
if (entry->texture) {
|
||||
target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, u, v, color);
|
||||
target.Flush(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerWin32::RecreateFonts() {
|
||||
@@ -340,29 +369,6 @@ void TextDrawerWin32::ClearCache() {
|
||||
sizeCache_.clear();
|
||||
}
|
||||
|
||||
void TextDrawerWin32::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) {
|
||||
float x = bounds.x;
|
||||
float y = bounds.y;
|
||||
if (align & ALIGN_HCENTER) {
|
||||
x = bounds.centerX();
|
||||
} else if (align & ALIGN_RIGHT) {
|
||||
x = bounds.x2();
|
||||
}
|
||||
if (align & ALIGN_VCENTER) {
|
||||
y = bounds.centerY();
|
||||
} else if (align & ALIGN_BOTTOM) {
|
||||
y = bounds.y2();
|
||||
}
|
||||
|
||||
std::string toDraw = str;
|
||||
if (align & FLAG_WRAP_TEXT) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toDraw, str, rotated ? bounds.h : bounds.w);
|
||||
}
|
||||
|
||||
DrawString(target, toDraw.c_str(), x, y, color, align);
|
||||
}
|
||||
|
||||
void TextDrawerWin32::OncePerFrame() {
|
||||
frameCount_++;
|
||||
// If DPI changed (small-mode, future proper monitor DPI support), drop everything.
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
void MeasureString(const char *str, size_t len, float *w, float *h) override;
|
||||
void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override;
|
||||
void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override;
|
||||
// Use for housekeeping like throwing out old strings.
|
||||
void OncePerFrame() override;
|
||||
|
||||
@@ -40,4 +40,4 @@ protected:
|
||||
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cstring>
|
||||
#include "gfx_es2/draw_buffer.h"
|
||||
#include "util/text/utf8.h"
|
||||
#include "util/text/wrap_text.h"
|
||||
|
||||
@@ -75,39 +76,58 @@ std::string WordWrapper::Wrapped() {
|
||||
}
|
||||
|
||||
bool WordWrapper::WrapBeforeWord() {
|
||||
if (x_ + wordWidth_ > maxW_ && out_.size() > 0) {
|
||||
if (IsShy(out_[out_.size() - 1])) {
|
||||
// Soft hyphen, replace it with a real hyphen since we wrapped at it.
|
||||
// TODO: There's an edge case here where the hyphen might not fit.
|
||||
out_[out_.size() - 1] = '-';
|
||||
if (flags_ & FLAG_WRAP_TEXT) {
|
||||
if (x_ + wordWidth_ > maxW_ && !out_.empty()) {
|
||||
if (IsShy(out_[out_.size() - 1])) {
|
||||
// Soft hyphen, replace it with a real hyphen since we wrapped at it.
|
||||
// TODO: There's an edge case here where the hyphen might not fit.
|
||||
out_[out_.size() - 1] = '-';
|
||||
}
|
||||
out_ += "\n";
|
||||
lastLineStart_ = out_.size();
|
||||
x_ = 0.0f;
|
||||
forceEarlyWrap_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (flags_ & FLAG_ELLIPSIZE_TEXT) {
|
||||
if (x_ + wordWidth_ > maxW_) {
|
||||
if (!out_.empty() && IsSpace(out_[out_.size() - 1])) {
|
||||
out_[out_.size() - 1] = '.';
|
||||
out_ += "..";
|
||||
} else {
|
||||
out_ += "...";
|
||||
}
|
||||
x_ = maxW_;
|
||||
}
|
||||
out_ += "\n";
|
||||
lastLineStart_ = out_.size();
|
||||
x_ = 0.0f;
|
||||
forceEarlyWrap_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WordWrapper::AppendWord(int endIndex, bool addNewline) {
|
||||
int nextWordIndex = lastIndex_;
|
||||
int lastWordStartIndex = lastIndex_;
|
||||
if (WrapBeforeWord()) {
|
||||
// Advance to the first non-whitespace UTF-8 character in the following word (if any) to prevent starting the new line with a whitespace
|
||||
UTF8 utf8Word(str_, nextWordIndex);
|
||||
while (nextWordIndex < endIndex) {
|
||||
UTF8 utf8Word(str_, lastWordStartIndex);
|
||||
while (lastWordStartIndex < endIndex) {
|
||||
const uint32_t c = utf8Word.next();
|
||||
if (!IsSpace(c)) {
|
||||
break;
|
||||
}
|
||||
nextWordIndex = utf8Word.byteIndex();
|
||||
lastWordStartIndex = utf8Word.byteIndex();
|
||||
}
|
||||
}
|
||||
|
||||
// This will include the newline.
|
||||
out_.append(str_ + nextWordIndex, str_ + endIndex);
|
||||
if (addNewline) {
|
||||
if (x_ < maxW_) {
|
||||
out_.append(str_ + lastWordStartIndex, str_ + endIndex);
|
||||
} else {
|
||||
scanForNewline_ = true;
|
||||
}
|
||||
if (addNewline && (flags_ & FLAG_WRAP_TEXT)) {
|
||||
out_ += "\n";
|
||||
lastLineStart_ = out_.size();
|
||||
scanForNewline_ = false;
|
||||
} else {
|
||||
// We may have appended a newline - check.
|
||||
size_t pos = out_.substr(lastLineStart_).find_last_of("\n");
|
||||
@@ -129,6 +149,10 @@ void WordWrapper::Wrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags_ & FLAG_ELLIPSIZE_TEXT) {
|
||||
ellipsisWidth_ = MeasureWidth("...", 3);
|
||||
}
|
||||
|
||||
for (UTF8 utf(str_); !utf.end(); ) {
|
||||
int beforeIndex = utf.byteIndex();
|
||||
uint32_t c = utf.next();
|
||||
@@ -142,6 +166,13 @@ void WordWrapper::Wrap() {
|
||||
wordWidth_ = 0.0f;
|
||||
// We wrapped once, so stop forcing.
|
||||
forceEarlyWrap_ = false;
|
||||
scanForNewline_ = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scanForNewline_) {
|
||||
// We're discarding the rest of the characters until a newline (no wrapping.)
|
||||
lastIndex_ = afterIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -175,13 +206,33 @@ void WordWrapper::Wrap() {
|
||||
}
|
||||
// Now, add the word so far (without this latest character) and break.
|
||||
AppendWord(beforeIndex, true);
|
||||
x_ = 0.0f;
|
||||
if (lastLineStart_ != out_.size()) {
|
||||
x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_);
|
||||
} else {
|
||||
x_ = 0.0f;
|
||||
}
|
||||
wordWidth_ = 0.0f;
|
||||
forceEarlyWrap_ = false;
|
||||
// The current character will be handled as part of the next word.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((flags_ & FLAG_ELLIPSIZE_TEXT) && wordWidth_ > 0.0f && x_ + newWordWidth + ellipsisWidth_ > maxW_) {
|
||||
if ((flags_ & FLAG_WRAP_TEXT) == 0) {
|
||||
// Now, add the word so far (without this latest character) and show the ellipsis.
|
||||
AppendWord(beforeIndex, true);
|
||||
if (lastLineStart_ != out_.size()) {
|
||||
x_ = MeasureWidth(out_.c_str() + lastLineStart_, out_.size() - lastLineStart_);
|
||||
} else {
|
||||
x_ = 0.0f;
|
||||
}
|
||||
wordWidth_ = 0.0f;
|
||||
forceEarlyWrap_ = false;
|
||||
// The current character will be handled as part of the next word.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wordWidth_ = newWordWidth;
|
||||
|
||||
// Is this the end of a word via punctuation / CJK?
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
class WordWrapper {
|
||||
public:
|
||||
WordWrapper(const char *str, float maxW)
|
||||
: str_(str), maxW_(maxW) {
|
||||
WordWrapper(const char *str, float maxW, int flags)
|
||||
: str_(str), maxW_(maxW), flags_(flags) {
|
||||
}
|
||||
|
||||
std::string Wrapped();
|
||||
@@ -23,7 +23,9 @@ protected:
|
||||
|
||||
const char *const str_;
|
||||
const float maxW_;
|
||||
const int flags_;
|
||||
std::string out_;
|
||||
|
||||
// Index of last output / start of current word.
|
||||
int lastIndex_ = 0;
|
||||
// Index of last line start.
|
||||
@@ -32,6 +34,10 @@ protected:
|
||||
float x_ = 0.0f;
|
||||
// Most recent width of word since last index.
|
||||
float wordWidth_ = 0.0f;
|
||||
// Width of "..." when flag is set, zero otherwise.
|
||||
float ellipsisWidth_ = 0.0f;
|
||||
// Force the next word to cut partially and wrap.
|
||||
bool forceEarlyWrap_ = false;
|
||||
// Skip all characters until the next newline.
|
||||
bool scanForNewline_ = false;
|
||||
};
|
||||
Reference in new issue
Block a user