Files
StellarX-kaifa/tests/visual-probe/WideTextProbe.cpp
T

435 lines
16 KiB
C++

#include "VisualProbeCommon.h"
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <sstream>
namespace VisualProbe
{
namespace
{
constexpr COLORREF MainBackground = RGB(22, 34, 56);
constexpr COLORREF WorkingBackground = RGB(64, 28, 46);
constexpr COLORREF EasyXMarker = RGB(30, 210, 235);
constexpr COLORREF WideTextColor = RGB(246, 224, 64);
struct TextTest
{
const char* id;
std::vector<std::uint32_t> codePoints;
};
struct TextApiResult
{
std::string id;
std::vector<std::uint32_t> codePoints;
std::wstring text;
bool validUtf16 = false;
bool extentSuccess = false;
SIZE extent{};
bool textOutSuccess = false;
int drawTextHeight = 0;
PixelBounds textOutBounds{};
PixelBounds drawTextBounds{};
};
struct WideCaseResult
{
std::string id;
std::string surfaceId;
std::string batchState;
SurfaceFingerprint before{};
SurfaceFingerprint afterDraw{};
SurfaceFingerprint afterFlush{};
SurfaceFingerprint afterEnd{};
SurfaceFingerprint afterEasyXFollowUp{};
SurfaceFingerprint nonTargetBefore{};
SurfaceFingerprint nonTargetAfterDraw{};
bool targetChanged = false;
bool nonTargetUnchanged = false;
bool gdiStateRestored = false;
bool flushApplicable = false;
bool endApplicable = false;
bool explicitWorkingComposition = false;
bool wideContentPreservedAfterEasyXFollowUp = false;
bool evidenceComplete = true;
std::vector<TextApiResult> textResults;
};
std::vector<TextTest> fixedTests()
{
return {
{ "ascii", { 0x0053, 0x0074, 0x0065, 0x006C, 0x006C, 0x0061, 0x0072,
0x0058, 0x0020, 0x0030, 0x0043, 0x0020, 0x0050, 0x0072, 0x006F, 0x0062, 0x0065 } },
{ "cjk_bmp", { 0x661F, 0x57A3, 0x7ED8, 0x5236, 0x6D4B, 0x8BD5 } },
{ "supplementary", { 0x1F600 } },
{ "combining_nfd", { 0x0065, 0x0301 } },
{ "mixed", { 0x661F, 0x57A3, 0x0020, 0x0041, 0x1F600, 0x0042,
0x0020, 0x0065, 0x0301 } }
};
}
void checkpoint(const Options& options, const std::string& value) noexcept
{
try
{
std::ofstream output(options.outputDirectory / "progress.log",
std::ios::binary | std::ios::app);
output << GetTickCount64() << ' ' << value << '\n';
output.flush();
}
catch (...)
{
}
}
void clearSurface(IMAGE* image, COLORREF background)
{
SetWorkingImage(image);
setbkcolor(background);
cleardevice();
}
std::string normalizedRelative(const std::filesystem::path& path)
{
std::string value = path.generic_string();
std::replace(value.begin(), value.end(), '\\', '/');
return value;
}
bool addSurfaceEvidence(
const Options& options,
HDC hdc,
const std::string& fileName,
const std::string& purpose,
std::vector<EvidenceFile>& evidence)
{
const std::filesystem::path relative = std::filesystem::path("evidence") / fileName;
const bool saved = saveHdcImage(hdc, CanvasWidth, CanvasHeight,
options.outputDirectory / relative);
if (saved)
evidence.push_back({ normalizedRelative(relative), purpose, "surface_bitblt" });
return saved;
}
bool addClientEvidence(
const Options& options,
HWND hwnd,
const std::string& fileName,
const std::string& purpose,
std::vector<EvidenceFile>& evidence)
{
const std::filesystem::path relative = std::filesystem::path("evidence") / fileName;
Sleep(30);
std::string backend;
const bool saved = captureClientImage(hwnd, options.outputDirectory / relative, &backend);
if (saved)
evidence.push_back({ normalizedRelative(relative), purpose, backend });
return saved;
}
void drawEasyXBaseline()
{
setfillcolor(EasyXMarker);
solidrectangle(20, 20, 170, 58);
settextcolor(EasyXMarker);
setbkmode(TRANSPARENT);
settextstyle(22, 0, "Microsoft YaHei UI");
outtextxy(20, 68, "EasyX baseline");
}
std::vector<TextApiResult> drawWideText(HDC hdc, COLORREF background, bool& restored)
{
std::vector<TextApiResult> results;
const GdiState before = captureGdiState(hdc);
const int saved = SaveDC(hdc);
HFONT font = CreateFontW(-28, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
DEFAULT_PITCH | FF_DONTCARE, L"Microsoft YaHei UI");
HGDIOBJ previousFont = font ? SelectObject(hdc, font) : nullptr;
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, WideTextColor);
const auto tests = fixedTests();
for (std::size_t index = 0; index < tests.size(); ++index)
{
TextApiResult result;
result.id = tests[index].id;
result.codePoints = tests[index].codePoints;
result.text = makeUtf16(result.codePoints, result.validUtf16);
const int y = 120 + static_cast<int>(index) * 105;
result.extentSuccess = GetTextExtentPoint32W(hdc, result.text.data(),
static_cast<int>(result.text.size()), &result.extent) != FALSE;
result.textOutSuccess = TextOutW(hdc, 20, y, result.text.data(),
static_cast<int>(result.text.size())) != FALSE;
RECT drawRect{ 430, y, 930, y + 70 };
result.drawTextHeight = DrawTextW(hdc, result.text.data(),
static_cast<int>(result.text.size()), &drawRect,
DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX);
const RECT textOutRegion{ 15, y - 3, 410, y + 70 };
const RECT drawTextRegion{ 425, y - 3, 940, y + 70 };
result.textOutBounds = findChangedBounds(hdc, textOutRegion, background);
result.drawTextBounds = findChangedBounds(hdc, drawTextRegion, background);
results.push_back(std::move(result));
}
if (previousFont) SelectObject(hdc, previousFont);
if (saved) RestoreDC(hdc, saved);
if (font) DeleteObject(font);
const GdiState after = captureGdiState(hdc);
restored = saved != 0 && sameGdiState(before, after);
return results;
}
WideCaseResult runCase(
const Options& options,
HWND hwnd,
IMAGE& working,
bool targetMain,
bool insideBatch,
std::vector<EvidenceFile>& evidence)
{
WideCaseResult result;
result.surfaceId = targetMain ? "main" : "working";
result.batchState = insideBatch ? "inside" : "outside";
result.id = result.surfaceId + "_" + result.batchState;
result.flushApplicable = insideBatch;
result.endApplicable = insideBatch;
checkpoint(options, result.id + ":begin");
clearSurface(nullptr, MainBackground);
clearSurface(&working, WorkingBackground);
checkpoint(options, result.id + ":surfaces_cleared");
IMAGE* targetImage = targetMain ? nullptr : &working;
SetWorkingImage(nullptr);
HDC mainDc = GetImageHDC(nullptr);
HDC workingDc = GetImageHDC(&working);
HDC targetDc = targetMain ? mainDc : workingDc;
HDC nonTargetDc = targetMain ? workingDc : mainDc;
const COLORREF targetBackground = targetMain ? MainBackground : WorkingBackground;
result.before = captureSurfaceFingerprint(targetDc, CanvasWidth, CanvasHeight);
result.nonTargetBefore = captureSurfaceFingerprint(nonTargetDc, CanvasWidth, CanvasHeight);
checkpoint(options, result.id + ":before_captured");
SetWorkingImage(nullptr);
if (insideBatch) BeginBatchDraw();
SetWorkingImage(targetImage);
drawEasyXBaseline();
result.textResults = drawWideText(targetDc, targetBackground, result.gdiStateRestored);
checkpoint(options, result.id + ":draw_complete");
result.afterDraw = captureSurfaceFingerprint(targetDc, CanvasWidth, CanvasHeight);
result.nonTargetAfterDraw = captureSurfaceFingerprint(nonTargetDc, CanvasWidth, CanvasHeight);
checkpoint(options, result.id + ":after_draw_captured");
result.targetChanged = result.before.pixelDigest != result.afterDraw.pixelDigest;
result.nonTargetUnchanged =
result.nonTargetBefore.pixelDigest == result.nonTargetAfterDraw.pixelDigest;
result.evidenceComplete &= addSurfaceEvidence(options, targetDc,
result.id + "_after_draw_target.png", result.id + " target after draw", evidence);
result.evidenceComplete &= addSurfaceEvidence(options, nonTargetDc,
result.id + "_after_draw_non_target.png", result.id + " non-target after draw", evidence);
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_after_draw_client.png", result.id + " client after draw", evidence);
checkpoint(options, result.id + ":after_draw_evidence");
if (insideBatch)
{
SetWorkingImage(nullptr);
FlushBatchDraw();
result.afterFlush = captureSurfaceFingerprint(targetDc, CanvasWidth, CanvasHeight);
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_after_flush_client.png", result.id + " client after flush", evidence);
EndBatchDraw();
checkpoint(options, result.id + ":batch_ended");
result.afterEnd = captureSurfaceFingerprint(targetDc, CanvasWidth, CanvasHeight);
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_after_end_client.png", result.id + " client after end", evidence);
}
else
{
result.afterFlush = result.afterDraw;
result.afterEnd = result.afterDraw;
}
std::vector<std::uint64_t> protectedRegionDigests;
for (std::size_t index = 0; index < result.textResults.size(); ++index)
{
const int y = 120 + static_cast<int>(index) * 105;
const RECT protectedRegion{ 15, y - 3, 940, y + 70 };
protectedRegionDigests.push_back(
capturePixelRegionDigest(targetDc, protectedRegion));
}
SetWorkingImage(targetImage);
setfillcolor(EasyXMarker);
solidrectangle(760, 20, 900, 55);
result.afterEasyXFollowUp = captureSurfaceFingerprint(
targetDc, CanvasWidth, CanvasHeight);
result.wideContentPreservedAfterEasyXFollowUp = true;
for (std::size_t index = 0; index < result.textResults.size(); ++index)
{
const int y = 120 + static_cast<int>(index) * 105;
const RECT protectedRegion{ 15, y - 3, 940, y + 70 };
result.wideContentPreservedAfterEasyXFollowUp =
result.wideContentPreservedAfterEasyXFollowUp &&
protectedRegionDigests[index] ==
capturePixelRegionDigest(targetDc, protectedRegion);
}
result.evidenceComplete &= addSurfaceEvidence(options, targetDc,
result.id + "_after_easyx_follow_up_target.png",
result.id + " target after EasyX follow-up drawing", evidence);
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_after_easyx_follow_up_client.png",
result.id + " client after EasyX follow-up drawing", evidence);
checkpoint(options, result.id + ":easyx_follow_up_complete");
if (!targetMain)
{
SetWorkingImage(nullptr);
BeginBatchDraw();
putimage(0, 0, &working);
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_composition_before_flush.png",
result.id + " explicit putimage before flush", evidence);
FlushBatchDraw();
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_composition_after_flush.png",
result.id + " explicit putimage after flush", evidence);
EndBatchDraw();
result.evidenceComplete &= addClientEvidence(options, hwnd,
result.id + "_composition_after_end.png",
result.id + " explicit putimage after end", evidence);
result.explicitWorkingComposition = true;
}
SetWorkingImage(nullptr);
checkpoint(options, result.id + ":end");
return result;
}
void writeTextResult(std::ostream& output, const TextApiResult& result)
{
output << "{\"testStringId\":" << jsonString(result.id)
<< ",\"codePoints\":" << codePointsJson(result.codePoints)
<< ",\"utf16Units\":" << utf16UnitsJson(result.text)
<< ",\"isValidUtf16\":";
writeBoolean(output, result.validUtf16);
output << ",\"getTextExtentPoint32W\":";
writeBoolean(output, result.extentSuccess);
output << ",\"logicalExtent\":";
writeSizeJson(output, result.extent);
output << ",\"textOutW\":";
writeBoolean(output, result.textOutSuccess);
output << ",\"drawTextWHeight\":" << result.drawTextHeight
<< ",\"textOutInkBounds\":";
writeBoundsJson(output, result.textOutBounds);
output << ",\"drawTextInkBounds\":";
writeBoundsJson(output, result.drawTextBounds);
output << '}';
}
void writeCaseResult(std::ostream& output, const WideCaseResult& result)
{
output << "{\"testCaseId\":" << jsonString(result.id)
<< ",\"surfaceId\":" << jsonString(result.surfaceId)
<< ",\"batchState\":" << jsonString(result.batchState)
<< ",\"targetChanged\":";
writeBoolean(output, result.targetChanged);
output << ",\"nonTargetUnchanged\":";
writeBoolean(output, result.nonTargetUnchanged);
output << ",\"gdiStateRestored\":";
writeBoolean(output, result.gdiStateRestored);
output << ",\"flushApplicable\":";
writeBoolean(output, result.flushApplicable);
output << ",\"endApplicable\":";
writeBoolean(output, result.endApplicable);
output << ",\"explicitWorkingComposition\":";
writeBoolean(output, result.explicitWorkingComposition);
output << ",\"wideContentPreservedAfterEasyXFollowUp\":";
writeBoolean(output, result.wideContentPreservedAfterEasyXFollowUp);
output << ",\"evidenceComplete\":";
writeBoolean(output, result.evidenceComplete);
output << ",\"observations\":{\"afterDraw\":";
writeSurfaceFingerprintJson(output, result.afterDraw);
output << ",\"afterFlush\":";
writeSurfaceFingerprintJson(output, result.afterFlush);
output << ",\"afterEnd\":";
writeSurfaceFingerprintJson(output, result.afterEnd);
output << ",\"afterEasyXFollowUp\":";
writeSurfaceFingerprintJson(output, result.afterEasyXFollowUp);
output << "},\"before\":";
writeSurfaceFingerprintJson(output, result.before);
output << ",\"nonTargetBefore\":";
writeSurfaceFingerprintJson(output, result.nonTargetBefore);
output << ",\"nonTargetAfterDraw\":";
writeSurfaceFingerprintJson(output, result.nonTargetAfterDraw);
output << ",\"textTests\":[";
for (std::size_t index = 0; index < result.textResults.size(); ++index)
{
if (index) output << ',';
writeTextResult(output, result.textResults[index]);
}
output << "]}";
}
}
int runWideTextProbe(const Options& options, const DpiConfiguration& dpiConfiguration)
{
checkpoint(options, "probe:before_initgraph");
HWND hwnd = initgraph(CanvasWidth, CanvasHeight, EX_SHOWCONSOLE);
if (!hwnd)
{
std::cerr << "initgraph failed\n";
return 3;
}
checkpoint(options, "probe:after_initgraph");
SetWindowTextA(hwnd, "StellarX 0C Wide Text Probe");
ShowWindow(hwnd, SW_SHOWNORMAL);
SetForegroundWindow(hwnd);
Sleep(80);
const EnvironmentSnapshot environment = captureEnvironment(hwnd);
IMAGE working(CanvasWidth, CanvasHeight);
checkpoint(options, "probe:environment_and_working_ready");
std::vector<EvidenceFile> evidence;
std::vector<WideCaseResult> cases;
cases.push_back(runCase(options, hwnd, working, true, false, evidence));
cases.push_back(runCase(options, hwnd, working, true, true, evidence));
cases.push_back(runCase(options, hwnd, working, false, false, evidence));
cases.push_back(runCase(options, hwnd, working, false, true, evidence));
checkpoint(options, "probe:all_cases_complete");
bool completed = true;
for (const auto& item : cases)
{
completed = completed && item.targetChanged && item.nonTargetUnchanged &&
item.gdiStateRestored && item.wideContentPreservedAfterEasyXFollowUp &&
item.evidenceComplete;
for (const auto& text : item.textResults)
{
completed = completed && text.validUtf16 && text.extentSuccess &&
text.textOutSuccess && text.drawTextHeight > 0;
}
}
std::ostringstream result;
result << "{\"caseCount\":" << cases.size() << ",\"cases\":[";
for (std::size_t index = 0; index < cases.size(); ++index)
{
if (index) result << ',';
writeCaseResult(result, cases[index]);
}
result << "]}";
SetWorkingImage(nullptr);
closegraph();
std::string error;
if (!writeRawResult(options, dpiConfiguration, environment, completed,
evidence, result.str(), error))
{
std::cerr << error << '\n';
return 4;
}
return completed ? 0 : 5;
}
}