Skip to content

Commit

Permalink
AtlasEngine: Scale glyphs to better fit the cell size
Browse files Browse the repository at this point in the history
  • Loading branch information
lhecker committed Jul 26, 2022
1 parent 92a0518 commit b6acacc
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 154 deletions.
15 changes: 8 additions & 7 deletions src/renderer/atlas/AtlasEngine.api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
const auto strikethroughPosition = static_cast<float>(-metrics.strikethroughPosition) * designUnitsPerPx;
const auto strikethroughThickness = static_cast<float>(metrics.strikethroughThickness) * designUnitsPerPx;

const auto cellWidth = std::ceilf(advanceWidth);
const auto halfGap = lineGap / 2.0f;
const auto baseline = std::ceilf(ascent + halfGap);
const auto lineHeight = std::ceilf(baseline + descent + halfGap);
Expand Down Expand Up @@ -648,21 +649,20 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
// Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries.
doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, lineHeight - thinLineWidth);

const auto cellWidth = gsl::narrow<u16>(std::ceilf(advanceWidth));
const auto cellHeight = gsl::narrow<u16>(lineHeight);
const u16x2 cellSize{ gsl::narrow<u16>(cellWidth), gsl::narrow<u16>(lineHeight) };

{
til::size coordSize;
coordSize.X = cellWidth;
coordSize.Y = cellHeight;
coordSize.X = cellSize.x;
coordSize.Y = cellSize.y;

if (requestedSize.X == 0)
{
// The coordSizeUnscaled parameter to SetFromEngine is used for API functions like GetConsoleFontSize.
// Since clients expect that settings the font height to Y yields back a font height of Y,
// we're scaling the X relative/proportional to the actual cellWidth/cellHeight ratio.
// we're scaling the X relative/proportional to the actual cellSize.x/.y ratio.
// The code below uses a poor form of integer rounding.
requestedSize.X = (requestedSize.Y * cellWidth + cellHeight / 2) / cellHeight;
requestedSize.X = (requestedSize.Y * cellSize.x + cellSize.y / 2) / cellSize.y;
}

fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, coordSize, requestedSize);
Expand All @@ -687,7 +687,8 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
fontMetrics->fontName = std::move(fontName);
fontMetrics->fontSizeInDIP = fontSize / static_cast<float>(_api.dpi) * 96.0f;
fontMetrics->baselineInDIP = baseline / static_cast<float>(_api.dpi) * 96.0f;
fontMetrics->cellSize = { cellWidth, cellHeight };
fontMetrics->advanceScale = cellWidth / advanceWidth;
fontMetrics->cellSize = cellSize;
fontMetrics->fontWeight = fontWeightU16;
fontMetrics->underlinePos = underlinePosU16;
fontMetrics->underlineWidth = underlineWidthU16;
Expand Down
150 changes: 14 additions & 136 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,134 +25,6 @@

using namespace Microsoft::Console::Render;

struct TextAnalyzer final : IDWriteTextAnalysisSource, IDWriteTextAnalysisSink
{
constexpr TextAnalyzer(const std::vector<wchar_t>& text, std::vector<AtlasEngine::TextAnalyzerResult>& results) noexcept :
_text{ text }, _results{ results }
{
Ensures(_text.size() <= UINT32_MAX);
}

// TextAnalyzer will be allocated on the stack and reference counting is pointless because of that.
// The debug version will assert that we don't leak any references though.
#ifdef NDEBUG
ULONG __stdcall AddRef() noexcept override
{
return 1;
}

ULONG __stdcall Release() noexcept override
{
return 1;
}
#else
ULONG _refCount = 1;

~TextAnalyzer()
{
assert(_refCount == 1);
}

ULONG __stdcall AddRef() noexcept override
{
return ++_refCount;
}

ULONG __stdcall Release() noexcept override
{
return --_refCount;
}
#endif

HRESULT __stdcall QueryInterface(const IID& riid, void** ppvObject) noexcept override
{
__assume(ppvObject != nullptr);

if (IsEqualGUID(riid, __uuidof(IDWriteTextAnalysisSource)) || IsEqualGUID(riid, __uuidof(IDWriteTextAnalysisSink)))
{
*ppvObject = this;
return S_OK;
}

*ppvObject = nullptr;
return E_NOINTERFACE;
}

HRESULT __stdcall GetTextAtPosition(UINT32 textPosition, const WCHAR** textString, UINT32* textLength) noexcept override
{
// Writing to address 0 is a crash in practice. Just what we want.
__assume(textString != nullptr);
__assume(textLength != nullptr);

const auto size = gsl::narrow_cast<UINT32>(_text.size());
textPosition = std::min(textPosition, size);
*textString = _text.data() + textPosition;
*textLength = size - textPosition;
return S_OK;
}

HRESULT __stdcall GetTextBeforePosition(UINT32 textPosition, const WCHAR** textString, UINT32* textLength) noexcept override
{
// Writing to address 0 is a crash in practice. Just what we want.
__assume(textString != nullptr);
__assume(textLength != nullptr);

const auto size = gsl::narrow_cast<UINT32>(_text.size());
textPosition = std::min(textPosition, size);
*textString = _text.data();
*textLength = textPosition;
return S_OK;
}

DWRITE_READING_DIRECTION __stdcall GetParagraphReadingDirection() noexcept override
{
return DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
}

HRESULT __stdcall GetLocaleName(UINT32 textPosition, UINT32* textLength, const WCHAR** localeName) noexcept override
{
// Writing to address 0 is a crash in practice. Just what we want.
__assume(textLength != nullptr);
__assume(localeName != nullptr);

*textLength = gsl::narrow_cast<UINT32>(_text.size()) - textPosition;
*localeName = nullptr;
return S_OK;
}

HRESULT __stdcall GetNumberSubstitution(UINT32 textPosition, UINT32* textLength, IDWriteNumberSubstitution** numberSubstitution) noexcept override
{
return E_NOTIMPL;
}

HRESULT __stdcall SetScriptAnalysis(UINT32 textPosition, UINT32 textLength, const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) noexcept override
try
{
_results.emplace_back(AtlasEngine::TextAnalyzerResult{ textPosition, textLength, scriptAnalysis->script, static_cast<UINT8>(scriptAnalysis->shapes), 0 });
return S_OK;
}
CATCH_RETURN()

HRESULT __stdcall SetLineBreakpoints(UINT32 textPosition, UINT32 textLength, const DWRITE_LINE_BREAKPOINT* lineBreakpoints) noexcept override
{
return E_NOTIMPL;
}

HRESULT __stdcall SetBidiLevel(UINT32 textPosition, UINT32 textLength, UINT8 explicitLevel, UINT8 resolvedLevel) noexcept override
{
return E_NOTIMPL;
}

HRESULT __stdcall SetNumberSubstitution(UINT32 textPosition, UINT32 textLength, IDWriteNumberSubstitution* numberSubstitution) noexcept override
{
return E_NOTIMPL;
}

private:
const std::vector<wchar_t>& _text;
std::vector<AtlasEngine::TextAnalyzerResult>& _results;
};

#pragma warning(suppress : 26455) // Default constructor may not throw. Declare it 'noexcept' (f.6).
AtlasEngine::AtlasEngine()
{
Expand Down Expand Up @@ -976,9 +848,12 @@ void AtlasEngine::_recreateFontDependentResources()

_r.cellSizeDIP.x = static_cast<float>(_api.fontMetrics.cellSize.x) / scaling;
_r.cellSizeDIP.y = static_cast<float>(_api.fontMetrics.cellSize.y) / scaling;
_r.fontMetrics = _api.fontMetrics;
_r.cellCount = _api.cellCount;
_r.dpi = _api.dpi;
_r.fontMetrics = _api.fontMetrics;
_r.dipPerPixel = static_cast<float>(USER_DEFAULT_SCREEN_DPI) / static_cast<float>(_r.dpi);
_r.pixelPerDIP = static_cast<float>(_r.dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
_r.atlasSizeInPixel = { 0, 0 };
_r.tileAllocator = TileAllocator{ _api.fontMetrics.cellSize, _api.sizeInPixel };

Expand Down Expand Up @@ -1032,18 +907,20 @@ void AtlasEngine::_recreateFontDependentResources()
auto& textFormat = _r.textFormats[italic][bold];

THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontMetrics.fontName.c_str(), _api.fontMetrics.fontCollection.get(), fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontMetrics.fontSizeInDIP, L"", textFormat.put()));
textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
THROW_IF_FAILED(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));

// DWRITE_LINE_SPACING_METHOD_UNIFORM:
// > Lines are explicitly set to uniform spacing, regardless of contained font sizes.
// > This can be useful to avoid the uneven appearance that can occur from font fallback.
// We want that. Otherwise fallback fonts might be rendered with an incorrect baseline and get cut off vertically.
THROW_IF_FAILED(textFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, _r.cellSizeDIP.y, _api.fontMetrics.baselineInDIP));

if (!_api.fontAxisValues.empty())
if (const auto textFormat3 = textFormat.try_query<IDWriteTextFormat3>())
{
if (const auto textFormat3 = textFormat.try_query<IDWriteTextFormat3>())
THROW_IF_FAILED(textFormat3->SetAutomaticFontAxes(DWRITE_AUTOMATIC_FONT_AXES_OPTICAL_SIZE));

if (!_api.fontAxisValues.empty())
{
// The wght axis defaults to the font weight.
_api.fontAxisValues[0].value = bold || standardAxes[0].value == -1.0f ? static_cast<float>(fontWeight) : standardAxes[0].value;
Expand Down Expand Up @@ -1176,7 +1053,8 @@ void AtlasEngine::_flushBufferLine()
const auto textFormat = _getTextFormat(_api.attributes.bold, _api.attributes.italic);
const auto& textFormatAxis = _getTextFormatAxis(_api.attributes.bold, _api.attributes.italic);

TextAnalyzer atlasAnalyzer{ _api.bufferLine, _api.analysisResults };
TextAnalysisSource analysisSource{ _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
TextAnalysisSink analysisSink{ _api.analysisResults };

wil::com_ptr<IDWriteFontCollection> fontCollection;
THROW_IF_FAILED(textFormat->GetFontCollection(fontCollection.addressof()));
Expand All @@ -1195,7 +1073,7 @@ void AtlasEngine::_flushBufferLine()
{
wil::com_ptr<IDWriteFontFace5> fontFace5;
THROW_IF_FAILED(_sr.systemFontFallback.query<IDWriteFontFallback1>()->MapCharacters(
/* analysisSource */ &atlasAnalyzer,
/* analysisSource */ &analysisSource,
/* textPosition */ idx,
/* textLength */ gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx,
/* baseFontCollection */ fontCollection.get(),
Expand All @@ -1214,7 +1092,7 @@ void AtlasEngine::_flushBufferLine()
wil::com_ptr<IDWriteFont> font;

THROW_IF_FAILED(_sr.systemFontFallback->MapCharacters(
/* analysisSource */ &atlasAnalyzer,
/* analysisSource */ &analysisSource,
/* textPosition */ idx,
/* textLength */ gsl::narrow_cast<u32>(_api.bufferLine.size()) - idx,
/* baseFontCollection */ fontCollection.get(),
Expand Down Expand Up @@ -1300,7 +1178,7 @@ void AtlasEngine::_flushBufferLine()
else
{
_api.analysisResults.clear();
THROW_IF_FAILED(_sr.textAnalyzer->AnalyzeScript(&atlasAnalyzer, idx, complexityLength, &atlasAnalyzer));
THROW_IF_FAILED(_sr.textAnalyzer->AnalyzeScript(&analysisSource, idx, complexityLength, &analysisSink));
//_sr.textAnalyzer->AnalyzeBidi(&atlasAnalyzer, idx, complexityLength, &atlasAnalyzer);

for (const auto& a : _api.analysisResults)
Expand Down
10 changes: 8 additions & 2 deletions src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
#include <dwrite_3.h>

#include "../../renderer/inc/IRenderEngine.hpp"
#include "DWriteTextAnalysis.h"

namespace Microsoft::Console::Render
{
struct TextAnalysisSinkResult;

class AtlasEngine final : public IRenderEngine
{
public:
Expand Down Expand Up @@ -397,6 +400,7 @@ namespace Microsoft::Console::Render
std::wstring fontName;
float baselineInDIP = 0.0f;
float fontSizeInDIP = 0.0f;
f32 advanceScale;
u16x2 cellSize;
u16 fontWeight = 0;
u16 underlinePos = 0;
Expand Down Expand Up @@ -953,6 +957,8 @@ namespace Microsoft::Console::Render
u16x2 cellCount; // invalidated by ApiInvalidations::Font|Size, caches _api.cellCount
u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidated by ApiInvalidations::Font, caches _api.dpi
FontMetrics fontMetrics; // invalidated by ApiInvalidations::Font, cached _api.fontMetrics
f32 dipPerPixel = 1.0f; // invalidated by ApiInvalidations::Font, caches USER_DEFAULT_SCREEN_DPI / _api.dpi
f32 pixelPerDIP = 1.0f; // invalidated by ApiInvalidations::Font, caches _api.dpi / USER_DEFAULT_SCREEN_DPI
u16x2 atlasSizeInPixel; // invalidated by ApiInvalidations::Font
TileHashMap glyphs;
TileAllocator tileAllocator;
Expand Down Expand Up @@ -983,7 +989,7 @@ namespace Microsoft::Console::Render
std::vector<wchar_t> bufferLine;
std::vector<u16> bufferLineColumn;
Buffer<BufferLineMetadata> bufferLineMetadata;
std::vector<TextAnalyzerResult> analysisResults;
std::vector<TextAnalysisSinkResult> analysisResults;
Buffer<u16> clusterMap;
Buffer<DWRITE_SHAPING_TEXT_PROPERTIES> textProps;
Buffer<u16> glyphIndices;
Expand All @@ -992,7 +998,7 @@ namespace Microsoft::Console::Render
Buffer<DWRITE_GLYPH_OFFSET> glyphOffsets;
std::vector<DWRITE_FONT_FEATURE> fontFeatures; // changes are flagged as ApiInvalidations::Font|Size
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size
FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size
FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font

u16x2 cellCount; // caches `sizeInPixel / cellSize`
u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
Expand Down
Loading

0 comments on commit b6acacc

Please sign in to comment.