// 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 #include #include #include #include #include #include #include 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 images() { std::vector 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 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 out(static_cast(bits), static_cast(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 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 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; }