Merge pull request #12702 from unknownbrackets/ppge-text

PPGe: Use TextDrawer for save UI if available
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2020-03-11 17:26:50 +01:00
commit d0e2aa3a4f
15 files changed
+714 -341

No files matched your search

+2
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+7 -5
View File
@@ -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();
}
+1
View File
@@ -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 {
+39 -2
View File
@@ -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)
+15 -8
View File
@@ -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_;
};
};
+76 -70
View File
@@ -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.
+1 -1
View File
@@ -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;
+102 -71
View File
@@ -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.
+4 -5
View File
@@ -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
+116 -110
View File
@@ -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.
+2 -2
View File
@@ -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
+68 -17
View File
@@ -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?
+8 -2
View File
@@ -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;
};