// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include "BoxDrawingEffect.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 const factory, gsl::not_null const analyzer, gsl::not_null const format, gsl::not_null const font, const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters, size_t const width, IBoxDrawingEffect* const boxEffect); [[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; [[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept; 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 fontFace; FLOAT fontScale; ::Microsoft::WRL::ComPtr 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 _SetMappedFont(UINT32 textPosition, UINT32 textLength, IDWriteFont* const font, FLOAT const scale); [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null const source, UINT32 textPosition, UINT32 textLength); [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength); [[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 _CorrectBoxDrawing() noexcept; [[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext, IDWriteTextRenderer* renderer, const D2D_POINT_2F origin) noexcept; [[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept; private: const ::Microsoft::WRL::ComPtr _factory; // DirectWrite analyzer const ::Microsoft::WRL::ComPtr _analyzer; // DirectWrite text format const ::Microsoft::WRL::ComPtr _format; // DirectWrite font face const ::Microsoft::WRL::ComPtr _font; // Box drawing effect const ::Microsoft::WRL::ComPtr _boxDrawingEffect; // The text we're analyzing and processing into a layout std::wstring _text; std::vector _textClusterColumns; size_t _width; // Properties of the text that might be relevant. std::wstring _localeName; ::Microsoft::WRL::ComPtr _numberSubstitution; DWRITE_READING_DIRECTION _readingDirection; // Text analysis results std::vector _runs; std::vector _breakpoints; // Text analysis interim status variable (to assist the Analyzer Sink in operations involving _runs) UINT32 _runIndex; // Glyph shaping results std::vector _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 _glyphClusters; // This appears to be the index of the glyph inside each font. std::vector _glyphIndices; std::vector _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 _glyphScaleCorrections; #ifdef UNIT_TESTING public: CustomTextLayout() = default; friend class CustomTextLayoutTests; #endif }; }