Owner-authorized source migration: 31 helper-only translation units were merged into their callers or existing inline headers; both build manifests updated. Includes the preserved source payload, Lua configuration matching wowdecomp, and exact malloca expansion without diagnostic pragmas. 449 native units compiled locally and 449 staged units compiled by the pinned server toolchain. No previously byte-matched function regressed. Compiler mapping diagnostics remain recorded as needs_cleanup, not accepted or peer-confirmed. Existing independent review records are preserved. README.md is intentionally unchanged.
327 lines
8.5 KiB
C++
327 lines
8.5 KiB
C++
#include "Render/DeviceContext/DeviceContext.h"
|
|
#include "Render/Text/FontCache.h"
|
|
#include "Render/Text/TextContext.h"
|
|
|
|
#include <algorithm>
|
|
#include <bit>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <malloc.h>
|
|
|
|
namespace {
|
|
// STATIC: 0x01311F30
|
|
// GLOBAL: gl_21912
|
|
constexpr unsigned charsets[20] = {HANGUL_CHARSET, ANSI_CHARSET, SHIFTJIS_CHARSET, GB2312_CHARSET, CHINESEBIG5_CHARSET, THAI_CHARSET,
|
|
TURKISH_CHARSET, ANSI_CHARSET, ANSI_CHARSET, RUSSIAN_CHARSET, VIETNAMESE_CHARSET, ARABIC_CHARSET};
|
|
|
|
// INLINED
|
|
unsigned Hex(unsigned char value) {
|
|
if (value >= '0' && value <= '9') {
|
|
return value - '0';
|
|
}
|
|
|
|
if (value >= 'a' && value <= 'f') {
|
|
return value - 'a' + 10;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// INLINED
|
|
bool ValidHex(unsigned char value) {
|
|
return (value >= '0' && value <= '9') || (value >= 'a' && value <= 'f') || (value >= 'A' && value <= 'F');
|
|
}
|
|
|
|
// INLINED
|
|
unsigned InlineCharset(const char* marker) {
|
|
return 16 * Hex(static_cast<unsigned char>(marker[1])) + Hex(static_cast<unsigned char>(marker[2]));
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x0054D4B0
|
|
// FUNCTION: fn_01934
|
|
TextContext::TextContext(CDC& surface, FontCache& fonts, const TextLocale& locale) : m_dc(nullptr), m_surface(&surface), m_fonts(&fonts), m_locale(&locale) {
|
|
if (surface.GetDC(&this->m_dc)) {
|
|
this->m_previous = GetCurrentObject(this->m_dc, OBJ_FONT);
|
|
SetBkMode(this->m_dc, TRANSPARENT);
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x0054D7E0
|
|
// FUNCTION: fn_01939
|
|
TextContext::~TextContext() {
|
|
if (this->m_surface && this->m_dc) {
|
|
SelectObject(this->m_dc, this->m_previous);
|
|
this->m_surface->ReleaseDC(this->m_dc);
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x0054FFE0
|
|
// FUNCTION: fn_01966
|
|
void TextContext::SetFont(int family, int height, unsigned char bold, unsigned char italic) {
|
|
if (family == 0) {
|
|
switch (this->m_locale->language) {
|
|
case 1:
|
|
if (this->m_locale->service == 6) {
|
|
family = 8;
|
|
height = (height + 2);
|
|
} else {
|
|
height = (height + 3);
|
|
if (this->m_locale->service == 14) {
|
|
family = 9;
|
|
} else {
|
|
family = 1;
|
|
if (this->m_locale->serverLanguage && *this->m_locale->serverLanguage == 31) {
|
|
family = 6;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
family = 2;
|
|
break;
|
|
case 3:
|
|
family = 3;
|
|
break;
|
|
case 4:
|
|
family = 4;
|
|
break;
|
|
case 5:
|
|
family = 5;
|
|
height = (height + 3);
|
|
break;
|
|
case 6:
|
|
case 8:
|
|
family = 7;
|
|
height = (height + 3);
|
|
if (this->m_locale->service == 12) {
|
|
height = (height - 2);
|
|
} else if (this->m_locale->service == 18) {
|
|
if (*this->m_locale->serverLanguage == 31) {
|
|
family = 6;
|
|
} else {
|
|
height = (height - 2);
|
|
}
|
|
}
|
|
break;
|
|
case 7:
|
|
family = 10;
|
|
height = (height + 3);
|
|
break;
|
|
case 12:
|
|
family = 11;
|
|
height = (height + 3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->m_family = family;
|
|
this->m_height = height;
|
|
this->m_charset = this->m_locale->language == 0 && family >= 20 ? charsets[0] : charsets[family];
|
|
this->m_bold = bold;
|
|
this->m_italic = italic;
|
|
}
|
|
|
|
// OFFSET: 0x00550170
|
|
// FUNCTION: fn_01969
|
|
void TextContext::SetColor(COLORREF color) {
|
|
SetTextColor(this->m_dc, color);
|
|
}
|
|
|
|
// OFFSET: 0x00550130
|
|
// OFFSET: 0x00550150
|
|
void TextContext::SetBackground(COLORREF color, int mode) {
|
|
SetBkColor(this->m_dc, color);
|
|
SetBkMode(this->m_dc, mode);
|
|
}
|
|
|
|
// OFFSET: 0x00ACD9B0
|
|
// FUNCTION: fn_17772
|
|
unsigned TextContext::CodePage(unsigned charset) {
|
|
switch (charset) {
|
|
case SHIFTJIS_CHARSET:
|
|
return 932;
|
|
case HANGUL_CHARSET:
|
|
return 949;
|
|
case GB2312_CHARSET:
|
|
return 936;
|
|
case CHINESEBIG5_CHARSET:
|
|
return 950;
|
|
case GREEK_CHARSET:
|
|
return 1253;
|
|
case TURKISH_CHARSET:
|
|
return 1254;
|
|
case VIETNAMESE_CHARSET:
|
|
return 1258;
|
|
case HEBREW_CHARSET:
|
|
return 1255;
|
|
case ARABIC_CHARSET:
|
|
return 1256;
|
|
case BALTIC_CHARSET:
|
|
return 1257;
|
|
case RUSSIAN_CHARSET:
|
|
return 1251;
|
|
case THAI_CHARSET:
|
|
return 874;
|
|
case EASTEUROPE_CHARSET:
|
|
return 1250;
|
|
default:
|
|
return 1252;
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x0054F2E0
|
|
// FUNCTION: fn_01957
|
|
unsigned TextContext::Marker(const char* text, unsigned bytes, bool enabled) {
|
|
if (!enabled) {
|
|
return bytes;
|
|
}
|
|
|
|
const unsigned limit = std::min<unsigned>(static_cast<unsigned>(std::strlen(text)), bytes);
|
|
if (std::bit_cast<std::int32_t>(limit - 2) <= 0) {
|
|
return bytes;
|
|
}
|
|
|
|
for (unsigned i = 0; i < limit - 2; ++i) {
|
|
if (text[i] == '|' && ValidHex(static_cast<unsigned char>(text[i + 1])) && ValidHex(static_cast<unsigned char>(text[i + 2]))) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
// OFFSET: 0x00550520
|
|
// FUNCTION: fn_01983
|
|
SIZE TextContext::MeasurePart(const char* text, int bytes, unsigned charset) {
|
|
SIZE size = {};
|
|
if (!text || bytes <= 0) {
|
|
return size;
|
|
}
|
|
|
|
const unsigned extent = 2u * static_cast<unsigned>(bytes);
|
|
const unsigned allocation = extent < extent + 8 ? extent + 8 : 0;
|
|
if (!allocation) {
|
|
return size;
|
|
}
|
|
|
|
void* memory = allocation > 0x400 ? std::malloc(allocation) : _alloca(allocation);
|
|
if (!memory) {
|
|
return size;
|
|
}
|
|
|
|
auto* wide = reinterpret_cast<WCHAR*>(static_cast<char*>(memory) + 8);
|
|
const int count = MultiByteToWideChar(CodePage(charset), 0, text, bytes, wide, static_cast<int>(extent));
|
|
SelectObject(this->m_dc, this->m_fonts->Get(this->m_family, this->m_height, charset, this->m_bold, this->m_italic));
|
|
GetTextExtentPoint32W(this->m_dc, wide, count, &size);
|
|
|
|
if (this->m_italic == 1) {
|
|
int width = 0;
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
ABC abc;
|
|
GetCharABCWidthsW(this->m_dc, wide[i], wide[i], &abc);
|
|
width = (width + (abc.abcA + abc.abcB));
|
|
}
|
|
|
|
size.cx = width;
|
|
}
|
|
|
|
if (allocation > 0x400) {
|
|
std::free(memory);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
// OFFSET: 0x00550680
|
|
// FUNCTION: fn_01984
|
|
void TextContext::DrawPart(int x, int y, const char* text, int bytes, unsigned charset) {
|
|
if (!text || bytes <= 0) {
|
|
return;
|
|
}
|
|
|
|
const unsigned extent = 2u * static_cast<unsigned>(bytes);
|
|
const unsigned allocation = extent < extent + 8 ? extent + 8 : 0;
|
|
if (!allocation) {
|
|
return;
|
|
}
|
|
|
|
void* memory = allocation > 0x400 ? std::malloc(allocation) : _alloca(allocation);
|
|
if (!memory) {
|
|
return;
|
|
}
|
|
|
|
auto* wide = reinterpret_cast<WCHAR*>(static_cast<char*>(memory) + 8);
|
|
|
|
const int count = MultiByteToWideChar(CodePage(this->m_charset), 0, text, bytes, wide, static_cast<int>(extent));
|
|
SelectObject(this->m_dc, this->m_fonts->Get(this->m_family, this->m_height, charset, this->m_bold, this->m_italic));
|
|
TextOutW(this->m_dc, x, y, wide, count);
|
|
|
|
if (allocation > 0x400) {
|
|
std::free(memory);
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x0054FD00
|
|
// FUNCTION: fn_01961
|
|
SIZE TextContext::Measure(const char* text, int bytes) {
|
|
const int first = Marker(text, bytes, this->m_locale->inlineCharset);
|
|
SIZE result = this->MeasurePart(text, first, this->m_charset);
|
|
if (first < bytes) {
|
|
SIZE second = this->MeasurePart(text + first + 3, bytes - first - 3, InlineCharset(text + first));
|
|
result.cx = (result.cx + second.cx);
|
|
result.cy = std::max(result.cy, second.cy);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// OFFSET: 0x00550190
|
|
// FUNCTION: fn_01970
|
|
void TextContext::DrawPair(int x, int y, const char* text, int bytes) {
|
|
const int first = Marker(text, bytes, this->m_locale->inlineCharset);
|
|
this->DrawPart(x, y, text, first, this->m_charset);
|
|
if (first < bytes) {
|
|
const SIZE size = this->MeasurePart(text, first, this->m_charset);
|
|
this->DrawPart((x + size.cx), y, text + first + 3, bytes - first - 3, InlineCharset(text + first));
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x00550220
|
|
// FUNCTION: fn_01971
|
|
void TextContext::Draw(int x, int y, const char* text, int bytes) {
|
|
if (!this->m_locale->inlineCharset) {
|
|
this->DrawPair(x, y, text, bytes);
|
|
return;
|
|
}
|
|
|
|
const char* current = text;
|
|
int scanned = 0, remaining = bytes;
|
|
|
|
while (scanned < bytes) {
|
|
scanned = (scanned + Marker(text + scanned, remaining, true));
|
|
if (scanned >= bytes) {
|
|
this->DrawPair(x, y, current, scanned);
|
|
return;
|
|
}
|
|
|
|
if (remaining <= 3) {
|
|
return;
|
|
}
|
|
|
|
const int segment = (Marker(text + scanned + 3, remaining - 3, true) + 3);
|
|
this->DrawPair(x, y, current, segment);
|
|
const int first = Marker(current, segment, true);
|
|
int width = this->MeasurePart(current, first, this->m_charset).cx;
|
|
|
|
if (first < segment) {
|
|
width = (width + this->MeasurePart(current + first + 3, segment - first - 3, InlineCharset(current + first)).cx);
|
|
}
|
|
|
|
current += segment;
|
|
scanned = (scanned + segment);
|
|
x = (x + width);
|
|
remaining -= segment;
|
|
}
|
|
}
|