TextOutA / GetTextExtentPoint32A in the Win32 GDI shim always decoded with CP_ACP (1252), so the server notices drawn by uiTip.TipBoard / BigBoard (CTextBar, a GB2312_CHARSET font in the 936 locale) showed GBK bytes as Latin-1 mojibake. Windows GDI decodes ANSI calls with the code page of the selected font's charset; the shim now keeps lfCharSet on the font and does the same. port.text_bar checks TextOutA of GBK bytes against TextOutW of the Unicode text, and the extent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
228 lines
7.4 KiB
C++
228 lines
7.4 KiB
C++
// EterLib CTextBar -> CDibBar -> CBlockTexture (40250 uiTip.TipBoard / BigBoard: grp.CreateTextBar):
|
|
// GDI text into the DIB over platform/Win32Gdi.cpp, the DIB split into power-of-two device textures,
|
|
// and each block's quad recorded as a UI image (the draw 40250 sends through the device).
|
|
//
|
|
// Needs a system Arial or Tahoma; exit 77 (ctest SKIP) without one, unless MT_ASSETS_STRICT=1.
|
|
#include "EterLib/StdAfx.h"
|
|
#include "EterLib/TextBar.h"
|
|
#include "EterLib/GrpBase.h"
|
|
#include "EterLib/Util.h"
|
|
#include "../../src/platform/EterLib/UIRenderCommands.h"
|
|
#include "../../src/platform/EterLib/RecordingDevice.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <unistd.h>
|
|
|
|
static int g_failures = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
|
|
++g_failures; \
|
|
} \
|
|
} while (0)
|
|
|
|
struct DeviceAccess : CGraphicBase
|
|
{
|
|
static void Set(IDirect3DDevice8* device) { ms_lpd3dDevice = device; }
|
|
};
|
|
|
|
static std::vector<UIRenderCommand> images()
|
|
{
|
|
std::vector<UIRenderCommand> out;
|
|
for (const auto& command : UIRenderCommands())
|
|
if (command.kind == UIRenderCommand::Image)
|
|
out.push_back(command);
|
|
return out;
|
|
}
|
|
|
|
static int opaque_pixels(const std::string& name, std::uint32_t* sample)
|
|
{
|
|
UIMemoryTexture texture;
|
|
if (!UIRenderMemoryTexture(name, &texture))
|
|
return -1;
|
|
int count = 0;
|
|
for (std::uint32_t px : texture.argb)
|
|
{
|
|
if ((px >> 24) == 0xFF)
|
|
{
|
|
++count;
|
|
*sample = px;
|
|
}
|
|
else if (px >> 24)
|
|
return -2; // InvalidateRect writes either 0 or src | 0xff000000
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// Renders text into a 64 x 20 DIB with a 12 px font of the given charset (A or W call); returns the bits.
|
|
static std::vector<std::uint32_t> gdi_text(BYTE charset, const char* ansi, const wchar_t* wide)
|
|
{
|
|
BITMAPINFO info = {};
|
|
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
info.bmiHeader.biWidth = 64;
|
|
info.bmiHeader.biHeight = -20;
|
|
info.bmiHeader.biPlanes = 1;
|
|
info.bmiHeader.biBitCount = 32;
|
|
void* bits = NULL;
|
|
HDC dc = CreateCompatibleDC(NULL);
|
|
HBITMAP bmp = CreateDIBSection(dc, &info, DIB_RGB_COLORS, &bits, NULL, 0);
|
|
SelectObject(dc, bmp);
|
|
LOGFONT lf = {};
|
|
lf.lfHeight = 12;
|
|
lf.lfCharSet = charset;
|
|
lf.lfQuality = ANTIALIASED_QUALITY;
|
|
std::strcpy(lf.lfFaceName, "Arial");
|
|
HFONT font = CreateFontIndirect(&lf);
|
|
SelectObject(dc, font);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, RGB(255, 255, 255));
|
|
if (ansi)
|
|
TextOutA(dc, 0, 0, ansi, int(std::strlen(ansi)));
|
|
else
|
|
TextOutW(dc, 0, 0, wide, int(std::wcslen(wide)));
|
|
std::vector<std::uint32_t> out(static_cast<std::uint32_t*>(bits), static_cast<std::uint32_t*>(bits) + 64 * 20);
|
|
DeleteObject(font);
|
|
DeleteDC(dc);
|
|
DeleteObject(bmp);
|
|
return out;
|
|
}
|
|
|
|
static bool near(float a, float b)
|
|
{
|
|
return std::fabs(a - b) < 1e-3f;
|
|
}
|
|
|
|
int main()
|
|
{
|
|
const bool is_strict = std::getenv("MT_ASSETS_STRICT") && std::string(std::getenv("MT_ASSETS_STRICT")) == "1";
|
|
if (access("/System/Library/Fonts/Supplemental/Arial.ttf", 0) != 0 && access("/System/Library/Fonts/Supplemental/Tahoma.ttf", 0) != 0)
|
|
{
|
|
std::fprintf(stderr, "port_text_bar_test: no system Arial/Tahoma\n");
|
|
return is_strict ? 1 : 77;
|
|
}
|
|
SetDefaultCodePage(1252);
|
|
IDirect3DDevice8* device = MtCreateRecordingDevice(800, 600);
|
|
DeviceAccess::Set(device);
|
|
|
|
{
|
|
// uiTip.TipBoard: grp.CreateTextBar(370, 300) -> CTextBar(12, false).
|
|
CTextBar bar(12, false);
|
|
CHECK(bar.Create(NULL, 370, 300));
|
|
|
|
SIZE size = {};
|
|
bar.GetTextExtent("Notice", &size);
|
|
CHECK(size.cx > 20 && size.cx < 60 && size.cy >= 12 && size.cy <= 16);
|
|
|
|
bar.TextOut(0, 0, "Notice");
|
|
UIRenderBeginFrame();
|
|
bar.Render(10, 20);
|
|
std::vector<UIRenderCommand> blocks = images();
|
|
// 370 x 300 = 256 + 114 by 256 + 44: blocks of 256x256, 128x256, 256x64, 128x64 texels.
|
|
CHECK(blocks.size() == 4);
|
|
if (blocks.size() == 4)
|
|
{
|
|
CHECK(blocks[0].text.compare(0, 8, "mem:cpu_") == 0);
|
|
// Unclipped: the quad is the full texture from the block origin, shifted +0.5 back.
|
|
CHECK(near(blocks[0].qx[0], 10.0f) && near(blocks[0].qy[0], 20.0f));
|
|
CHECK(near(blocks[0].qx[3], 266.0f) && near(blocks[0].qy[3], 276.0f));
|
|
CHECK(near(blocks[1].qx[0], 266.0f) && near(blocks[1].qx[3], 394.0f));
|
|
CHECK(near(blocks[2].qy[0], 276.0f) && near(blocks[2].qy[3], 340.0f));
|
|
CHECK(blocks[0].su == 0.0f && blocks[0].ev == 1.0f);
|
|
|
|
std::uint32_t sample = 0;
|
|
const int lit = opaque_pixels(blocks[0].text, &sample);
|
|
CHECK(lit > 20);
|
|
// CGraphicDib::Create sets a white text colour; anti-aliased edges stay grey.
|
|
CHECK(((sample >> 16) & 0xFF) > 0 && ((sample >> 16) & 0xFF) == (sample & 0xFF));
|
|
std::uint32_t unused = 0;
|
|
CHECK(opaque_pixels(blocks[1].text, &unused) == 0);
|
|
}
|
|
|
|
// uiTip.BigBoard colour; ClearBar zeroes the DIB and every block.
|
|
bar.SetTextColor(242, 231, 193);
|
|
bar.TextOut(0, 0, "Notice");
|
|
UIRenderBeginFrame();
|
|
bar.Render(0, 0);
|
|
blocks = images();
|
|
if (!blocks.empty())
|
|
{
|
|
std::uint32_t sample = 0;
|
|
CHECK(opaque_pixels(blocks[0].text, &sample) > 20);
|
|
}
|
|
bar.ClearBar();
|
|
UIRenderBeginFrame();
|
|
bar.Render(0, 0);
|
|
blocks = images();
|
|
if (!blocks.empty())
|
|
{
|
|
std::uint32_t sample = 0;
|
|
CHECK(opaque_pixels(blocks[0].text, &sample) == 0);
|
|
}
|
|
|
|
// TipBoard's SetClipRect(0, y, screen width, y + 18) at textBar position (3, 5 - curPos):
|
|
// the lower blocks start past the clip bottom and are skipped, the upper ones are cut to 18 rows.
|
|
RECT clip = {0, 20, 800, 38};
|
|
bar.SetClipRect(clip);
|
|
UIRenderBeginFrame();
|
|
bar.Render(3, 5);
|
|
blocks = images();
|
|
CHECK(blocks.size() == 2);
|
|
if (blocks.size() == 2)
|
|
{
|
|
CHECK(near(blocks[0].qy[0], 20.0f) && near(blocks[0].qy[3], 38.0f));
|
|
CHECK(near(blocks[0].sv, 15.0f / 256.0f) && near(blocks[0].ev, 1.0f - 223.0f / 256.0f));
|
|
}
|
|
}
|
|
|
|
{
|
|
// uiTip.BigBoard: grp.CreateBigTextBar(500, 300, 18) -> CTextBar(18, true).
|
|
CTextBar big(18, true);
|
|
CHECK(big.Create(NULL, 500, 300));
|
|
SIZE small = {}, large = {};
|
|
CTextBar normal(12, false);
|
|
CHECK(normal.Create(NULL, 64, 64));
|
|
normal.GetTextExtent("Notice", &small);
|
|
big.GetTextExtent("Notice", &large);
|
|
CHECK(large.cx > small.cx && large.cy > small.cy);
|
|
UIRenderBeginFrame();
|
|
normal.Render(0, 0);
|
|
CHECK(images().size() == 1); // 64 x 64 fits in one block (count 0, rest 64)
|
|
}
|
|
|
|
{
|
|
// GDI's ANSI calls decode with the selected font's charset (CTextBar: GetCharsetFromCodePage of the
|
|
// locale), so a 936 server notice in GBK draws as the CJK text, not as its bytes read as CP 1252.
|
|
const char gbk[] = "\xB9\xAB\xB8\xE6"; // "公告"
|
|
const std::vector<std::uint32_t> wide = gdi_text(GB2312_CHARSET, NULL, L"\u516C\u544A");
|
|
int lit = 0;
|
|
for (std::uint32_t px : wide)
|
|
lit += px != 0;
|
|
CHECK(lit > 20);
|
|
CHECK(gdi_text(GB2312_CHARSET, gbk, NULL) == wide);
|
|
CHECK(gdi_text(ANSI_CHARSET, gbk, NULL) != wide);
|
|
SIZE size = {};
|
|
SetDefaultCodePage(936);
|
|
CTextBar bar(12, false);
|
|
CHECK(bar.Create(NULL, 64, 20));
|
|
bar.GetTextExtent(gbk, &size);
|
|
SIZE one = {};
|
|
bar.GetTextExtent("\xB9\xAB", &one);
|
|
CHECK(one.cx > 0 && size.cx == 2 * one.cx); // two full-width glyphs, not four Latin-1 ones
|
|
SetDefaultCodePage(1252);
|
|
}
|
|
|
|
DeviceAccess::Set(NULL);
|
|
device->Release();
|
|
if (!g_failures)
|
|
std::printf("ok\n");
|
|
return g_failures ? 1 : 0;
|
|
}
|