terminal/src/renderer/dx/CustomTextLayout.h

173 lines
8.4 KiB
C
Raw Normal View History

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <dwrite.h>
#include <d2d1.h>
#include <wrl.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#include "../inc/Cluster.hpp"
namespace Microsoft::Console::Render
{
class CustomTextLayout : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextAnalysisSource, IDWriteTextAnalysisSink>
{
public:
// Based on the Windows 7 SDK sample at https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomLayout
CustomTextLayout(IDWriteFactory1* const factory,
IDWriteTextAnalyzer1* const analyzer,
IDWriteTextFormat* const format,
IDWriteFontFace1* const font,
const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters,
size_t const width);
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns);
Merged PR 3215853: Fix spacing/layout for block characters and many retroactively-recategorized emoji (and more!) This encompasses a handful of problems with column counting. The Terminal project didn't set a fallback column counter. Oops. I've fixed this to use the `DxEngine` as the fallback. The `DxEngine` didn't implement its fallback method. Oops. I've fixed this to use the `CustomTextLayout` to figure out the advances based on the same font and fallback pattern as the real final layout, just without "rounding" it into cells yet. - `CustomTextLayout` has been updated to move the advance-correction into a separate phase from glyph shaping. Previously, we corrected the advances to nice round cell counts during shaping, which is fine for drawing, but hard for column count analysis. - Now that there are separate phases, an `Analyze` method was added to the `CustomTextLayout` which just performs the text analysis steps and the glyph shaping, but no advance correction to column boundaries nor actual drawing. I've taken the caching code that I was working on to improve chafa, and I've brought it into this. Now that we're doing a lot of fallback and heavy lifting in terms of analysis via the layout, we should cache the results until the font changes. I've adjusted how column counting is done overall. It's always been in these phases: 1. We used a quick-lookup of ranges of characters we knew to rapidly decide `Narrow`, `Wide` or `Invalid` (a.k.a. "I dunno") 2. If it was `Invalid`, we consulted a table based off of the Unicode standard that has either `Narrow`, `Wide`, or `Ambiguous` as a result. 3. If it's still `Ambiguous`, we consult a render engine fallback (usually GDI or now DX) to see how many columns it would take. 4. If we still don't know, then it's `Wide` to be safe. - I've added an additional flow here. The quick-lookup can now return `Ambiguous` off the bat for some glyph characters in the x2000-x3000 range that used to just be simple shapes but have been retroactively recategorized as emoji and are frequently now using full width color glyphs. - This new state causes the lookup to go immediately to the render engine if it is available instead of consulting the Unicode standard table first because the half/fullwidth table doesn't appear to have been updated for this nuance to reclass these characters as ambiguous, but we'd like to keep that table as a "generated from the spec" sort of table and keep our exceptions in the "quick lookup" function. I have confirmed the following things "just work" now: - The windows logo flag from the demo. (⚫⚪💖✅🌌😊) - The dotted chart on the side of crossterm demo (•) - The powerline characters that make arrows with the Consolas patched font (██) - An accented é - The warning and checkmark symbols appearing same size as the X. (✔⚠🔥) Related work items: #21167256, #21237515, #21243859, #21274645, #21296827
2019-05-02 01:13:53 +02:00
// IDWriteTextLayout methods (but we don't actually want to implement them all, so just this one matching the existing interface)
[[nodiscard]] HRESULT STDMETHODCALLTYPE Draw(_In_opt_ void* clientDrawingContext,
_In_ IDWriteTextRenderer* renderer,
FLOAT originX,
FLOAT originY);
// IDWriteTextAnalysisSource methods
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition,
_Outptr_result_buffer_(*textLength) WCHAR const** textString,
_Out_ UINT32* textLength) override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition,
_Outptr_result_buffer_(*textLength) WCHAR const** textString,
_Out_ UINT32* textLength) override;
[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition,
_Out_ UINT32* textLength,
_Outptr_result_z_ WCHAR const** localeName) override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition,
_Out_ UINT32* textLength,
_COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) override;
// IDWriteTextAnalysisSink methods
[[nodiscard]] HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition,
UINT32 textLength,
_In_ DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition,
UINT32 textLength,
_In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition,
UINT32 textLength,
UINT8 explicitLevel,
UINT8 resolvedLevel) override;
[[nodiscard]] HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition,
UINT32 textLength,
_In_ IDWriteNumberSubstitution* numberSubstitution) override;
protected:
// A single contiguous run of characters containing the same analysis results.
struct Run
{
Run() :
textStart(),
textLength(),
glyphStart(),
glyphCount(),
bidiLevel(),
script(),
isNumberSubstituted(),
isSideways(),
Merged PR 3215853: Fix spacing/layout for block characters and many retroactively-recategorized emoji (and more!) This encompasses a handful of problems with column counting. The Terminal project didn't set a fallback column counter. Oops. I've fixed this to use the `DxEngine` as the fallback. The `DxEngine` didn't implement its fallback method. Oops. I've fixed this to use the `CustomTextLayout` to figure out the advances based on the same font and fallback pattern as the real final layout, just without "rounding" it into cells yet. - `CustomTextLayout` has been updated to move the advance-correction into a separate phase from glyph shaping. Previously, we corrected the advances to nice round cell counts during shaping, which is fine for drawing, but hard for column count analysis. - Now that there are separate phases, an `Analyze` method was added to the `CustomTextLayout` which just performs the text analysis steps and the glyph shaping, but no advance correction to column boundaries nor actual drawing. I've taken the caching code that I was working on to improve chafa, and I've brought it into this. Now that we're doing a lot of fallback and heavy lifting in terms of analysis via the layout, we should cache the results until the font changes. I've adjusted how column counting is done overall. It's always been in these phases: 1. We used a quick-lookup of ranges of characters we knew to rapidly decide `Narrow`, `Wide` or `Invalid` (a.k.a. "I dunno") 2. If it was `Invalid`, we consulted a table based off of the Unicode standard that has either `Narrow`, `Wide`, or `Ambiguous` as a result. 3. If it's still `Ambiguous`, we consult a render engine fallback (usually GDI or now DX) to see how many columns it would take. 4. If we still don't know, then it's `Wide` to be safe. - I've added an additional flow here. The quick-lookup can now return `Ambiguous` off the bat for some glyph characters in the x2000-x3000 range that used to just be simple shapes but have been retroactively recategorized as emoji and are frequently now using full width color glyphs. - This new state causes the lookup to go immediately to the render engine if it is available instead of consulting the Unicode standard table first because the half/fullwidth table doesn't appear to have been updated for this nuance to reclass these characters as ambiguous, but we'd like to keep that table as a "generated from the spec" sort of table and keep our exceptions in the "quick lookup" function. I have confirmed the following things "just work" now: - The windows logo flag from the demo. (⚫⚪💖✅🌌😊) - The dotted chart on the side of crossterm demo (•) - The powerline characters that make arrows with the Consolas patched font (██) - An accented é - The warning and checkmark symbols appearing same size as the X. (✔⚠🔥) Related work items: #21167256, #21237515, #21243859, #21274645, #21296827
2019-05-02 01:13:53 +02:00
fontFace{ nullptr },
fontScale{ 1.0 }
{
}
UINT32 textStart; // starting text position of this run
UINT32 textLength; // number of contiguous code units covered
UINT32 glyphStart; // starting glyph in the glyphs array
UINT32 glyphCount; // number of glyphs associated with this run of text
DWRITE_SCRIPT_ANALYSIS script;
UINT8 bidiLevel;
bool isNumberSubstituted;
bool isSideways;
::Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
FLOAT fontScale;
inline bool ContainsTextPosition(UINT32 desiredTextPosition) const
{
return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength;
}
inline bool operator==(UINT32 desiredTextPosition) const
{
// Search by text position using std::find
return ContainsTextPosition(desiredTextPosition);
}
};
// Single text analysis run, which points to the next run.
struct LinkedRun : Run
{
LinkedRun() :
nextRunIndex(0)
{
}
UINT32 nextRunIndex; // index of next run
};
[[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength);
void _SetCurrentRun(const UINT32 textPosition);
void _SplitCurrentRun(const UINT32 splitPosition);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFont(UINT32 textPosition, UINT32 textLength, IDWriteFont* const font, FLOAT const scale);
[[nodiscard]] HRESULT _AnalyzeRuns() noexcept;
[[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept;
[[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept;
[[nodiscard]] HRESULT _CorrectGlyphRuns() noexcept;
[[nodiscard]] HRESULT _CorrectGlyphRun(const UINT32 runIndex) noexcept;
[[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext,
IDWriteTextRenderer* renderer,
const D2D_POINT_2F origin) noexcept;
[[nodiscard]] static UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept;
private:
const ::Microsoft::WRL::ComPtr<IDWriteFactory1> _factory;
// DirectWrite analyzer
const ::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _analyzer;
// DirectWrite text format
const ::Microsoft::WRL::ComPtr<IDWriteTextFormat> _format;
// DirectWrite font face
const ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _font;
// The text we're analyzing and processing into a layout
std::wstring _text;
std::vector<UINT16> _textClusterColumns;
size_t _width;
// Properties of the text that might be relevant.
std::wstring _localeName;
::Microsoft::WRL::ComPtr<IDWriteNumberSubstitution> _numberSubstitution;
DWRITE_READING_DIRECTION _readingDirection;
// Text analysis results
std::vector<LinkedRun> _runs;
std::vector<DWRITE_LINE_BREAKPOINT> _breakpoints;
// Text analysis interim status variable (to assist the Analyzer Sink in operations involving _runs)
UINT32 _runIndex;
// Glyph shaping results
std::vector<DWRITE_GLYPH_OFFSET> _glyphOffsets;
std::vector<UINT16> _glyphClusters;
std::vector<UINT16> _glyphIndices;
std::vector<float> _glyphAdvances;
};
}