Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,33 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface

return success;
}

/// Checks whether the font face contains a COLR table by probing via
/// IDWriteFontFace::TryGetFontTable. The table data is immediately released
/// since we only need to know whether the table exists, not its contents.
/// TranslateColorGlyphRun does its own deep parsing of COLR/CPAL.
__declspec(noinline) bool FontFace::HasColorGlyphs()
{
const void* tableData;
void* tableContext;
UINT32 tableSize = 0;
BOOL exists = FALSE;

HRESULT hr = _fontFace->Value->TryGetFontTable(
DWRITE_MAKE_OPENTYPE_TAG('C','O','L','R'),
&tableData,
&tableSize,
&tableContext,
&exists
);

if (SUCCEEDED(hr) && exists)
{
_fontFace->Value->ReleaseFontTable(tableContext);
}

System::GC::KeepAlive(_fontFace);
return (!!exists);
}

}}}}//MS::Internal::Text::TextInterface
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ namespace MS { namespace Internal { namespace Text { namespace TextInterface
/// <returns>True if os2 table exists and the FontEmbeddingRights was read successfully.</returns>
bool ReadFontEmbeddingRights([System::Runtime::InteropServices::Out] unsigned short% fsType);

/// <summary>
/// Returns true if the font face contains a COLR (Color) OpenType table,
/// indicating it has color glyph layer definitions. Only checks for table
/// presence, not validity -- a corrupt COLR table would return true here
/// but fail during TranslateColorGlyphRun.
/// </summary>
bool HasColorGlyphs();

/// <summary>
/// dtor.
/// </summary>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

namespace MS.Internal.Interop.DWrite
{
[StructLayout(LayoutKind.Sequential)]
internal struct DWRITE_GLYPH_OFFSET
{
public float advanceOffset;
public float ascenderOffset;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DWRITE_GLYPH_RUN
{
public void* fontFace; // IDWriteFontFace*
public float fontEmSize;
public uint glyphCount;
public ushort* glyphIndices;
public float* glyphAdvances;
public DWRITE_GLYPH_OFFSET* glyphOffsets;
public int isSideways; // BOOL (4 bytes)
public uint bidiLevel;
}

[StructLayout(LayoutKind.Sequential)]
internal struct DWRITE_COLOR_F
{
public float r;
public float g;
public float b;
public float a;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DWRITE_COLOR_GLYPH_RUN
{
public DWRITE_GLYPH_RUN glyphRun;
public void* glyphRunDescription; // DWRITE_GLYPH_RUN_DESCRIPTION* (nullable)
public float baselineOriginX;
public float baselineOriginY;
public DWRITE_COLOR_F runColor;
public ushort paletteIndex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace MS.Internal.Interop.DWrite
{
/// <summary>
/// Managed interop for IDWriteColorGlyphRunEnumerator COM interface.
/// Vtable layout: IUnknown (0-2), MoveNext (3), GetCurrentRun (4).
/// </summary>
internal unsafe struct IDWriteColorGlyphRunEnumerator
{
public void** lpVtbl;

public uint Release()
{
return ((delegate* unmanaged<IDWriteColorGlyphRunEnumerator*, uint>)(lpVtbl[2]))((IDWriteColorGlyphRunEnumerator*)Unsafe.AsPointer(ref this));
Comment thread
etvorun marked this conversation as resolved.
}

public int MoveNext(int* hasRun)
{
return ((delegate* unmanaged<IDWriteColorGlyphRunEnumerator*, int*, int>)(lpVtbl[3]))((IDWriteColorGlyphRunEnumerator*)Unsafe.AsPointer(ref this), hasRun);
}

public int GetCurrentRun(DWRITE_COLOR_GLYPH_RUN** colorGlyphRun)
{
return ((delegate* unmanaged<IDWriteColorGlyphRunEnumerator*, DWRITE_COLOR_GLYPH_RUN**, int>)(lpVtbl[4]))((IDWriteColorGlyphRunEnumerator*)Unsafe.AsPointer(ref this), colorGlyphRun);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace MS.Internal.Text.TextInterface
{
/// <summary>
/// Represents a single color layer from a COLR v0 color glyph run decomposition.
/// </summary>
internal struct ColorGlyphLayer
{
public ushort[] GlyphIndices;
public float[] GlyphAdvances;
public float[] GlyphOffsets; // Interleaved (advanceOffset, ascenderOffset) pairs; may be null.
public float BaselineOriginX;
public float BaselineOriginY;
public float ColorR;
public float ColorG;
public float ColorB;
public float ColorA;
public bool UseForegroundColor;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Runtime.InteropServices;
using MS.Internal.Interop;
using MS.Internal.Interop.DWrite;
Expand Down Expand Up @@ -35,6 +36,13 @@ internal unsafe class Factory

private readonly IFontSourceFactory _fontSourceFactory;

/// <summary>
/// Pointer to the IDWriteFactory2 interface, or null if not available.
/// Used for COLR v0 color glyph support (TranslateColorGlyphRun).
/// Available on Windows 8.1+.
/// </summary>
private void* _pFactory2;

/// <summary>
/// Constructs a factory object.
/// </summary>
Expand All @@ -50,6 +58,16 @@ private Factory(FactoryType factoryType, IFontSourceCollectionFactory fontSource
{
Initialize(factoryType);

// QueryInterface for IDWriteFactory2, which provides TranslateColorGlyphRun
// for COLR v0 color glyph decomposition. Available on Windows 8.1+; on older
// versions the QI returns E_NOINTERFACE and _pFactory2 stays null, which
// disables color glyph rendering gracefully.
Guid iidFactory2 = new Guid(0x0439fc60, 0xca44, 0x4994, 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec);
void* pFactory2 = null;
int qiHr = _factory.Value->QueryInterface(&iidFactory2, &pFactory2);
if (qiHr == 0 && pFactory2 != null)
_pFactory2 = pFactory2;

_wpfFontFileLoader = new FontFileLoader(fontSourceFactory);
_wpfFontCollectionLoader = new FontCollectionLoader(
fontSourceCollectionFactory,
Expand Down Expand Up @@ -330,6 +348,179 @@ internal TextAnalyzer CreateTextAnalyzer()
return new TextAnalyzer((Native.IDWriteTextAnalyzer*)textAnalyzer);
}

/// <summary>
/// Gets whether this factory supports color glyph rendering (IDWriteFactory2+).
/// </summary>
internal bool SupportsColorGlyphs => _pFactory2 != null;

// DWRITE_E_NOCOLOR: font has no COLR table or glyphs have no color layers.
private const int DWRITE_E_NOCOLOR = unchecked((int)0x8898500C);

/// <summary>
/// Translates a glyph run into color glyph layers using IDWriteFactory2.
/// Returns null if the glyph run has no color layers or color glyphs are not supported.
/// </summary>
internal List<ColorGlyphLayer> TranslateColorGlyphRun(
FontFace fontFace,
float fontEmSize,
ushort[] glyphIndices,
float[] glyphAdvances,
float[] glyphOffsets,
uint glyphCount,
bool isSideways,
uint bidiLevel,
float baselineOriginX,
float baselineOriginY,
uint measuringMode)
{
if (_pFactory2 == null || fontFace == null ||
glyphIndices == null || glyphAdvances == null || glyphCount == 0 ||
glyphCount > (uint)int.MaxValue ||
glyphIndices.Length < (int)glyphCount ||
glyphAdvances.Length < (int)glyphCount)
{
return null;
}

IDWriteColorGlyphRunEnumerator* pColorEnum = null;
try
{
fixed (ushort* pGlyphIndices = glyphIndices)
fixed (float* pGlyphAdvances = glyphAdvances)
{
// Convert interleaved float pairs to DWRITE_GLYPH_OFFSET structs.
DWRITE_GLYPH_OFFSET* pOffsets = null;
DWRITE_GLYPH_OFFSET* allocatedOffsets = null;

try
{
if (glyphOffsets != null && glyphCount <= (uint)(glyphOffsets.Length / 2))
{
allocatedOffsets = (DWRITE_GLYPH_OFFSET*)Marshal.AllocHGlobal(
Comment thread
etvorun marked this conversation as resolved.
(int)(glyphCount * (uint)sizeof(DWRITE_GLYPH_OFFSET)));

fixed (float* pGlyphOffsets = glyphOffsets)
{
for (uint i = 0; i < glyphCount; i++)
{
allocatedOffsets[i].advanceOffset = pGlyphOffsets[i * 2];
allocatedOffsets[i].ascenderOffset = pGlyphOffsets[i * 2 + 1];
}
}
pOffsets = allocatedOffsets;
}

DWRITE_GLYPH_RUN glyphRun;
glyphRun.fontFace = (void*)fontFace.DWriteFontFaceAddRef;
glyphRun.fontEmSize = fontEmSize;
glyphRun.glyphCount = glyphCount;
glyphRun.glyphIndices = pGlyphIndices;
glyphRun.glyphAdvances = pGlyphAdvances;
glyphRun.glyphOffsets = pOffsets;
glyphRun.isSideways = isSideways ? 1 : 0;
glyphRun.bidiLevel = bidiLevel;

void* pEnum = null;

// IDWriteFactory2::TranslateColorGlyphRun is vtable slot 28.
void** factory2Vtbl = *(void***)_pFactory2;
int hr = ((delegate* unmanaged<void*, float, float, DWRITE_GLYPH_RUN*, void*, int, void*, uint, void**, int>)(factory2Vtbl[28]))(
_pFactory2,
baselineOriginX, baselineOriginY,
&glyphRun, null,
(int)measuringMode, null,
0, &pEnum);
Comment thread
etvorun marked this conversation as resolved.
Outdated
Comment thread
etvorun marked this conversation as resolved.
Outdated

// Release the AddRef'd font face now that the DWrite call is complete.
((IDWriteFontFace*)glyphRun.fontFace)->Release();
glyphRun.fontFace = null;

GC.KeepAlive(fontFace);
GC.KeepAlive(this);

if (hr == DWRITE_E_NOCOLOR || hr < 0 || pEnum == null)
{
if (pEnum != null)
((IDWriteColorGlyphRunEnumerator*)pEnum)->Release();
return null;
}

pColorEnum = (IDWriteColorGlyphRunEnumerator*)pEnum;
}
finally
{
if (allocatedOffsets != null)
Marshal.FreeHGlobal((IntPtr)allocatedOffsets);
}
}

// Enumerate color layers. DirectWrite returns them in back-to-front paint order.
const int MaxColorLayers = 1024;
var layers = new List<ColorGlyphLayer>();

int hasRun = 0;
while (pColorEnum->MoveNext(&hasRun) >= 0 && hasRun != 0 && layers.Count < MaxColorLayers)
{
DWRITE_COLOR_GLYPH_RUN* pColorRun = null;
int runHr = pColorEnum->GetCurrentRun(&pColorRun);
if (runHr < 0 || pColorRun == null)
break;

ColorGlyphLayer layer;
layer.BaselineOriginX = pColorRun->baselineOriginX;
layer.BaselineOriginY = pColorRun->baselineOriginY;

// paletteIndex 0xFFFF means "use the text foreground color".
layer.UseForegroundColor = (pColorRun->paletteIndex == 0xFFFF);

// Clamp RGBA to [0,1].
layer.ColorR = Clamp01(pColorRun->runColor.r);
layer.ColorG = Clamp01(pColorRun->runColor.g);
layer.ColorB = Clamp01(pColorRun->runColor.b);
layer.ColorA = Clamp01(pColorRun->runColor.a);

uint layerGlyphCount = pColorRun->glyphRun.glyphCount;

// Deep-copy glyph data (pointer is only valid until next MoveNext).
layer.GlyphIndices = new ushort[layerGlyphCount];
for (uint i = 0; i < layerGlyphCount; i++)
layer.GlyphIndices[i] = pColorRun->glyphRun.glyphIndices[i];
Comment thread
etvorun marked this conversation as resolved.

layer.GlyphAdvances = new float[layerGlyphCount];
if (pColorRun->glyphRun.glyphAdvances != null)
{
for (uint i = 0; i < layerGlyphCount; i++)
layer.GlyphAdvances[i] = pColorRun->glyphRun.glyphAdvances[i];
}

if (pColorRun->glyphRun.glyphOffsets != null)
{
layer.GlyphOffsets = new float[layerGlyphCount * 2];
for (uint i = 0; i < layerGlyphCount; i++)
{
layer.GlyphOffsets[i * 2] = pColorRun->glyphRun.glyphOffsets[i].advanceOffset;
layer.GlyphOffsets[i * 2 + 1] = pColorRun->glyphRun.glyphOffsets[i].ascenderOffset;
}
}
else
{
layer.GlyphOffsets = null;
Comment thread
etvorun marked this conversation as resolved.
}

layers.Add(layer);
}

return layers.Count > 0 ? layers : null;
}
finally
{
if (pColorEnum != null)
pColorEnum->Release();
}
}

private static float Clamp01(float v) => v < 0f ? 0f : v > 1f ? 1f : v;
Comment thread
etvorun marked this conversation as resolved.

internal static bool IsLocalUri(Uri uri)
{
return uri.IsFile && uri.IsLoopback && !uri.IsUnc;
Expand Down Expand Up @@ -371,6 +562,13 @@ protected override bool ReleaseHandle()
_managedFactory._wpfFontFileLoader = null;
}

// Release the IDWriteFactory2 interface obtained via QueryInterface.
if (_managedFactory._pFactory2 != null)
{
((IDWriteFactory*)_managedFactory._pFactory2)->Release();
_managedFactory._pFactory2 = null;
}

return base.ReleaseHandle();
}
}
Expand Down
Loading