DirectWrite Text Backend (Windows)¶
WhatsCanvas ships a native Windows text backend built on DirectWrite (shaping, layout, metrics, font enumeration) and Direct2D + WIC (headless glyph rasterization). It is an alternative to the portable FreeType/stb glyph-atlas backend and produces high-quality, OS-consistent text on Windows.
Selecting the backend¶
Select it from the public Canvas API (portable is the default):
// Native DirectWrite with ClearType, else portable fallback off Windows.
canvas->setTextBackend(wsc::Canvas::TextBackend::DirectWrite,
wsc::Canvas::TextRenderMode::ClearType);
if (canvas->textBackend() == wsc::Canvas::TextBackend::DirectWrite) {
// native backend active
}
setTextBackend resets text state (registered fonts / fallback chains), so call
it before registering fonts. A DirectWrite request falls back to the portable
glyph-atlas backend when DirectWrite is unavailable.
The backend can also be created directly through the internal factory:
#include "text/DirectWriteTextBackend.h"
if (wsc::text::isDirectWriteAvailable()) { // true on _WIN32
wsc::text::DirectWriteBackendOptions options;
options.rasterMode = wsc::text::DirectWriteRasterMode::Grayscale; // or ClearType
std::unique_ptr<wsc::text::ITextBackend> backend =
wsc::text::createDirectWriteTextBackend(options); // nullptr off Windows
}
What it does¶
- Shaping & layout via
IDWriteTextLayout(complex scripts, bidi, kerning). - Metrics — width/height, and ascent/descent/lineGap from the font's design metrics.
- Rasterization into an RGBA bitmap through a Direct2D WIC render target.
- System font fallback —
IDWriteTextLayoutresolves missing glyphs automatically.
Raster modes¶
| Mode | Use when | Notes |
|---|---|---|
Grayscale (default) |
Any surface, transforms, animation | Coverage in the alpha channel; white RGB. Safe and portable. |
ClearType |
Axis-aligned text over a known opaque background | Preserves horizontal RGB subpixel coverage; best-effort composite. Avoid under alpha blending / transforms (colored fringes). |
Text styling surface¶
All of these Paint knobs flow through to DirectWrite:
setFontFamily/setFontWeight(100–900) /setFontSlant(normal/italic/oblique)setTextSizesetLetterSpacing— baked into the layout viaIDWriteTextLayout1::SetCharacterSpacing, so measurement and rendering stay consistentsetTextAlign/setTextBaselinesetTextLocale(BCP-47, e.g."en-US","ja-JP") — locale-aware shaping and Han-unification fallbacksetFontFeature— whole-run, case-sensitive four-byte OpenType tags are applied throughIDWriteTypographyand included in bitmap cache keys. There is no whitelist; unsupported features have no effect. Value0disables, value1normally enables, and other values are passed through for feature-specific alternate selection. See the Text Feature Matrix.setUnderline/setStrikethrough— text decorations drawn by DirectWrite over the runsetTextRenderMode— per-Paint raster mode override (Grayscale/ClearType/Default= inherit backend setting). Lets a single canvas mix ClearType body text with grayscale labels/animated text; distinct modes get distinct cache entries.
Custom fonts¶
registerFontFace accepts both on-disk and in-memory fonts:
const std::string fontPath = resolveApplicationFont("Inter-Regular.ttf");
backend->registerFontFace(wsc::FontFace::fromFile(wsc::FontDescriptor("Inter"),
fontPath));
std::vector<std::uint8_t> bytes = /* load a .ttf/.otf */;
backend->registerFontFace(wsc::FontFace::fromMemory(wsc::FontDescriptor("Inter"), bytes));
- On-disk fonts use a custom
IDWriteFontCollectionLoader+ file enumerator. - In-memory fonts use
IDWriteInMemoryFontFileLoader(IDWriteFactory5, Windows 10+); unavailable factories degrade gracefully (returnsfalse). - A registered family is preferred over the system collection; the system collection still drives automatic fallback.
Refreshing installed fonts¶
Applications that install or remove fonts while a canvas is alive can refresh the process snapshot and the native DirectWrite collection without recreating the canvas:
const std::uint64_t before = wsc::FontSystem::installedFontGeneration();
if (canvas->refreshSystemFonts()) {
const std::uint64_t after = wsc::FontSystem::installedFontGeneration();
// after > before; application-registered fonts and fallback chains remain.
}
Refreshing invalidates text/atlas bitmap caches whose results may depend on the installed collection. Explicit file-backed and memory-backed font registrations and custom fallback chains are preserved.
Custom font fallback chains¶
wsc::FontFallbackChain chain("Inter");
chain.addFallbackFamily("Yu Gothic"); // Japanese
chain.addFallbackFamily("Microsoft YaHei"); // Simplified Chinese
backend->setFontFallbackChain(chain);
Builds an IDWriteFontFallback (via IDWriteFontFallbackBuilder) mapping the
Unicode range to the requested families in order, then appends the system
fallback so any uncovered scripts still resolve. An empty chain clears it.
HiDPI¶
DirectWrite text participates in the same device-resolution path as the portable
backend: under a scaled transform or setDevicePixelRatio, glyphs rasterize at
the effective device pixel size and stay crisp. See
Text Sharpness & HiDPI.
Known limitations¶
- Bitmap per draw: text is rasterized to a CPU bitmap and uploaded as a GPU
texture on the first
drawTextfor a given styled string. Three caches keep the amortized cost low for repeated UI text: the COM apartment plus the WIC/D2D factories live for the process; identical(text, paint, dpr, mode)reuses a cached bitmap + intrinsic metrics on the backend (LRU, 4 MB byte budget by default); andCanvaskeeps a bounded LRU (256 entries) of GPU textures keyed by the backend-provided content id, so repeat renders skip the CPU→GPU upload entirely. Only a shared GPU glyph atlas that batches distinct same-frame text into one texture remains a future optimization. - ClearType is best-effort: it renders white-on-opaque-black and derives an alpha from the brightest subpixel, so true subpixel sharpness only holds for axis-aligned text over an opaque destination.