Open timeester3648 opened 7 months ago
On the first pass, after executing ParsePseudoMarkdownBlock(inputPos, inputEnd, text, boldRanges, italicRanges, codeRanges)
, none of the range vectors are populated. So moving the feature code into the for (DWRITE_TEXT_RANGE textRange : boldRanges)
loop effectively turns the operation into a no-op. (As expected, nothing happens.)
Surrounding the added fractions in the Markdown document with bold (**
) markup gets you into that loop and things start working again. But beware that ParsePseudoMarkdownBlock
does not support parsing bold markup in headings (blockType == MarkdownBlockType::Body)
.
Are you seeing something else?
I apologize if that example was a bit flawed. The bug becomes more apparent later in the report (when I use my application). Setting a topology does work sometimes, but when mixing font sizes, it does not. I do not know whether other features also make this bug appear, but I at the very least know that changing font size (using SetFontSize) breaks the typographic features.
I changed the Introduction.md to this:
# DWriteCore Sample Gallery 1/3
This sample application demonstrates the DWriteCore API, which is a reimplementation of the Windows DirectWrite API.
Select items from the *Scenario* menu to see pages that demonstrate various API functionality.
DWriteCore is a low-level API for formatting and rendering text. It is a nano-COM API, meaning it uses COM-style
interfaces (derived from `IUnknown`) but does not actually use the COM run-time. It is therefore not necessary to
call `CoCreateInstance` before using DWriteCore. Instead, call the `DWriteCreateFactory` function to create a factory
object (`IDWriteFactory7`), and then call factory methods to create other objects or perform other actions.
1/3 1/3 111111/33333
# API Layers
The **text layout API** is the highest layer of the DWriteCore API. It includes *text format* objects (`IDWriteTextFormat4`),
which encapsulate formatting properties, and *text layout* objects (`IDWriteTextLayout4`), which represents formatted text
strings. A text layout object exposes methods for drawing, getting metrics, hit testing, and so on. Each paragraph on this
page is a text layout object.
The **font API** exposes information about fonts, and provides functionality needed for text layout and rendering. It includes
*font collection* objects (`IDWriteFontCollection3`), which are collections of fonts grouped into families, *font set* objects
(`IDWriteFontSet3`), which are flat collections of fonts, and *font face* objects (`IDWriteFontFace6`), which represent specific
fonts.
The **text rendering API** provides interfaces uses for rendering text. When you call a text layout object's `Draw` method,
it calls back to an interface (`IDWriteTextRenderer1`), which must be implemented by the application or by some other library.
The use of an abstract callback interface enables DWriteCore to be decoupled from any particular graphics engine. The text
renderer implementation can use text rendering APIs provided by DWriteCore to help with rendering glyphs. The `TextRenderer`
class in this application provides a sample implementation of the `IDWriteTextRenderer1` interface.
The **text analysis API** provides low-level APIs for sophisticated applications that implement their own text layout engines.
This includes script analysis, bidi analysis, shaping, and so on.
And changed the loop to this:
while (inputPos < inputEnd)
{
auto blockType = ParsePseudoMarkdownBlock(inputPos, inputEnd, text, boldRanges, italicRanges, codeRanges);
auto textLayout = CreateTextLayout(GetTextFormat(blockType), text);
wil::com_ptr<IDWriteTypography> typo;
THROW_IF_FAILED(g_factory->CreateTypography(&typo));
typo->AddFontFeature(DWRITE_FONT_FEATURE{
.nameTag = DWRITE_FONT_FEATURE_TAG_FRACTIONS,
.parameter = 1u
});
THROW_IF_FAILED(textLayout->SetTypography(typo.get(), DWRITE_TEXT_RANGE{ 4u, 3u }));
for (DWRITE_TEXT_RANGE textRange : boldRanges)
{
THROW_IF_FAILED(textLayout->SetFontAxisValues(style.boldAxisValues.data(), static_cast<uint32_t>(style.boldAxisValues.size()), textRange));
}
for (DWRITE_TEXT_RANGE textRange : italicRanges)
{
THROW_IF_FAILED(textLayout->SetFontAxisValues(style.italicAxisValues.data(), static_cast<uint32_t>(style.italicAxisValues.size()), textRange));
}
for (DWRITE_TEXT_RANGE textRange : codeRanges)
{
THROW_IF_FAILED(textLayout->SetFontFamilyName(style.codeFamilyName, textRange));
THROW_IF_FAILED(textLayout->SetFontSize(style.codeFontSize, textRange));
}
result.push_back(std::move(textLayout));
}
With that, I get a good result:
However, if I change the size of the font after the range I set the typographic feature (which should not break anything), it fails like this:
With always the first X
amount of characters (if they are [0,9]) in that odd upper-fraction mode:
(even though an 'a' is in between, it still does that odd upper-fraction mode)
I changed the size by adding THROW_IF_FAILED(textLayout->SetFontSize(32.0f, DWRITE_TEXT_RANGE{ 7u, UINT32_MAX - 7u }));
directly under the line THROW_IF_FAILED(textLayout->SetTypography(typo.get(), DWRITE_TEXT_RANGE{ 4u, 3u }));
. If I move that line just above the line result.push_back(std::move(textLayout));
to prevent the size overriding due to styling, the same bug appears:
Even just changing the size of 1 character makes the bug appear (changed the size code to: THROW_IF_FAILED(textLayout->SetFontSize(32.0f, DWRITE_TEXT_RANGE{ 7u, 1u }));
):
I want to make clear that some features do work, for example, small caps (my application, not the samples): Observe the feature not starting at 0, and after the feature, the size of the font is different, but the feature still works.
Here's a potentially simpler set of repro steps:
Samples\TextRendering\cpp-win32\DWriteCoreGallery.sln
)MarkdownWindow.cpp
CreateTextLayoutsFromPseudoMarkdown
function with the following:std::vector<wil::com_ptr<IDWriteTextLayout4>> CreateTextLayoutsFromPseudoMarkdown(std::span<char const> inputText, MarkdownStyle style)
{
std::vector<wil::com_ptr<IDWriteTextLayout4>> result;
auto bodyFormat = CreateTextFormat(style.bodyFamilyName, style.bodyFontSize, style.bodyAxisValues);
auto textLayout = CreateTextLayout(
bodyFormat.get(),
//0 10 20 30 40 50 60
//| | | | | | |
L"This should be formatted as a fraction => 1/2. This should not => 1/2."
);
wil::com_ptr<IDWriteTypography> typo;
THROW_IF_FAILED(g_factory->CreateTypography(&typo));
// Enable fractions feature for span (0-45)
typo->AddFontFeature(DWRITE_FONT_FEATURE{
.nameTag = DWRITE_FONT_FEATURE_TAG_FRACTIONS,
.parameter = 1u
});
THROW_IF_FAILED(textLayout->SetTypography(typo.get(), DWRITE_TEXT_RANGE{ 0u, 45u }));
// Increase font size for span (46-*)
THROW_IF_FAILED(textLayout->SetFontSize(32.0f, DWRITE_TEXT_RANGE{ 46u, UINT_MAX - 46u }));
result.push_back(std::move(textLayout));
return result;
}
Tagging @niklasb-ms
Describe the bug
DWriteCore's IDWriteTypography does not properly modify the typography when setting it in a text IDWriteTextLayout4. When I tell it to enable some typographic features in a range, where the range does not start at 0 (DWRITE_TEXT_RANGE::startPosition), it will not work. If I give it a start position of 0, it will enable the feature (if it works in the first place) beyond DWRITE_TEXT_RANGE::length. Feature disabling also does not work, if a feature is automatically enabled (which is the case for ligatures for Google Material Symbols) and I disable it, even for the entire text (start at 0, use UINT32_MAX for end) it still keeps using ligatures.
The fraction feature has even more odd behaviour. Aside from not working properly it enables and disables differently depending on which size I set the font to.
I tried this with WindowsAppSdk 1.4.231115000 and 1.5.231202003-experimental1 (for my own application), and also the WindowsAppSdk samples, I do not know if that uses a different version.
MSVC 2022 17.8.3
Steps to reproduce the bug
while (inputPos < inputEnd)
loop at line306
, under thetextLayout
creation add this:Observe how it works properly (first screenshot).
for (DWRITE_TEXT_RANGE textRange : boldRanges)
loop and change theDWRITE_TEXT_RANGE{ 0u, UINT32_MAX }
totextRange
. You now see that the fraction feature does not work anymore. Even though "1/3" is still bold which means the font axis does work. This shows thattextRange
holds the correct range. But the typographic feature does not work.The loop now looks like this:
Which gives this result (second screenshot)
DWRITE_TEXT_RANGE{ 0u, UINT32_MAX }
, which also does not work, even though it starts at 0, which makes it work sometimes, but not in this case (third screenshot)Since the markdown window uses separate text layouts I cannot show you the other bugs using it easily, so now I'll take screenshots from my application (using 1.5.231202003-experimental1, but has the same behaviour as 1.4.231115000). In the fourth screenshot you can see when I use DWriteCore and set the entire text range to use the fraction feature, and in the fifth screenshot you can see what happens when I make the "1111111111" a different size (does not matter how small the difference in size is). I did not change anything aside from the size (72pt to 72.1pt, converted to use DIP), but now always the first 3 characters are in "numerator" mode if it is a number (observe screenshot 6). This also happens when I limit the fractions feature to the first three characters only ("1/3"). However, if I change the size back again, it works like normal, but the range still leaks through to the rest of the text: observe screenshot 7 (only the first 3 characters have the feature applied), also see screenshot 8 for how it completely breaks when I also change the size of the text after "1/3".
Expected behavior
Typography features work properly. They work when I enable them, they do not work when I disable, unlike what now happens. And when I enable them for a specific range, they work only within that range and do not bleed to the rest of the text.
Screenshots
NuGet package version
Windows App SDK 1.4.3: 1.4.231115000
Packaging type
Packaged (MSIX), Unpackaged
Windows version
Windows 11 version 22H2 (22621, 2022 Update)
IDE
Visual Studio 2022
Additional context
No response