microsoft / microsoft/WindowsAppSDK
DWriteCore Typographics features do not work
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 4.7k
- Forks
- 471
- Avg merge
- 3d 13h
- Merged PRs (30d)
- 28
Description
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
- Clone the Windows App SDK samples.
- Open the DWriteCoreGallery sample solution.
- Open Introduction.md and edit the first line from "# DWriteCore Sample Gallery" to "# DWriteCore Sample Gallery 1/3" and above the "# API Layers" add "1/3 1/3" so you get 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
# 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.
- Open MarkdownWindow.cpp and edit g_defaultStyle to use Arial:
const MarkdownStyle g_defaultStyle =
{
L"Arial", // headingFamilyName
L"Arial", // bodyFamilyName
L"Consolas", // codeFamilyName
g_defaultHeadingAxisValues,
g_defaultBodyAxisValues,
g_defaultBoldAxisValues,
g_defaultItalicAxisValues,
28.0f, // headingFontSize
14.0f, // bodyFontSize
13.0f // codeFontSize
};
- in the
while (inputPos < inputEnd)loop at line306, under thetextLayoutcreation add this:
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{ 0u, UINT32_MAX }));
Observe how it works properly (first screenshot).
- Now move the above code in the
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 thattextRangeholds the correct range. But the typographic feature does not work.
The loop now looks like this:
for (DWRITE_TEXT_RANGE textRange : boldRanges)
{
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(), textRange));
THROW_IF_FAILED(textLayout->SetFontAxisValues(style.boldAxisValues.data(), static_cast<uint32_t>(style.boldAxisValues.size()), textRange));
}
Which gives this result (second screenshot)
- You can also use
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
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the DWriteCoreGallery sample, editing Introduction.md and MarkdownWindow.cpp as described, then reproduce the issue around textLayout->SetTypography and DWRITE_TEXT_RANGE. Compare whole-text and boldRanges cases for fractions, enabling, and disabling features; done means typography applies only within the requested range and behaves consistently across font sizes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics, desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100