一次性重排,无任何行为改动。四个目录各自的 .clang-format 放在仓库里已久,但全组织只有 MSIME-Linux 在 CI 里执行 clang-format 检查,这边 149 个文件里有 149 个不符合自己声明的风格。 已逐文件核对语义等价:去掉空白、注释与相邻字符串字面量拆分之后,全部 149 个文件与重排前逐字节 相同。差异只有三类——缩进与换行、clang-format 补的 } // namespace 结尾注释、以及长字符串被拆成 相邻字面量(C++ 保证自动拼接)。 这个提交会写进 .git-blame-ignore-revs。
217 lines
6.4 KiB
C++
217 lines
6.4 KiB
C++
#include "utils/candidate_size_estimator.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <mutex>
|
|
#include <string>
|
|
|
|
#include <dwrite.h>
|
|
#include <wrl/client.h>
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
|
|
namespace
|
|
{
|
|
constexpr wchar_t kCaretMarker = L'\uE000';
|
|
|
|
ComPtr<IDWriteFactory> SharedDWriteFactory()
|
|
{
|
|
static std::mutex mutex;
|
|
static ComPtr<IDWriteFactory> factory;
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
if (!factory)
|
|
{
|
|
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
|
|
reinterpret_cast<IUnknown **>(factory.GetAddressOf()));
|
|
}
|
|
return factory;
|
|
}
|
|
|
|
double FallbackGlyphWidth(const std::wstring &text, float fontSizePx)
|
|
{
|
|
if (text.empty() || fontSizePx <= 0.0f)
|
|
{
|
|
return 0.0;
|
|
}
|
|
return static_cast<double>(text.size()) * static_cast<double>(fontSizePx) * 0.92;
|
|
}
|
|
|
|
double MeasureTextWidthDip(const std::wstring &text, const std::wstring &fontFamily, float fontSizePx)
|
|
{
|
|
if (text.empty() || fontSizePx <= 0.0f)
|
|
{
|
|
return 0.0;
|
|
}
|
|
const ComPtr<IDWriteFactory> factory = SharedDWriteFactory();
|
|
if (!factory)
|
|
{
|
|
return FallbackGlyphWidth(text, fontSizePx);
|
|
}
|
|
|
|
const std::wstring family = fontFamily.empty() ? std::wstring(L"Microsoft YaHei") : fontFamily;
|
|
ComPtr<IDWriteTextFormat> format;
|
|
if (FAILED(factory->CreateTextFormat(family.c_str(), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
|
|
DWRITE_FONT_STRETCH_NORMAL, fontSizePx, L"en-us", &format)) ||
|
|
!format)
|
|
{
|
|
return FallbackGlyphWidth(text, fontSizePx);
|
|
}
|
|
|
|
ComPtr<IDWriteTextLayout> layout;
|
|
const float layoutWidth = 8192.0f;
|
|
const float layoutHeight = fontSizePx * 4.0f;
|
|
if (FAILED(factory->CreateTextLayout(text.c_str(), static_cast<UINT32>(text.size()), format.Get(), layoutWidth,
|
|
layoutHeight, &layout)) ||
|
|
!layout)
|
|
{
|
|
return FallbackGlyphWidth(text, fontSizePx);
|
|
}
|
|
|
|
DWRITE_TEXT_METRICS metrics{};
|
|
if (FAILED(layout->GetMetrics(&metrics)))
|
|
{
|
|
return FallbackGlyphWidth(text, fontSizePx);
|
|
}
|
|
return static_cast<double>(metrics.widthIncludingTrailingWhitespace);
|
|
}
|
|
|
|
double ClampPositive(double value, double cap)
|
|
{
|
|
if (value < 1.0)
|
|
{
|
|
value = 1.0;
|
|
}
|
|
if (cap > 1.0)
|
|
{
|
|
value = (std::min)(value, cap);
|
|
}
|
|
return value;
|
|
}
|
|
} // namespace
|
|
|
|
std::wstring StripCandidateHtmlForMeasure(const std::wstring &html)
|
|
{
|
|
std::wstring text;
|
|
text.reserve(html.size());
|
|
bool inTag = false;
|
|
for (size_t i = 0; i < html.size(); ++i)
|
|
{
|
|
const wchar_t ch = html[i];
|
|
if (ch == L'<')
|
|
{
|
|
inTag = true;
|
|
continue;
|
|
}
|
|
if (ch == L'>')
|
|
{
|
|
inTag = false;
|
|
continue;
|
|
}
|
|
if (inTag)
|
|
{
|
|
continue;
|
|
}
|
|
if (ch == kCaretMarker)
|
|
{
|
|
continue;
|
|
}
|
|
if (ch == L'&')
|
|
{
|
|
const size_t semi = html.find(L';', i + 1);
|
|
if (semi != std::wstring::npos && semi - i < 10)
|
|
{
|
|
const std::wstring entity = html.substr(i, semi - i + 1);
|
|
if (entity == L"&")
|
|
text += L'&';
|
|
else if (entity == L"<")
|
|
text += L'<';
|
|
else if (entity == L">")
|
|
text += L'>';
|
|
else if (entity == L""")
|
|
text += L'"';
|
|
else if (entity == L"'" || entity == L"'")
|
|
text += L'\'';
|
|
else
|
|
text += L' ';
|
|
i = semi;
|
|
continue;
|
|
}
|
|
}
|
|
text += ch;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
std::pair<double, double> ComposeCandidateCardSizeDip(double preeditWidthDip, const std::vector<double> &itemWidthDip,
|
|
const CandidateSizeEstimateInput &input)
|
|
{
|
|
const double font = input.fontSizePx > 0.0f ? static_cast<double>(input.fontSizePx) : 16.0;
|
|
const double preeditFont = input.preeditFontSizePx > 0.0f ? static_cast<double>(input.preeditFontSizePx) : font;
|
|
const double numberAndBar = font * 0.8 + font * 0.2 + 8.0;
|
|
const double padX = 12.0;
|
|
const double padY = 8.0;
|
|
const double slackX = 14.0;
|
|
const double slackY = 10.0;
|
|
const double minWidth = font * 7.0;
|
|
const double preeditRowH = input.preeditVisible ? preeditFont * 1.4 + 6.0 : 0.0;
|
|
const double candRowH = font * 1.45 + 6.0;
|
|
|
|
double width = 0.0;
|
|
double height = padY + slackY;
|
|
if (input.preeditVisible)
|
|
{
|
|
width = (std::max)(width, preeditWidthDip + 6.0);
|
|
height += preeditRowH;
|
|
}
|
|
|
|
size_t visibleRows = 0;
|
|
double itemRowWidthSum = 0.0;
|
|
double itemRowWidthMax = 0.0;
|
|
for (double itemWidth : itemWidthDip)
|
|
{
|
|
if (itemWidth <= 0.0)
|
|
{
|
|
continue;
|
|
}
|
|
++visibleRows;
|
|
const double rowWidth = itemWidth + numberAndBar;
|
|
itemRowWidthSum += rowWidth + 8.0;
|
|
itemRowWidthMax = (std::max)(itemRowWidthMax, rowWidth);
|
|
}
|
|
|
|
if (input.horizontal)
|
|
{
|
|
width = (std::max)(width, itemRowWidthSum);
|
|
height += candRowH;
|
|
}
|
|
else
|
|
{
|
|
width = (std::max)(width, itemRowWidthMax);
|
|
height += candRowH * static_cast<double>((std::max)(visibleRows, size_t{1}));
|
|
}
|
|
|
|
width = (std::max)(width + padX + slackX, minWidth);
|
|
return {ClampPositive(width, input.maxWidthDip), ClampPositive(height, input.maxHeightDip)};
|
|
}
|
|
|
|
std::pair<double, double> EstimateCandidateCardSizeDip(const CandidateSizeEstimateInput &input)
|
|
{
|
|
const std::wstring preeditText = StripCandidateHtmlForMeasure(input.preedit);
|
|
const double preeditWidth =
|
|
input.preeditVisible ? MeasureTextWidthDip(preeditText, input.fontFamily, input.preeditFontSizePx) : 0.0;
|
|
|
|
std::vector<double> itemWidths;
|
|
itemWidths.reserve(input.itemHtml.size());
|
|
for (const std::wstring &html : input.itemHtml)
|
|
{
|
|
const std::wstring text = StripCandidateHtmlForMeasure(html);
|
|
if (text.empty())
|
|
{
|
|
itemWidths.push_back(0.0);
|
|
continue;
|
|
}
|
|
itemWidths.push_back(MeasureTextWidthDip(text, input.fontFamily, input.fontSizePx));
|
|
}
|
|
return ComposeCandidateCardSizeDip(preeditWidth, itemWidths, input);
|
|
}
|