---
layout: post.liquid
title: Loading a WOFF font in SkiaSharp on Windows
published_date: 2022-03-01 08:23:00 -0800
---
Recently I found myself trying to use a font in WOFF format with SkiaSharp.
SkiaSharp does not natively support WOFF fonts. Fortunatly the DirectWrite
component in Windows can turn these fonts into the SFNT (aka TrueType) format
that SkiaSharp supports.
There should be a way to do this that does not invlove using Windows-specific
components. [This JavaScript converter tool](https://github.com/odemiral/woff2sfnt-sfnt2woff)
does not have a lot of code in it. So it should be possible to create a pure-C#
implementation of the font conversion. But I could not find one and this code
only has to run on Windows.
## Code
I'm using [CsWin32](https://github.com/microsoft/CsWin32) to generate the native
interop bindings. I put this code in a separate library, as it seems like the
source generator slows down Visual Studio quite a bit. Hopefully the CsWin32
source generator converts to an [incremental generator](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md)
and this becomes unnecessary.
First we define the dependency in our `csproj` file:
```xml
net6.0-windows7.0
enable
all
runtime; build; native; contentfiles; analyzers; buildtransitive
```
Next we define which native APIs we want to call in `NativeMethods.txt`:
```
IDWriteFactory5
DWriteCreateFactory
```
And now we can extract the font from the WOFF file:
```c#
using SkiaSharp;
using System;
using Windows.Win32;
using Windows.Win32.Graphics.DirectWrite;
namespace FontInterop;
public static class WoffFontLoader
{
static readonly Guid IID_IDWriteFactory5 = Guid.Parse("958DB99A-BE2A-4F09-AF7D-65189803D1D3");
public unsafe static SKTypeface LoadWoffFont(ReadOnlySpan woffFont)
{
object factory;
var hr = PInvoke.DWriteCreateFactory(
DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED,
IID_IDWriteFactory5,
out factory);
hr.ThrowOnFailure();
var writerFactory = (IDWriteFactory5)factory;
fixed (byte* pBytes = woffFont)
{
IDWriteFontFileStream stream;
writerFactory.UnpackFontFile(
DWRITE_CONTAINER_TYPE.DWRITE_CONTAINER_TYPE_WOFF,
(void*)pBytes,
(uint)woffFont.Length,
out stream);
ulong fileSize = 0;
stream.GetFileSize(&fileSize);
void* fragStart = (void*)IntPtr.Zero;
void* cookie = (void*)IntPtr.Zero;
try
{
stream.ReadFileFragment(&fragStart, 0, fileSize, &cookie);
var data = SKData.CreateCopy((IntPtr)fragStart, (int)fileSize);
return SKTypeface.FromData(data);
}
finally
{
if (new IntPtr(cookie) != IntPtr.Zero)
{
stream.ReleaseFileFragment(cookie);
}
}
}
}
}
```
This COM interop could probably be done better.
Perhaps the `IDWriteFontFileStream` should be cleaned eagerly up with `Marshal.ReleaseComObject`
Perhaps the `IDWriteFactory5` should be
created once and cached. But for my short-lived console program, this runs fast
enough as is.