diff --git a/audit/history.jsonl b/audit/history.jsonl index 4bbf1682..70ea8c37 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -614,3 +614,6 @@ {"date": "2026-09-30", "unit": "EterLib/GrpMath.cpp", "action": "all PORTED verbatim", "evidence": "port.grp_math"} {"date": "2026-09-30", "unit": "EterLib/GrpColor.cpp", "action": "all PORTED verbatim", "evidence": "port.grp_math"} {"date": "2026-09-30", "unit": "EterLib/GrpColorInstance.cpp", "action": "all PORTED verbatim", "evidence": "port.grp_math"} +{"date": "2026-09-30", "unit": "EterLib/TextBar.cpp", "action": "all PORTED verbatim; GetTextExtentPoint32A added to the GDI shim", "evidence": "port.text_bar"} +{"date": "2026-09-30", "unit": "EterLib/DibBar.cpp", "action": "all PORTED verbatim", "evidence": "port.text_bar"} +{"date": "2026-09-30", "unit": "EterLib/BlockTexture.cpp", "action": "Render ADAPTED (UI image of the CPU texture), rest PORTED verbatim", "evidence": "port.text_bar"} diff --git a/audit/port-map/EterLib/BlockTexture.cpp.json b/audit/port-map/EterLib/BlockTexture.cpp.json new file mode 100644 index 00000000..fec09316 --- /dev/null +++ b/audit/port-map/EterLib/BlockTexture.cpp.json @@ -0,0 +1,68 @@ +{ + "reference": "EterLib/BlockTexture.cpp", + "reference_sha256": "791df4fed556b4aad015ede57d85763393fefd48cc505584198865f84c7a1b99", + "priority": "P4", + "contracts": [], + "functions": { + "CBlockTexture::SetClipRect": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::SetClipRect" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (CreateTexture/LockRect on the recording device's CPU texture)." + }, + "CBlockTexture::Render": { + "status": "ADAPTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::Render" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "40250: SetPDTStream + DEFAULT_IB_FILL_RECT + stage-0 m_lpd3dTexture + DrawIndexedPrimitive in the UI pass. Port: the same clipped corners/UVs recorded as a UI image of the device CPU texture (UIRenderAddImageNamed). Invariant: identical quad, UVs and texels; clipped-out blocks draw nothing." + }, + "CBlockTexture::InvalidateRect": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::InvalidateRect" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (CreateTexture/LockRect on the recording device's CPU texture)." + }, + "CBlockTexture::Create": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::Create" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (CreateTexture/LockRect on the recording device's CPU texture)." + }, + "CBlockTexture::CBlockTexture": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::CBlockTexture" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (CreateTexture/LockRect on the recording device's CPU texture)." + }, + "CBlockTexture::~CBlockTexture": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/BlockTexture.cpp:CBlockTexture::~CBlockTexture" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (CreateTexture/LockRect on the recording device's CPU texture)." + } + } +} diff --git a/audit/port-map/EterLib/DibBar.cpp.json b/audit/port-map/EterLib/DibBar.cpp.json new file mode 100644 index 00000000..897a362b --- /dev/null +++ b/audit/port-map/EterLib/DibBar.cpp.json @@ -0,0 +1,118 @@ +{ + "reference": "EterLib/DibBar.cpp", + "reference_sha256": "5523feed6b35f7e13d1af6fb3e517db52a2ba88a5b8887a2bf5a35bbff91aec2", + "priority": "P4", + "contracts": [], + "functions": { + "CDibBar::Invalidate": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::Invalidate" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::SetClipRect": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::SetClipRect" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::ClearBar": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::ClearBar" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::Render": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::Render" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::__NearTextureSize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::__NearTextureSize" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::__DivideTextureSize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::__DivideTextureSize" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::__BuildTextureBlock": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::__BuildTextureBlock" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::__BuildTextureBlockList": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::__BuildTextureBlockList" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::Create": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::Create" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::CDibBar": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::CDibBar" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + }, + "CDibBar::~CDibBar": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/DibBar.cpp:CDibBar::~CDibBar" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 (RECT initialisers cast to the 32-bit LONG)." + } + } +} diff --git a/audit/port-map/EterLib/TextBar.cpp.json b/audit/port-map/EterLib/TextBar.cpp.json new file mode 100644 index 00000000..b420a8d1 --- /dev/null +++ b/audit/port-map/EterLib/TextBar.cpp.json @@ -0,0 +1,78 @@ +{ + "reference": "EterLib/TextBar.cpp", + "reference_sha256": "f9f6d12d01e94d76150cb3b50617c10bdf22fcd1c9075ab4d6f40e0b429f7de3", + "priority": "P4", + "contracts": [], + "functions": { + "CTextBar::__SetFont": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::__SetFont" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::SetTextColor": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::SetTextColor" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::GetTextExtent": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::GetTextExtent" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::TextOut": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::TextOut" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::OnCreate": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::OnCreate" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::CTextBar": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::CTextBar" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + }, + "CTextBar::~CTextBar": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/TextBar.cpp:CTextBar::~CTextBar" + ], + "test": [ + "tests/port/port_text_bar_test.cpp" + ], + "note": "Verbatim 40250 over the Win32Gdi FreeType GDI shim (CreateFontIndirect/SelectObject/SetTextColor/GetTextExtentPoint32A). Live: uiTip.TipBoard/BigBoard via grp.CreateTextBar/CreateBigTextBar." + } + } +} diff --git a/src/platform/EterLib/BlockTexture.cpp b/src/platform/EterLib/BlockTexture.cpp new file mode 100644 index 00000000..36469118 --- /dev/null +++ b/src/platform/EterLib/BlockTexture.cpp @@ -0,0 +1,185 @@ +// 40250 EterLib/BlockTexture.cpp. Verbatim except Render: the texture is the device's CPU texture +// (CreateTexture/LockRect as 40250), and the quad 40250 draws with SetPDTStream + DrawIndexedPrimitive +// in the UI pass is recorded as a UI image of that texture with the same corners and UVs. +#include "EterLib/StdAfx.h" +#include "EterLib/BlockTexture.h" +#include "EterLib/GrpBase.h" +#include "EterLib/GrpDIB.h" +#include "EterBase/Stl.h" +#include "EterLib/StateManager.h" +#include "UIRenderCommands.h" +void CBlockTexture::SetClipRect(const RECT & c_rRect) +{ + m_bClipEnable = TRUE; + m_clipRect = c_rRect; +} + +void CBlockTexture::Render(int ix, int iy) +{ + int isx = ix + m_rect.left; + int isy = iy + m_rect.top; + int iex = ix + m_rect.left + m_dwWidth; + int iey = iy + m_rect.top + m_dwHeight; + + float su = 0.0f; + float sv = 0.0f; + float eu = 1.0f; + float ev = 1.0f; + + if (m_bClipEnable) + { + if (isx > m_clipRect.right) + return; + if (iex < m_clipRect.left) + return; + + if (isy > m_clipRect.bottom) + return; + if (iey < m_clipRect.top) + return; + + if (m_clipRect.left > isx) + { + int idx = m_clipRect.left - isx; + isx += idx; + su += float(idx) / float(m_dwWidth); + } + if (iex > m_clipRect.right) + { + int idx = iex - m_clipRect.right; + iex -= idx; + eu -= float(idx) / float(m_dwWidth); + } + + if (m_clipRect.top > isy) + { + int idy = m_clipRect.top - isy; + isy += idy; + sv += float(idy) / float(m_dwHeight); + } + if (iey > m_clipRect.bottom) + { + int idy = iey - m_clipRect.bottom; + iey -= idy; + ev -= float(idy) / float(m_dwHeight); + } + } + + TPDTVertex vertices[4]; + vertices[0].position.x = isx - 0.5f; + vertices[0].position.y = isy - 0.5f; + vertices[0].position.z = 0.0f; + vertices[0].texCoord = TTextureCoordinate(su, sv); + vertices[0].diffuse = 0xffffffff; + + vertices[1].position.x = iex - 0.5f; + vertices[1].position.y = isy - 0.5f; + vertices[1].position.z = 0.0f; + vertices[1].texCoord = TTextureCoordinate(eu, sv); + vertices[1].diffuse = 0xffffffff; + + vertices[2].position.x = isx - 0.5f; + vertices[2].position.y = iey - 0.5f; + vertices[2].position.z = 0.0f; + vertices[2].texCoord = TTextureCoordinate(su, ev); + vertices[2].diffuse = 0xffffffff; + + vertices[3].position.x = iex - 0.5f; + vertices[3].position.y = iey - 0.5f; + vertices[3].position.z = 0.0f; + vertices[3].texCoord = TTextureCoordinate(eu, ev); + vertices[3].diffuse = 0xffffffff; + + // PORT: 40250 sets the stream, stage-0 texture and FVF and draws the fill-rect triangle list. + float x[4], y[4]; + for (int i = 0; i < 4; ++i) + { + x[i] = vertices[i].position.x; + y[i] = vertices[i].position.y; + } + UIRenderAddImageNamed(UIRenderTextureNameFromHandle(m_lpd3dTexture), x, y, su, sv, eu, ev, vertices[0].diffuse); +} + +void CBlockTexture::InvalidateRect(const RECT & c_rsrcRect) +{ + RECT dstRect = m_rect; + if (c_rsrcRect.right < dstRect.left || + c_rsrcRect.left > dstRect.right || + c_rsrcRect.bottom < dstRect.top || + c_rsrcRect.top > dstRect.bottom) + { + Tracef("InvalidateRect() - Strange rect"); + return; + } + + + // DIBBAR_LONGSIZE_BUGFIX + const RECT clipRect = { + max(c_rsrcRect.left - dstRect.left, 0), + max(c_rsrcRect.top - dstRect.top, 0), + min(c_rsrcRect.right - dstRect.left, dstRect.right - dstRect.left), + min(c_rsrcRect.bottom - dstRect.top, dstRect.bottom - dstRect.top), + }; + // END_OF_DIBBAR_LONGSIZE_BUGFIX + + + DWORD * pdwSrc; + pdwSrc = (DWORD *)m_pDIB->GetPointer(); + pdwSrc += dstRect.left + dstRect.top*m_pDIB->GetWidth(); + + D3DLOCKED_RECT lockedRect; + if (FAILED(m_lpd3dTexture->LockRect(0, &lockedRect, &clipRect, 0))) + { + Tracef("InvalidateRect() - Failed to LockRect"); + return; + } + + int iclipWidth = clipRect.right - clipRect.left; + int iclipHeight = clipRect.bottom - clipRect.top; + DWORD * pdwDst = (DWORD *)lockedRect.pBits; + DWORD dwDstWidth = lockedRect.Pitch>>2; + DWORD dwSrcWidth = m_pDIB->GetWidth(); + for (int i = 0; i < iclipHeight; ++i) + { + for (int i = 0; i < iclipWidth; ++i) + { + if (pdwSrc[i]) + pdwDst[i] = pdwSrc[i] | 0xff000000; + else + pdwDst[i] = 0; + } + pdwDst += dwDstWidth; + pdwSrc += dwSrcWidth; + } + + m_lpd3dTexture->UnlockRect(0); +} + +bool CBlockTexture::Create(CGraphicDib * pDIB, const RECT & c_rRect, DWORD dwWidth, DWORD dwHeight) +{ + if (FAILED(ms_lpd3dDevice->CreateTexture(dwWidth, dwHeight, 0, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_lpd3dTexture))) + { + Tracef("Failed to create block texture %u, %u\n", dwWidth, dwHeight); + return false; + } + + m_pDIB = pDIB; + m_rect = c_rRect; + m_dwWidth = dwWidth; + m_dwHeight = dwHeight; + m_bClipEnable = FALSE; + + return true; +} + +CBlockTexture::CBlockTexture() +{ + m_pDIB = NULL; + m_lpd3dTexture = NULL; +} + +CBlockTexture::~CBlockTexture() +{ + safe_release(m_lpd3dTexture); + m_lpd3dTexture = NULL; +} diff --git a/src/platform/EterLib/DibBar.cpp b/src/platform/EterLib/DibBar.cpp index 810218cf..a1c5c197 100644 --- a/src/platform/EterLib/DibBar.cpp +++ b/src/platform/EterLib/DibBar.cpp @@ -1,64 +1,166 @@ -// Platform skeleton for EterLib/DibBar.h (40250 EterLib/DibBar.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/DibBar.cpp, verbatim (RECT initialisers cast to LONG). #include "EterLib/StdAfx.h" #include "EterLib/DibBar.h" +#include "EterLib/BlockTexture.h" -#include "../PlatformStub.h" +void CDibBar::Invalidate() +{ + // PORT: RECT holds the 32-bit Win32 LONG, so static_cast would narrow here. + RECT rect = { static_cast(0), static_cast(0), static_cast(m_dwWidth), static_cast(m_dwHeight)}; + + std::vector::iterator itor = m_kVec_pkBlockTexture.begin(); + for (; itor != m_kVec_pkBlockTexture.end(); ++itor) + { + CBlockTexture * pTexture = *itor; + pTexture->InvalidateRect(rect); + } +} + +void CDibBar::SetClipRect(const RECT & c_rRect) +{ + std::vector::iterator itor = m_kVec_pkBlockTexture.begin(); + for (; itor != m_kVec_pkBlockTexture.end(); ++itor) + { + CBlockTexture * pTexture = *itor; + assert(pTexture); + if (!pTexture) + continue; + pTexture->SetClipRect(c_rRect); + } +} + +void CDibBar::ClearBar() +{ + DWORD * pdwBuf = (DWORD *)m_dib.GetPointer(); + memset(pdwBuf, 0, m_dib.GetWidth()*m_dib.GetHeight()*4); + Invalidate(); +} + +void CDibBar::Render(int ix, int iy) +{ + std::vector::iterator itor = m_kVec_pkBlockTexture.begin(); + for (; itor != m_kVec_pkBlockTexture.end(); ++itor) + { + CBlockTexture * pTexture = *itor; + pTexture->Render(ix, iy); + } +} + +DWORD CDibBar::__NearTextureSize(DWORD dwSize) +{ + if ((dwSize & (dwSize-1)) == 0) + return dwSize; + + DWORD dwRet = 2; + while (dwRet < dwSize) + { + dwRet <<= 1; + } + + return dwRet; +} + +void CDibBar::__DivideTextureSize(DWORD dwSize, DWORD dwMax, DWORD * pdwxStep, DWORD * pdwxCount, DWORD * pdwxRest) +{ + if (dwSize would narrow here. + RECT posRect = { static_cast(dwxPos), static_cast(dwyPos), static_cast(dwxPos+dwImageWidth), static_cast(dwyPos+dwImageHeight)}; + + CBlockTexture * pBlockTexture = new CBlockTexture; + if (!pBlockTexture->Create(&m_dib, posRect, dwTextureWidth, dwTextureHeight)) + { + delete pBlockTexture; + return NULL; + } + + return pBlockTexture; +} + +void CDibBar::__BuildTextureBlockList(DWORD dwWidth, DWORD dwHeight, DWORD dwMax) +{ + DWORD dwxStep, dwyStep; + DWORD dwxCount, dwyCount; + DWORD dwxRest, dwyRest; + __DivideTextureSize(dwWidth, dwMax, &dwxStep, &dwxCount, &dwxRest); + __DivideTextureSize(dwHeight, dwMax, &dwyStep, &dwyCount, &dwyRest); + DWORD dwxTexRest = __NearTextureSize(dwxRest); + DWORD dwyTexRest = __NearTextureSize(dwyRest); + + for (DWORD y = 0; y < dwyCount; ++y) + { + for (DWORD x = 0; x < dwxCount; ++x) + { + CBlockTexture * pTexture = __BuildTextureBlock(x*dwxStep, y*dwyStep, + dwxStep, dwyStep, + dwMax, dwMax); + if (pTexture) + m_kVec_pkBlockTexture.push_back(pTexture); + } + + CBlockTexture * pTexture = __BuildTextureBlock(dwxCount*dwxStep, y*dwyStep, + dwxRest, dwyStep, + dwxTexRest, dwMax); + + if (pTexture) + m_kVec_pkBlockTexture.push_back(pTexture); + } + + for (DWORD x = 0; x < dwxCount; ++x) + { + CBlockTexture * pTexture = __BuildTextureBlock(x*dwxStep, dwyCount*dwyStep, + dwxStep, dwyRest, + dwMax, dwyTexRest); + if (pTexture) + m_kVec_pkBlockTexture.push_back(pTexture); + } + + if (dwxRest > 0 && dwyRest > 0) + { + CBlockTexture * pTexture = __BuildTextureBlock(dwxCount*dwxStep, dwyCount*dwyStep, + dwxRest, dwyRest, + dwxTexRest, dwyTexRest); + if (pTexture) + m_kVec_pkBlockTexture.push_back(pTexture); + } +} + +bool CDibBar::Create(HDC hdc, DWORD dwWidth, DWORD dwHeight) +{ + if (!m_dib.Create(hdc, dwWidth, dwHeight)) + { + Tracef(" Failed to create CDibBar\n"); + return false; + } + + m_dwWidth = dwWidth; + m_dwHeight = dwHeight; + + __BuildTextureBlockList(dwWidth, dwHeight); + OnCreate(); + + return true; +} CDibBar::CDibBar() { - MT_PLATFORM_STUB(); } - CDibBar::~CDibBar() { - MT_PLATFORM_STUB(); -} - -auto CDibBar::Create(HDC, DWORD, DWORD) -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CDibBar::Invalidate() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CDibBar::SetClipRect(const RECT &) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CDibBar::ClearBar() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CDibBar::Render(int, int) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CDibBar::__NearTextureSize(DWORD) -> DWORD -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CDibBar::__DivideTextureSize(DWORD, DWORD, DWORD *, DWORD *, DWORD *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CDibBar::__BuildTextureBlock(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD) -> CBlockTexture * -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CDibBar::__BuildTextureBlockList(DWORD, DWORD, DWORD) -> void -{ - MT_PLATFORM_STUB(); } diff --git a/src/platform/EterLib/TextBar.cpp b/src/platform/EterLib/TextBar.cpp index ac6b0bf4..fed60226 100644 --- a/src/platform/EterLib/TextBar.cpp +++ b/src/platform/EterLib/TextBar.cpp @@ -1,41 +1,77 @@ -// Platform skeleton for EterLib/TextBar.h (40250 EterLib/TextBar.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/TextBar.cpp, verbatim. #include "EterLib/StdAfx.h" #include "EterLib/TextBar.h" +#include "EterLib/Util.h" -#include "../PlatformStub.h" - -CTextBar::CTextBar(int, bool) +void CTextBar::__SetFont(int fontSize, bool isBold) { - MT_PLATFORM_STUB(); + int iCodePage = GetDefaultCodePage(); + + LOGFONT logFont; + + memset(&logFont, 0, sizeof(LOGFONT)); + + logFont.lfHeight = fontSize; + logFont.lfEscapement = 0; + logFont.lfOrientation = 0; + + if (isBold) + logFont.lfWeight = FW_BOLD; + else + logFont.lfWeight = FW_NORMAL; + + logFont.lfItalic = FALSE; + logFont.lfUnderline = FALSE; + logFont.lfStrikeOut = FALSE; + logFont.lfCharSet = GetCharsetFromCodePage(iCodePage); + logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + logFont.lfQuality = ANTIALIASED_QUALITY; + logFont.lfPitchAndFamily = DEFAULT_PITCH; + strcpy(logFont.lfFaceName, GetFontFaceFromCodePage(iCodePage)); + m_hFont = CreateFontIndirect(&logFont); + + + HDC hdc = m_dib.GetDCHandle(); + m_hOldFont = (HFONT)SelectObject(hdc, m_hFont); + +} + +void CTextBar::SetTextColor(int r, int g, int b) +{ + HDC hDC = m_dib.GetDCHandle(); + ::SetTextColor(hDC, RGB(r, g, b)); +} + +void CTextBar::GetTextExtent(const char * c_szText, SIZE* p_size) +{ + HDC hDC = m_dib.GetDCHandle(); + GetTextExtentPoint32(hDC, c_szText, strlen(c_szText), p_size); +} + +void CTextBar::TextOut(int ix, int iy, const char * c_szText) +{ + m_dib.TextOut(ix, iy, c_szText); + Invalidate(); +} + +void CTextBar::OnCreate() +{ + m_dib.SetBkMode(TRANSPARENT); + + __SetFont(m_fontSize, m_isBold); +} + +CTextBar::CTextBar(int fontSize, bool isBold) +{ + m_hOldFont = NULL; + m_fontSize = fontSize; + m_isBold = isBold; + } CTextBar::~CTextBar() { - MT_PLATFORM_STUB(); -} - -auto CTextBar::TextOut(int, int, const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CTextBar::SetTextColor(int, int, int) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CTextBar::GetTextExtent(const char *, SIZE *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CTextBar::__SetFont(int, bool) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CTextBar::OnCreate() -> void -{ - MT_PLATFORM_STUB(); + HDC hdc = m_dib.GetDCHandle(); + SelectObject(hdc, m_hOldFont); } diff --git a/src/platform/EterLib/UIRenderCommands.cpp b/src/platform/EterLib/UIRenderCommands.cpp index 2859b67b..4604e440 100644 --- a/src/platform/EterLib/UIRenderCommands.cpp +++ b/src/platform/EterLib/UIRenderCommands.cpp @@ -104,6 +104,11 @@ void UIRenderRestoreClip() { void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4], float su, float sv, float eu, float ev, std::uint32_t argb, int blend) { + UIRenderAddImageNamed(UIRenderTextureName(texture), x, y, su, sv, eu, ev, argb, blend); +} + +void UIRenderAddImageNamed(const std::string& texture_name, const float x[4], const float y[4], + float su, float sv, float eu, float ev, std::uint32_t argb, int blend) { // 40250 draws the image quad through the device: the UI pass's world transform places it // (identity except where a caller sets one, e.g. CPythonMiniMap::RenderAtlas's window offset) and a // stage-0 MODULATE with D3DTA_TFACTOR tints it (the atlas's NPC/warp/waypoint marks). @@ -139,7 +144,7 @@ void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const fl command.y1 = std::min({command.qy[0], command.qy[1], command.qy[2], command.qy[3]}); command.x2 = std::max({command.qx[0], command.qx[1], command.qx[2], command.qx[3]}); command.y2 = std::max({command.qy[0], command.qy[1], command.qy[2], command.qy[3]}); - command.text = UIRenderTextureName(texture); + command.text = texture_name; command.quad = true; command.su = su; command.sv = sv; command.eu = eu; command.ev = ev; command.blend = blend; diff --git a/src/platform/EterLib/UIRenderCommands.h b/src/platform/EterLib/UIRenderCommands.h index 976f9feb..a5d053e7 100644 --- a/src/platform/EterLib/UIRenderCommands.h +++ b/src/platform/EterLib/UIRenderCommands.h @@ -63,6 +63,9 @@ bool MtCpuMemoryTexture(const std::string& name, UIMemoryTexture* out); // Godot canvas puts them on .5, so the quad is shifted back by +0.5 here. void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4], float su, float sv, float eu, float ev, std::uint32_t argb, int blend = 0); +// The same quad for a texture named as UIRenderTextureNameFromHandle (a raw device texture: CBlockTexture). +void UIRenderAddImageNamed(const std::string& texture_name, const float x[4], const float y[4], + float su, float sv, float eu, float ev, std::uint32_t argb, int blend = 0); void UIRenderSetSize(int width, int height); void UIRenderGetSize(unsigned* width, unsigned* height); diff --git a/src/platform/Win32Gdi.cpp b/src/platform/Win32Gdi.cpp index 673d636f..21189d7d 100644 --- a/src/platform/Win32Gdi.cpp +++ b/src/platform/Win32Gdi.cpp @@ -455,3 +455,16 @@ BOOL TextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c) MultiByteToWideChar(CP_ACP, 0, lpString, c, wide.data(), n); return TextOutW(hdc, x, y, wide.data(), n); } + +BOOL GetTextExtentPoint32A(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl) +{ + const int n = MultiByteToWideChar(CP_ACP, 0, lpString, c, nullptr, 0); + if (n <= 0) + { + if (c != 0 || !psizl) return FALSE; + return GetTextExtentPoint32W(hdc, nullptr, 0, psizl); + } + std::vector wide(static_cast(n)); + MultiByteToWideChar(CP_ACP, 0, lpString, c, wide.data(), n); + return GetTextExtentPoint32W(hdc, wide.data(), n, psizl); +} diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index adfc2836..9af7a92e 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -194,6 +194,10 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) target_link_libraries(port_text_test PRIVATE port_platform) add_test(NAME port.text COMMAND $) set_tests_properties(port.text PROPERTIES SKIP_RETURN_CODE 77) + add_executable(port_text_bar_test ${PROJECT_SOURCE_DIR}/tests/port/port_text_bar_test.cpp) + target_link_libraries(port_text_bar_test PRIVATE port_platform) + add_test(NAME port.text_bar COMMAND $) + set_tests_properties(port.text_bar PROPERTIES SKIP_RETURN_CODE 77) # MilesLib: the 40250 sound managers over the Miles emulation (platform/MilesLib/MilesAil.cpp). add_executable(port_miles_test ${PROJECT_SOURCE_DIR}/tests/port/port_miles_test.cpp) diff --git a/src/port/EterLib/BlockTexture.h b/src/port/EterLib/BlockTexture.h new file mode 100644 index 00000000..0b747d05 --- /dev/null +++ b/src/port/EterLib/BlockTexture.h @@ -0,0 +1,26 @@ +#pragma once + +#include "GrpBase.h" + +class CGraphicDib; + +class CBlockTexture : public CGraphicBase +{ + public: + CBlockTexture(); + virtual ~CBlockTexture(); + + bool Create(CGraphicDib * pDIB, const RECT & c_rRect, DWORD dwWidth, DWORD dwHeight); + void SetClipRect(const RECT & c_rRect); + void Render(int ix, int iy); + void InvalidateRect(const RECT & c_rsrcRect); + + protected: + CGraphicDib * m_pDIB; + RECT m_rect; + RECT m_clipRect; + BOOL m_bClipEnable; + DWORD m_dwWidth; + DWORD m_dwHeight; + LPDIRECT3DTEXTURE8 m_lpd3dTexture; +}; diff --git a/src/port/common/shim/win32/wingdi.h b/src/port/common/shim/win32/wingdi.h index efbe0600..5bc6ab19 100644 --- a/src/port/common/shim/win32/wingdi.h +++ b/src/port/common/shim/win32/wingdi.h @@ -58,6 +58,8 @@ COLORREF SetTextColor(HDC hdc, COLORREF color); COLORREF SetBkColor(HDC hdc, COLORREF color); int SetBkMode(HDC hdc, int mode); BOOL GetTextExtentPoint32W(HDC hdc, LPCWSTR lpString, int c, LPSIZE psizl); +BOOL GetTextExtentPoint32A(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl); +#define GetTextExtentPoint32 GetTextExtentPoint32A BOOL GetCharABCWidthsFloatW(HDC hdc, UINT iFirst, UINT iLast, LPABCFLOAT lpABC); BOOL TextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int c); BOOL TextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c); diff --git a/tests/port/port_text_bar_test.cpp b/tests/port/port_text_bar_test.cpp new file mode 100644 index 00000000..4ebe742e --- /dev/null +++ b/tests/port/port_text_bar_test.cpp @@ -0,0 +1,170 @@ +// 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 + +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; +} + +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) + } + + DeviceAccess::Set(NULL); + device->Release(); + if (!g_failures) + std::printf("ok\n"); + return g_failures ? 1 : 0; +}