4c16cb278e
Adds support for users to be able to set font features and axes (see the spec for more details!)
## Detailed Description
**CustomTextLayout**
- Asks the `DxFontRenderData` for the font features when getting glyphs
- _If any features have been set/updated, we always skip the "isTextSimple" shortcut_
- Asks the `_formatInUse` for any font axes when mapping characters in `_AnalyzeFontFallback`
**DxFontRenderData**
- Stores a map of font features (initialized to the [standard feature list])
- Stores a map of font axes
- Has methods to add font features/axes to the map or update existing ones
- Has methods to retrieve the font features/axes
- Sets the font axes in the `IDWriteTextFormat` when creating it
## Validation Steps Performed
It works!
[standard feature list]: ac5aef67d1/DrawableObject.ixx (L802)
Specified in #10457
Related to #1790
Closes #759
Closes #5828
217 lines
10 KiB
C++
217 lines
10 KiB
C++
// 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 "BoxDrawingEffect.h"
|
|
#include "DxFontRenderData.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(gsl::not_null<DxFontRenderData*> const fontRenderData);
|
|
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const gsl::span<const ::Microsoft::Console::Render::Cluster> clusters);
|
|
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE Reset() noexcept;
|
|
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns);
|
|
|
|
// 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) noexcept;
|
|
|
|
// 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) noexcept override;
|
|
[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override;
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition,
|
|
_Out_ UINT32* textLength,
|
|
_Outptr_result_z_ WCHAR const** localeName) noexcept override;
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition,
|
|
_Out_ UINT32* textLength,
|
|
_COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept 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() noexcept :
|
|
textStart(),
|
|
textLength(),
|
|
glyphStart(),
|
|
glyphCount(),
|
|
bidiLevel(),
|
|
script(),
|
|
isNumberSubstituted(),
|
|
isSideways(),
|
|
fontFace{ nullptr },
|
|
fontScale{ 1.0 },
|
|
drawingEffect{ nullptr }
|
|
{
|
|
}
|
|
|
|
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;
|
|
::Microsoft::WRL::ComPtr<IUnknown> drawingEffect;
|
|
|
|
inline bool ContainsTextPosition(UINT32 desiredTextPosition) const noexcept
|
|
{
|
|
return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength;
|
|
}
|
|
|
|
inline bool operator==(UINT32 desiredTextPosition) const noexcept
|
|
{
|
|
// Search by text position using std::find
|
|
return ContainsTextPosition(desiredTextPosition);
|
|
}
|
|
};
|
|
|
|
// Single text analysis run, which points to the next run.
|
|
struct LinkedRun : Run
|
|
{
|
|
LinkedRun() noexcept :
|
|
nextRunIndex(0)
|
|
{
|
|
}
|
|
|
|
UINT32 nextRunIndex; // index of next run
|
|
};
|
|
|
|
[[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength);
|
|
[[nodiscard]] LinkedRun& _GetCurrentRun();
|
|
void _SetCurrentRun(const UINT32 textPosition);
|
|
void _SplitCurrentRun(const UINT32 splitPosition);
|
|
void _OrderRuns();
|
|
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength);
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFontFace(UINT32 textPosition, UINT32 textLength, const ::Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace, FLOAT const scale);
|
|
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null<IDWriteTextAnalysisSource*> const source, UINT32 textPosition, UINT32 textLength);
|
|
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength);
|
|
|
|
[[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept;
|
|
[[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 STDMETHODCALLTYPE _CorrectBoxDrawing() noexcept;
|
|
[[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext,
|
|
IDWriteTextRenderer* renderer,
|
|
const D2D_POINT_2F origin) noexcept;
|
|
[[nodiscard]] HRESULT _DrawGlyphRun(_In_opt_ void* clientDrawingContext,
|
|
gsl::not_null<IDWriteTextRenderer*> renderer,
|
|
D2D_POINT_2F& mutableOrigin,
|
|
const Run& run) noexcept;
|
|
|
|
[[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept;
|
|
|
|
private:
|
|
// DirectWrite font render data
|
|
DxFontRenderData* _fontRenderData;
|
|
|
|
// DirectWrite text formats
|
|
IDWriteTextFormat* _formatInUse;
|
|
|
|
// DirectWrite font faces
|
|
IDWriteFontFace1* _fontInUse;
|
|
|
|
// 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
|
|
|
|
// Whether the entire text is determined to be simple and does not require full script shaping.
|
|
bool _isEntireTextSimple;
|
|
|
|
std::vector<DWRITE_GLYPH_OFFSET> _glyphOffsets;
|
|
|
|
// Clusters are complicated. They're in respect to each individual run.
|
|
// The offsets listed here are in respect to the _text string, but from the beginning index of
|
|
// each run.
|
|
// That means if we have two runs, we will see 0 1 2 3 4 0 1 2 3 4 5 6 7... in this clusters count.
|
|
std::vector<UINT16> _glyphClusters;
|
|
|
|
// This appears to be the index of the glyph inside each font.
|
|
std::vector<UINT16> _glyphIndices;
|
|
|
|
// This is for calculating glyph advances when the entire text is simple.
|
|
std::vector<INT32> _glyphDesignUnitAdvances;
|
|
|
|
std::vector<float> _glyphAdvances;
|
|
|
|
struct ScaleCorrection
|
|
{
|
|
UINT32 textIndex;
|
|
UINT32 textLength;
|
|
float scale;
|
|
};
|
|
|
|
// These are used to further break the runs apart and adjust the font size so glyphs fit inside the cells.
|
|
std::vector<ScaleCorrection> _glyphScaleCorrections;
|
|
|
|
#ifdef UNIT_TESTING
|
|
public:
|
|
CustomTextLayout() = default;
|
|
|
|
friend class CustomTextLayoutTests;
|
|
#endif
|
|
};
|
|
}
|