diff --git a/CADability/CffOutlines.cs b/CADability/CffOutlines.cs
new file mode 100644
index 00000000..d5bfeee2
--- /dev/null
+++ b/CADability/CffOutlines.cs
@@ -0,0 +1,505 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace CADability
+{
+ ///
+ /// Receives the outline of a glyph in font units: contours of lines and cubic Bezier segments.
+ ///
+ internal interface IGlyphOutlineSink
+ {
+ void MoveTo(double x, double y);
+ void LineTo(double x, double y);
+ void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3);
+ void ClosePath();
+ }
+
+ ///
+ /// Reads the glyph outlines of the "CFF " table of an OpenType font: the Compact Font Format with Type 2 charstrings, as used
+ /// by many .otf files. Name-keyed and CID-keyed fonts (FDArray, FDSelect) are supported, with local and global subroutines,
+ /// hint masks and the flex operators. Hints are skipped, they do not change the outline. Not supported: CFF2 (variable fonts)
+ /// and the deprecated accent composition of endchar (seac), which modern fonts do not use.
+ ///
+ internal sealed class CffOutlines
+ {
+ private readonly byte[] data;
+ private readonly List<(int start, int length)> charStrings;
+ private readonly List<(int start, int length)> globalSubrs;
+ private readonly List> localSubrs = new List>(); // per font dict
+ private readonly byte[] fdSelect; // font dict index per glyph, null for a name-keyed font
+ private readonly double scale; // FontMatrix * units per em, 1 in almost all fonts
+
+ ///
+ /// The number of glyphs (charstrings).
+ ///
+ public int GlyphCount => charStrings.Count;
+ ///
+ /// True for a CID-keyed font.
+ ///
+ public bool IsCid => fdSelect != null;
+
+ /// the font file
+ /// the start of the "CFF " table
+ /// its length
+ /// from the "head" table, to check the FontMatrix
+ public CffOutlines(byte[] data, int offset, int length, int unitsPerEm)
+ {
+ this.data = data;
+ int end = offset + length;
+ if (length < 4 || data[offset] != 1) throw new InvalidDataException("not a CFF table (CFF2 is not supported)");
+ int pos = offset + data[offset + 2]; // header size
+ ReadIndex(ref pos, end); // names
+ List<(int start, int length)> topDicts = ReadIndex(ref pos, end);
+ ReadIndex(ref pos, end); // strings
+ globalSubrs = ReadIndex(ref pos, end);
+ if (topDicts.Count == 0) throw new InvalidDataException("CFF table without a font");
+ Dictionary top = ReadDict(topDicts[0].start, topDicts[0].length);
+ if (!top.TryGetValue(17, out double[] charStringsOffset)) throw new InvalidDataException("CFF font without charstrings");
+ if (top.TryGetValue(1206, out double[] charstringType) && charstringType[0] != 2) throw new InvalidDataException("only Type 2 charstrings are supported");
+ int csPos = offset + (int)charStringsOffset[0];
+ charStrings = ReadIndex(ref csPos, end);
+ scale = 1.0;
+ if (top.TryGetValue(1207, out double[] fontMatrix) && fontMatrix.Length >= 4 && fontMatrix[0] != 0)
+ {
+ // the FontMatrix is stored rounded (e.g. 0.00048828 for 1/2048): a factor this close to 1 is 1
+ double s = fontMatrix[0] * unitsPerEm;
+ if (Math.Abs(s - 1.0) > 1e-3) scale = s;
+ }
+ if (top.TryGetValue(1236, out double[] fdArrayOffset) && top.TryGetValue(1237, out double[] fdSelectOffset))
+ { // CID-keyed: one private dict (with its own local subroutines) per font dict
+ int fdPos = offset + (int)fdArrayOffset[0];
+ foreach (var fd in ReadIndex(ref fdPos, end)) localSubrs.Add(ReadLocalSubrs(offset, ReadDict(fd.start, fd.length), end));
+ fdSelect = ReadFdSelect(offset + (int)fdSelectOffset[0], charStrings.Count, end);
+ }
+ else localSubrs.Add(ReadLocalSubrs(offset, top, end));
+ }
+
+ ///
+ /// Sends the outline of to .
+ ///
+ public void Draw(int glyph, IGlyphOutlineSink sink)
+ {
+ if (glyph < 0 || glyph >= charStrings.Count) return;
+ int fd = 0;
+ if (fdSelect != null)
+ {
+ fd = fdSelect[glyph];
+ if (fd >= localSubrs.Count) throw new InvalidDataException("invalid FDSelect");
+ }
+ new Interpreter(this, localSubrs[fd], scale == 1.0 ? sink : new ScalingSink(sink, scale)).Run(charStrings[glyph]);
+ }
+
+ private List<(int start, int length)> ReadLocalSubrs(int cffOffset, Dictionary dict, int end)
+ {
+ if (!dict.TryGetValue(18, out double[] priv) || priv.Length < 2) return new List<(int, int)>();
+ int privSize = (int)priv[0], privOffset = cffOffset + (int)priv[1];
+ if (privSize <= 0) return new List<(int, int)>();
+ Dictionary privateDict = ReadDict(privOffset, privSize);
+ if (!privateDict.TryGetValue(19, out double[] subrs)) return new List<(int, int)>();
+ int pos = privOffset + (int)subrs[0]; // relative to the private dict
+ return ReadIndex(ref pos, end);
+ }
+ private byte[] ReadFdSelect(int pos, int numGlyphs, int end)
+ {
+ byte[] res = new byte[numGlyphs];
+ int format = data[pos];
+ if (format == 0)
+ {
+ if (pos + 1 + numGlyphs > end) throw new InvalidDataException("invalid FDSelect");
+ Array.Copy(data, pos + 1, res, 0, numGlyphs);
+ }
+ else if (format == 3)
+ {
+ int nRanges = U16(pos + 1);
+ for (int i = 0; i < nRanges; ++i)
+ {
+ int r = pos + 3 + 3 * i;
+ int first = U16(r), fd = data[r + 2], next = U16(r + 3);
+ for (int g = first; g < next && g < numGlyphs; ++g) res[g] = (byte)fd;
+ }
+ }
+ else throw new InvalidDataException("unsupported FDSelect format " + format);
+ return res;
+ }
+ private List<(int start, int length)> ReadIndex(ref int pos, int end)
+ {
+ List<(int, int)> res = new List<(int, int)>();
+ if (pos + 2 > end) throw new InvalidDataException("CFF INDEX exceeds the table");
+ int count = U16(pos);
+ if (count == 0)
+ {
+ pos += 2;
+ return res;
+ }
+ int offSize = data[pos + 2];
+ if (offSize < 1 || offSize > 4) throw new InvalidDataException("invalid CFF INDEX");
+ int offsets = pos + 3;
+ int dataStart = offsets + (count + 1) * offSize - 1; // offsets count from 1
+ int Offset(int i)
+ {
+ int v = 0;
+ for (int k = 0; k < offSize; ++k) v = (v << 8) | data[offsets + i * offSize + k];
+ return v;
+ }
+ for (int i = 0; i < count; ++i)
+ {
+ int s = Offset(i), e = Offset(i + 1);
+ if (e < s || dataStart + e > end) throw new InvalidDataException("invalid CFF INDEX");
+ res.Add((dataStart + s, e - s));
+ }
+ pos = dataStart + Offset(count);
+ return res;
+ }
+ ///
+ /// Reads a DICT: operator (two-byte operators as 1200 + second byte) to its operands.
+ ///
+ private Dictionary ReadDict(int pos, int length)
+ {
+ Dictionary res = new Dictionary();
+ List operands = new List();
+ int end = pos + length;
+ if (end > data.Length) throw new InvalidDataException("CFF DICT exceeds the file");
+ while (pos < end)
+ {
+ int b0 = data[pos++];
+ if (b0 <= 21)
+ {
+ int op = b0;
+ if (b0 == 12) op = 1200 + data[pos++];
+ res[op] = operands.ToArray();
+ operands.Clear();
+ }
+ else if (b0 == 28) { operands.Add((short)((data[pos] << 8) | data[pos + 1])); pos += 2; }
+ else if (b0 == 29) { operands.Add((data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]); pos += 4; }
+ else if (b0 == 30) operands.Add(ReadReal(ref pos));
+ else if (b0 >= 32 && b0 <= 246) operands.Add(b0 - 139);
+ else if (b0 >= 247 && b0 <= 250) operands.Add((b0 - 247) * 256 + data[pos++] + 108);
+ else if (b0 >= 251 && b0 <= 254) operands.Add(-(b0 - 251) * 256 - data[pos++] - 108);
+ else throw new InvalidDataException("invalid CFF DICT");
+ }
+ return res;
+ }
+ private double ReadReal(ref int pos)
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ while (true)
+ {
+ int b = data[pos++];
+ foreach (int nibble in new[] { b >> 4, b & 0xF })
+ {
+ if (nibble <= 9) sb.Append((char)('0' + nibble));
+ else if (nibble == 0xA) sb.Append('.');
+ else if (nibble == 0xB) sb.Append('E');
+ else if (nibble == 0xC) sb.Append("E-");
+ else if (nibble == 0xE) sb.Append('-');
+ else if (nibble == 0xF)
+ {
+ double.TryParse(sb.ToString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double v);
+ return v;
+ }
+ }
+ }
+ }
+ private int U16(int pos) => (data[pos] << 8) | data[pos + 1];
+ private static int Bias(int count) => count < 1240 ? 107 : count < 33900 ? 1131 : 32768;
+
+ private sealed class ScalingSink : IGlyphOutlineSink
+ {
+ private readonly IGlyphOutlineSink sink;
+ private readonly double s;
+ public ScalingSink(IGlyphOutlineSink sink, double s) { this.sink = sink; this.s = s; }
+ public void MoveTo(double x, double y) => sink.MoveTo(x * s, y * s);
+ public void LineTo(double x, double y) => sink.LineTo(x * s, y * s);
+ public void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3) => sink.CurveTo(x1 * s, y1 * s, x2 * s, y2 * s, x3 * s, y3 * s);
+ public void ClosePath() => sink.ClosePath();
+ }
+
+ ///
+ /// The Type 2 charstring interpreter (Adobe Technical Note #5177).
+ ///
+ private sealed class Interpreter
+ {
+ private readonly CffOutlines font;
+ private readonly List<(int start, int length)> local;
+ private readonly IGlyphOutlineSink sink;
+ private readonly List stack = new List(48);
+ private readonly double[] transient = new double[32];
+ private double x, y;
+ private int stems;
+ private bool haveWidth, open, finished;
+ private int depth;
+ private readonly Random random = new Random(0);
+
+ public Interpreter(CffOutlines font, List<(int start, int length)> local, IGlyphOutlineSink sink)
+ {
+ this.font = font;
+ this.local = local;
+ this.sink = sink;
+ }
+ public void Run((int start, int length) charString)
+ {
+ Execute(charString.start, charString.start + charString.length);
+ if (open) sink.ClosePath();
+ open = false;
+ }
+
+ private void Execute(int pos, int end)
+ {
+ if (++depth > 12) throw new InvalidDataException("CFF subroutines nested too deeply");
+ byte[] d = font.data;
+ while (pos < end && !finished)
+ {
+ int b0 = d[pos++];
+ if (b0 >= 32 || b0 == 28)
+ { // an operand
+ if (b0 == 28) { stack.Add((short)((d[pos] << 8) | d[pos + 1])); pos += 2; }
+ else if (b0 <= 246) stack.Add(b0 - 139);
+ else if (b0 <= 250) stack.Add((b0 - 247) * 256 + d[pos++] + 108);
+ else if (b0 <= 254) stack.Add(-(b0 - 251) * 256 - d[pos++] - 108);
+ else { stack.Add(((d[pos] << 24) | (d[pos + 1] << 16) | (d[pos + 2] << 8) | d[pos + 3]) / 65536.0); pos += 4; }
+ continue;
+ }
+ switch (b0)
+ {
+ case 1: // hstem
+ case 3: // vstem
+ case 18: // hstemhm
+ case 23: // vstemhm
+ Stems();
+ break;
+ case 19: // hintmask
+ case 20: // cntrmask
+ Stems(); // the operands of an implicit vstemhm
+ pos += (stems + 7) / 8;
+ break;
+ case 21: // rmoveto
+ Width(2);
+ MoveTo(x + Arg(0), y + Arg(1));
+ stack.Clear();
+ break;
+ case 22: // hmoveto
+ Width(1);
+ MoveTo(x + Arg(0), y);
+ stack.Clear();
+ break;
+ case 4: // vmoveto
+ Width(1);
+ MoveTo(x, y + Arg(0));
+ stack.Clear();
+ break;
+ case 5: // rlineto
+ for (int i = 0; i + 1 < stack.Count; i += 2) LineTo(x + stack[i], y + stack[i + 1]);
+ stack.Clear();
+ break;
+ case 6: // hlineto
+ case 7: // vlineto
+ {
+ bool horizontal = b0 == 6;
+ for (int i = 0; i < stack.Count; ++i, horizontal = !horizontal)
+ {
+ if (horizontal) LineTo(x + stack[i], y);
+ else LineTo(x, y + stack[i]);
+ }
+ stack.Clear();
+ break;
+ }
+ case 8: // rrcurveto
+ for (int i = 0; i + 5 < stack.Count; i += 6) Curve(stack[i], stack[i + 1], stack[i + 2], stack[i + 3], stack[i + 4], stack[i + 5]);
+ stack.Clear();
+ break;
+ case 24: // rcurveline
+ {
+ int i = 0;
+ for (; i + 7 < stack.Count; i += 6) Curve(stack[i], stack[i + 1], stack[i + 2], stack[i + 3], stack[i + 4], stack[i + 5]);
+ if (i + 1 < stack.Count) LineTo(x + stack[i], y + stack[i + 1]);
+ stack.Clear();
+ break;
+ }
+ case 25: // rlinecurve
+ {
+ int i = 0;
+ for (; i + 7 < stack.Count; i += 2) LineTo(x + stack[i], y + stack[i + 1]);
+ if (i + 5 < stack.Count) Curve(stack[i], stack[i + 1], stack[i + 2], stack[i + 3], stack[i + 4], stack[i + 5]);
+ stack.Clear();
+ break;
+ }
+ case 26: // vvcurveto
+ {
+ int i = 0;
+ double dx1 = 0;
+ if (stack.Count % 2 == 1) dx1 = stack[i++];
+ for (; i + 3 < stack.Count; i += 4)
+ {
+ Curve(dx1, stack[i], stack[i + 1], stack[i + 2], 0, stack[i + 3]);
+ dx1 = 0;
+ }
+ stack.Clear();
+ break;
+ }
+ case 27: // hhcurveto
+ {
+ int i = 0;
+ double dy1 = 0;
+ if (stack.Count % 2 == 1) dy1 = stack[i++];
+ for (; i + 3 < stack.Count; i += 4)
+ {
+ Curve(stack[i], dy1, stack[i + 1], stack[i + 2], stack[i + 3], 0);
+ dy1 = 0;
+ }
+ stack.Clear();
+ break;
+ }
+ case 30: // vhcurveto
+ case 31: // hvcurveto
+ {
+ bool horizontal = b0 == 31;
+ int i = 0;
+ while (i + 3 < stack.Count)
+ {
+ double extra = stack.Count - i == 5 ? stack[i + 4] : 0; // the last curve may have a fifth operand
+ if (horizontal) Curve(stack[i], 0, stack[i + 1], stack[i + 2], extra, stack[i + 3]);
+ else Curve(0, stack[i], stack[i + 1], stack[i + 2], stack[i + 3], extra);
+ horizontal = !horizontal;
+ i += 4;
+ }
+ stack.Clear();
+ break;
+ }
+ case 10: // callsubr
+ case 29: // callgsubr
+ {
+ List<(int start, int length)> subrs = b0 == 10 ? local : font.globalSubrs;
+ int index = (int)Pop() + Bias(subrs.Count);
+ if (index < 0 || index >= subrs.Count) throw new InvalidDataException("invalid CFF subroutine index");
+ Execute(subrs[index].start, subrs[index].start + subrs[index].length);
+ break;
+ }
+ case 11: // return
+ --depth;
+ return;
+ case 14: // endchar (the accent composition "seac" with four operands is not supported)
+ if (!haveWidth && (stack.Count == 1 || stack.Count == 5)) stack.RemoveAt(0);
+ haveWidth = true;
+ if (open) sink.ClosePath();
+ open = false;
+ finished = true;
+ stack.Clear();
+ break;
+ case 12:
+ Escape(d[pos++]);
+ break;
+ default: // reserved operators are ignored
+ stack.Clear();
+ break;
+ }
+ }
+ --depth;
+ }
+
+ private void Escape(int op)
+ {
+ switch (op)
+ {
+ case 35: // flex
+ Curve(Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5));
+ Curve(Arg(6), Arg(7), Arg(8), Arg(9), Arg(10), Arg(11));
+ break;
+ case 34: // hflex
+ Curve(Arg(0), 0, Arg(1), Arg(2), Arg(3), 0);
+ Curve(Arg(4), 0, Arg(5), -Arg(2), Arg(6), 0);
+ break;
+ case 36: // hflex1
+ {
+ double dy1 = Arg(1), dy2 = Arg(3), dy5 = Arg(7);
+ Curve(Arg(0), dy1, Arg(2), dy2, Arg(4), 0);
+ Curve(Arg(5), 0, Arg(6), dy5, Arg(8), -(dy1 + dy2 + dy5));
+ break;
+ }
+ case 37: // flex1
+ {
+ double dx = 0, dy = 0;
+ for (int i = 0; i < 10; i += 2) { dx += Arg(i); dy += Arg(i + 1); }
+ Curve(Arg(0), Arg(1), Arg(2), Arg(3), Arg(4), Arg(5));
+ if (Math.Abs(dx) > Math.Abs(dy)) Curve(Arg(6), Arg(7), Arg(8), Arg(9), Arg(10), -dy);
+ else Curve(Arg(6), Arg(7), Arg(8), Arg(9), -dx, Arg(10));
+ break;
+ }
+ // arithmetic and storage operators, rarely used
+ case 3: { double b = Pop(), a = Pop(); stack.Add(a != 0 && b != 0 ? 1 : 0); return; } // and
+ case 4: { double b = Pop(), a = Pop(); stack.Add(a != 0 || b != 0 ? 1 : 0); return; } // or
+ case 5: stack.Add(Pop() == 0 ? 1 : 0); return; // not
+ case 9: stack.Add(Math.Abs(Pop())); return; // abs
+ case 10: { double b = Pop(), a = Pop(); stack.Add(a + b); return; } // add
+ case 11: { double b = Pop(), a = Pop(); stack.Add(a - b); return; } // sub
+ case 12: { double b = Pop(), a = Pop(); stack.Add(b == 0 ? 0 : a / b); return; } // div
+ case 14: stack.Add(-Pop()); return; // neg
+ case 15: { double b = Pop(), a = Pop(); stack.Add(a == b ? 1 : 0); return; } // eq
+ case 18: Pop(); return; // drop
+ case 20: { int i = (int)Pop(); double v = Pop(); if (i >= 0 && i < transient.Length) transient[i] = v; return; } // put
+ case 21: { int i = (int)Pop(); stack.Add(i >= 0 && i < transient.Length ? transient[i] : 0); return; } // get
+ case 22: { double v2 = Pop(), v1 = Pop(), s2 = Pop(), s1 = Pop(); stack.Add(v1 <= v2 ? s1 : s2); return; } // ifelse
+ case 23: stack.Add(random.NextDouble() + double.Epsilon); return; // random
+ case 24: { double b = Pop(), a = Pop(); stack.Add(a * b); return; } // mul
+ case 26: stack.Add(Math.Sqrt(Math.Abs(Pop()))); return; // sqrt
+ case 27: { double a = Pop(); stack.Add(a); stack.Add(a); return; } // dup
+ case 28: { double b = Pop(), a = Pop(); stack.Add(b); stack.Add(a); return; } // exch
+ case 29: { int i = (int)Pop(); stack.Add(i >= 0 && i < stack.Count ? stack[stack.Count - 1 - i] : stack.Count > 0 ? stack[stack.Count - 1] : 0); return; } // index
+ case 30: // roll
+ {
+ int j = (int)Pop(), n = (int)Pop();
+ if (n > 0 && n <= stack.Count)
+ {
+ double[] part = stack.GetRange(stack.Count - n, n).ToArray();
+ for (int i = 0; i < n; ++i) stack[stack.Count - n + ((i + j) % n + n) % n] = part[i];
+ }
+ return;
+ }
+ }
+ stack.Clear();
+ }
+
+ private void Stems()
+ {
+ if (!haveWidth && stack.Count % 2 == 1) stack.RemoveAt(0);
+ haveWidth = true;
+ stems += stack.Count / 2;
+ stack.Clear();
+ }
+ private void Width(int expected)
+ {
+ if (!haveWidth && stack.Count > expected) stack.RemoveAt(0);
+ haveWidth = true;
+ }
+ private double Arg(int i) => i < stack.Count ? stack[i] : 0;
+ private double Pop()
+ {
+ if (stack.Count == 0) return 0;
+ double v = stack[stack.Count - 1];
+ stack.RemoveAt(stack.Count - 1);
+ return v;
+ }
+ private void MoveTo(double nx, double ny)
+ {
+ if (open) sink.ClosePath();
+ x = nx;
+ y = ny;
+ sink.MoveTo(x, y);
+ open = true;
+ }
+ private void LineTo(double nx, double ny)
+ {
+ x = nx;
+ y = ny;
+ sink.LineTo(x, y);
+ }
+ private void Curve(double dx1, double dy1, double dx2, double dy2, double dx3, double dy3)
+ {
+ double x1 = x + dx1, y1 = y + dy1, x2 = x1 + dx2, y2 = y1 + dy2;
+ x = x2 + dx3;
+ y = y2 + dy3;
+ sink.CurveTo(x1, y1, x2, y2, x, y);
+ }
+ }
+ }
+}
diff --git a/CADability/FontProvider.cs b/CADability/FontProvider.cs
index e0c00708..52a9c5de 100644
--- a/CADability/FontProvider.cs
+++ b/CADability/FontProvider.cs
@@ -119,10 +119,11 @@ public abstract class FontFace
}
///
- /// The default : reads TrueType font files (.ttf, .ttc) itself, so it needs neither GDI nor any
- /// other part of the operating system and works on Windows, Linux, macOS and in the browser. By default it uses the font
- /// directories of the system; more files or directories can be added. OpenType fonts with CFF outlines are not supported
- /// and are left out. A bold or italic face that a family does not have is not synthesized: the closest face is used.
+ /// The default : reads TrueType and OpenType font files (.ttf, .ttc, .otf, .otc) itself, so it
+ /// needs neither GDI nor any other part of the operating system and works on Windows, Linux, macOS and in the browser. By
+ /// default it uses the font directories of the system; more files or directories can be added. Bitmap fonts and variable
+ /// OpenType fonts with CFF2 outlines are left out. A bold or italic face that a family does not have is not synthesized:
+ /// the closest face is used.
///
public class ManagedFontProvider : FontProvider
{
@@ -177,7 +178,7 @@ public static IEnumerable SystemFontDirectories()
}
}
///
- /// Adds the fonts of a font file (.ttf or .ttc).
+ /// Adds the fonts of a font file (.ttf, .ttc, .otf or .otc).
///
public void AddFontFile(string fileName)
{
@@ -232,7 +233,7 @@ public override FontFace GetFace(string familyName, bool bold, bool italic)
private static void AddEntry(Entry entry, TrueTypeFont font, List list)
{
- if (!font.HasTrueTypeOutlines || string.IsNullOrEmpty(font.FamilyName)) return; // CFF or bitmap fonts cannot be used
+ if (!font.HasOutlines || string.IsNullOrEmpty(font.FamilyName)) return; // bitmap fonts and CFF2 cannot be used
entry.Family = font.FamilyName;
entry.Bold = font.IsBold;
entry.Italic = font.IsItalic;
@@ -258,7 +259,7 @@ private Dictionary> Families()
foreach (string file in files)
{
string ext = Path.GetExtension(file).ToLowerInvariant();
- if (ext != ".ttf" && ext != ".ttc") continue;
+ if (ext != ".ttf" && ext != ".ttc" && ext != ".otf" && ext != ".otc") continue;
if (!seen.Add(Path.GetFullPath(file))) continue;
try
{
diff --git a/CADability/TrueTypeFont.cs b/CADability/TrueTypeFont.cs
index 7428e83d..01230fab 100644
--- a/CADability/TrueTypeFont.cs
+++ b/CADability/TrueTypeFont.cs
@@ -26,8 +26,8 @@ public GlyphPoint(double x, double y, bool onCurve)
/// Reads a TrueType font (.ttf, or one font of a .ttc collection) without any help of the operating system, so that text
/// works on every platform (issue #412). It reads the glyph outlines (exact quadratic curves from the "glyf" table,
/// composite glyphs included), the advance widths, the metrics, the kerning pairs of the "kern" table and the names.
- /// OpenType fonts with CFF outlines (some .otf files) are recognized, but their outlines cannot be read
- /// ( is false).
+ /// OpenType fonts with CFF outlines (most .otf files) are read with : their cubic curves go into
+ /// the path data unchanged.
///
internal sealed class TrueTypeFont
{
@@ -81,6 +81,16 @@ internal sealed class TrueTypeFont
/// False for an OpenType font with CFF outlines and for a bitmap font, whose glyphs cannot be read.
///
public bool HasTrueTypeOutlines { get; }
+ ///
+ /// True for an OpenType font with outlines in the "CFF " table (Compact Font Format, cubic curves).
+ ///
+ public bool HasCffOutlines { get; }
+ ///
+ /// True if the glyph outlines can be read: TrueType or CFF outlines. False for bitmap fonts and for CFF2 (variable
+ /// OpenType fonts).
+ ///
+ public bool HasOutlines => HasTrueTypeOutlines || HasCffOutlines;
+ private CffOutlines cff; // created on demand
public int GlyphCount => numGlyphs;
///
@@ -123,6 +133,7 @@ private TrueTypeFont(byte[] data, int offset)
}
// a TrueType font without outlines is a bitmap font (e.g. color emoji)
HasTrueTypeOutlines = trueType && tables.ContainsKey("glyf") && tables.ContainsKey("loca");
+ HasCffOutlines = !trueType && tables.ContainsKey("CFF ");
int head = Table("head");
UnitsPerEm = U16(head + 18);
if (UnitsPerEm == 0) UnitsPerEm = 1000;
@@ -228,8 +239,8 @@ private static TrueTypeFont ReadHeader(FileStream stream, uint offset)
if (length > stream.Length) throw new InvalidDataException("table " + tag + " exceeds the file");
copied.Add((record, ReadBytes(stream, ReadU32(records, 16 * i + 8), (int)length)));
}
- else if (tag == "glyf" || tag == "loca")
- { // only their presence matters (HasTrueTypeOutlines)
+ else if (tag == "glyf" || tag == "loca" || tag == "CFF ")
+ { // only their presence matters (HasTrueTypeOutlines, HasCffOutlines)
for (int k = 8; k < 16; ++k) record[k] = 0;
copied.Add((record, new byte[0]));
}
@@ -351,7 +362,8 @@ public void GetPathData(int glyph, bool underline, bool strikeout, out PointF[]
List tps = new List();
double scale = 1.0 / UnitsPerEm;
PointF Map(double x, double y) => new PointF((float)(x * scale), (float)((CellAscent - y) * scale));
- foreach (GlyphPoint[] contour in GetContours(glyph))
+ if (HasCffOutlines) DrawCff(glyph, new PathDataSink(pts, tps, Map));
+ else foreach (GlyphPoint[] contour in GetContours(glyph))
{
if (contour.Length < 2) continue;
int n = contour.Length;
@@ -443,6 +455,78 @@ void AddBar(int top, int thickness)
types = tps.ToArray();
}
+ ///
+ /// Sends the outline of a glyph of a font with CFF outlines to , in font units.
+ ///
+ internal void DrawCff(int glyph, IGlyphOutlineSink sink)
+ {
+ if (IsHeaderOnly) throw new InvalidOperationException("The font \"" + FullName + "\" was read by ReadHeaders and has no glyphs, load it with Load");
+ if (!HasCffOutlines) throw new NotSupportedException("The font \"" + FullName + "\" has no CFF outlines");
+ if (cff == null)
+ {
+ var t = tables["CFF "];
+ cff = new CffOutlines(data, t.offset, t.length, UnitsPerEm);
+ }
+ cff.Draw(glyph, sink);
+ }
+ ///
+ /// Collects an outline as GDI+ path data (see ).
+ ///
+ private sealed class PathDataSink : IGlyphOutlineSink
+ {
+ private readonly List pts;
+ private readonly List tps;
+ private readonly Func map;
+ private int start = -1; // index of the start point of the open figure
+ private double cx, cy;
+ public PathDataSink(List pts, List tps, Func map)
+ {
+ this.pts = pts;
+ this.tps = tps;
+ this.map = map;
+ }
+ public void MoveTo(double x, double y)
+ {
+ ClosePath();
+ start = pts.Count;
+ pts.Add(map(x, y));
+ tps.Add(0);
+ cx = x;
+ cy = y;
+ }
+ public void LineTo(double x, double y)
+ {
+ if (start < 0 || (x == cx && y == cy)) return;
+ pts.Add(map(x, y));
+ tps.Add(1);
+ cx = x;
+ cy = y;
+ }
+ public void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
+ {
+ if (start < 0) return;
+ if (x1 == cx && y1 == cy && x2 == cx && y2 == cy && x3 == cx && y3 == cy) return; // no length
+ pts.Add(map(x1, y1));
+ pts.Add(map(x2, y2));
+ pts.Add(map(x3, y3));
+ tps.Add(3);
+ tps.Add(3);
+ tps.Add(3);
+ cx = x3;
+ cy = y3;
+ }
+ public void ClosePath()
+ {
+ if (start < 0) return;
+ if (pts.Count - start < 2)
+ { // nothing but the start point
+ pts.RemoveRange(start, pts.Count - start);
+ tps.RemoveRange(start, tps.Count - start);
+ }
+ else tps[tps.Count - 1] |= 0x80;
+ start = -1;
+ }
+ }
private void AddContours(int glyph, List res, int depth)
{
if (glyph < 0 || glyph >= numGlyphs || depth > 8) return; // a deeper nesting is a broken (or malicious) font
diff --git a/REUSE.toml b/REUSE.toml
index 1532e096..d4b1bbae 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -41,6 +41,15 @@ SPDX-FileCopyrightText = [
]
SPDX-License-Identifier = "OFL-1.1"
+[[annotations]]
+path = [
+ "tests/CADability.Tests/Files/Fonts/InterCffTest.otf",
+ "tests/CADability.Tests/Files/Fonts/InterCidTest.otf",
+ "tests/CADability.Tests/Files/Fonts/InterCffTest.reference.json",
+]
+SPDX-FileCopyrightText = "2016 The Inter Project Authors"
+SPDX-License-Identifier = "OFL-1.1"
+
[[annotations]]
# Test drawings of R10 and R11/R12 from https://github.com/michal-josef-spacek/CAD-Format-DWG-AC1006
# and https://github.com/michal-josef-spacek/CAD-Format-DWG-AC1009, renamed after their content.
diff --git a/tests/CADability.Tests/CffOutlinesTests.cs b/tests/CADability.Tests/CffOutlinesTests.cs
new file mode 100644
index 00000000..6937bc68
--- /dev/null
+++ b/tests/CADability.Tests/CffOutlinesTests.cs
@@ -0,0 +1,125 @@
+using CADability.GeoObject;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Drawing;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using Path = System.IO.Path;
+
+namespace CADability.Tests
+{
+ ///
+ /// The outlines of OpenType fonts with CFF outlines (issue #412, step 6). InterCffTest.otf (name-keyed) and InterCidTest.otf
+ /// (CID-keyed, two font dicts) are made by make_cff_test_fonts.py from a subset of Inter so that they use subroutines,
+ /// the specialized curve and line operators, flex and hint masks; InterCffTest.reference.json holds the outlines as fontTools
+ /// draws them, the same for both fonts.
+ ///
+ [TestClass]
+ public class CffOutlinesTests
+ {
+ private static string FontFile(string name, [CallerFilePath] string thisFile = "")
+ => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "Fonts", name);
+
+ private sealed class Recorder : IGlyphOutlineSink
+ {
+ public readonly List<(string op, double[] args)> Commands = new List<(string, double[])>();
+ public void MoveTo(double x, double y) => Commands.Add(("moveTo", new[] { x, y }));
+ public void LineTo(double x, double y) => Commands.Add(("lineTo", new[] { x, y }));
+ public void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3) => Commands.Add(("curveTo", new[] { x1, y1, x2, y2, x3, y3 }));
+ public void ClosePath() => Commands.Add(("closePath", new double[0]));
+ }
+
+ private static void AssertOutlinesMatchFontTools(string fontFile)
+ {
+ TrueTypeFont font = TrueTypeFont.Load(FontFile(fontFile)).Single();
+ Assert.IsTrue(font.HasCffOutlines);
+ Assert.IsFalse(font.HasTrueTypeOutlines);
+ Assert.IsTrue(font.HasOutlines);
+ JsonElement glyphs = JsonDocument.Parse(File.ReadAllText(FontFile("InterCffTest.reference.json"))).RootElement.GetProperty("glyphs");
+ int compared = 0;
+ foreach (JsonElement expected in glyphs.EnumerateArray())
+ {
+ string c = expected.GetProperty("char").GetString();
+ int glyph = font.GetGlyphIndex(c[0]);
+ Assert.AreEqual(expected.GetProperty("glyph").GetInt32(), glyph, $"glyph index of '{c}'");
+ Recorder recorder = new Recorder();
+ font.DrawCff(glyph, recorder);
+ JsonElement commands = expected.GetProperty("commands");
+ Assert.AreEqual(commands.GetArrayLength(), recorder.Commands.Count, $"number of drawing commands of '{c}'");
+ int i = 0;
+ foreach (JsonElement command in commands.EnumerateArray())
+ {
+ (string op, double[] args) = recorder.Commands[i++];
+ Assert.AreEqual(command[0].GetString(), op, $"command {i} of '{c}'");
+ double[] expectedArgs = command[1].EnumerateArray().Select(v => v.GetDouble()).ToArray();
+ Assert.AreEqual(expectedArgs.Length, args.Length, $"arguments of command {i} of '{c}'");
+ for (int k = 0; k < args.Length; ++k) Assert.AreEqual(expectedArgs[k], args[k], 1e-9, $"argument {k} of command {i} of '{c}'");
+ }
+ ++compared;
+ }
+ Assert.AreEqual(104, compared);
+ }
+
+ [TestMethod]
+ public void NameKeyedOutlinesMatchFontTools()
+ {
+ AssertOutlinesMatchFontTools("InterCffTest.otf");
+ }
+
+ [TestMethod]
+ public void CidKeyedOutlinesMatchFontTools()
+ {
+ AssertOutlinesMatchFontTools("InterCidTest.otf");
+ }
+
+ [TestMethod]
+ public void PathDataOfACffGlyph()
+ {
+ TrueTypeFont font = TrueTypeFont.Load(FontFile("InterCffTest.otf")).Single();
+ Recorder recorder = new Recorder();
+ int glyph = font.GetGlyphIndex('B');
+ font.DrawCff(glyph, recorder);
+ font.GetPathData(glyph, out PointF[] points, out byte[] types);
+ Assert.AreEqual(recorder.Commands.Count(c => c.op == "closePath"), types.Count(t => (t & 0x80) != 0), "one closed figure per contour");
+ Assert.IsTrue(types.All(t => new[] { 0, 1, 3 }.Contains(t & 0x7F)));
+ Assert.AreEqual(recorder.Commands.Count(c => c.op == "curveTo") * 3, types.Count(t => (t & 0x7F) == 3), "the cubic curves go in unchanged");
+ Assert.AreEqual(font.CellAscent / (double)font.UnitsPerEm, points.Max(p => p.Y), 1e-6, "the B stands on the baseline");
+ }
+
+ [TestMethod]
+ public void HeadersOfAnOpenTypeFontAreRead()
+ {
+ TrueTypeFont header = TrueTypeFont.ReadHeaders(FontFile("InterCidTest.otf")).Single();
+ Assert.IsTrue(header.IsHeaderOnly);
+ Assert.IsTrue(header.HasCffOutlines);
+ Assert.AreEqual("Inter", header.FamilyName);
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void TextWithAnOpenTypeFont()
+ {
+ string directory = Path.Combine(Path.GetTempPath(), "CADability-otf-" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(directory);
+ try
+ {
+ File.Copy(FontFile("InterCffTest.otf"), Path.Combine(directory, "InterCffTest.otf"));
+ FontProvider.Current = new ManagedFontProvider(new[] { directory }, false);
+ FontFace face = FontCache.GlobalFontCache.GetFace("Inter", 0);
+ Assert.AreEqual("Inter", face.FamilyName);
+ Text text = Text.Construct();
+ text.Font = "Inter";
+ text.TextString = "CFF";
+ text.Location = GeoPoint.Origin;
+ text.LineDirection = GeoVector.XAxis;
+ text.GlyphDirection = GeoVector.YAxis;
+ text.TextSize = 1.0;
+ Assert.IsTrue(text.GetOutline().Count > 0);
+ }
+ finally
+ {
+ TestFonts.UseTestFonts(null);
+ Directory.Delete(directory, true);
+ }
+ }
+ }
+}
diff --git a/tests/CADability.Tests/Files/Fonts/InterCffTest.otf b/tests/CADability.Tests/Files/Fonts/InterCffTest.otf
new file mode 100644
index 00000000..8afd85e9
Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/InterCffTest.otf differ
diff --git a/tests/CADability.Tests/Files/Fonts/InterCffTest.reference.json b/tests/CADability.Tests/Files/Fonts/InterCffTest.reference.json
new file mode 100644
index 00000000..381571f5
--- /dev/null
+++ b/tests/CADability.Tests/Files/Fonts/InterCffTest.reference.json
@@ -0,0 +1 @@
+{"glyphs":[{"char":" ","glyph":104,"commands":[]},{"char":"!","glyph":74,"commands":[["moveTo",[204,479]],["lineTo",[383,479]],["lineTo",[396,1490]],["lineTo",[192,1490]],["closePath",[]],["moveTo",[295,-13]],["curveTo",[369,-13,429,47,429,121]],["curveTo",[429,196,369,256,295,256]],["curveTo",[220,256,160,196,160,121]],["curveTo",[160,47,220,-13,295,-13]],["closePath",[]]]},{"char":"\"","glyph":89,"commands":[["moveTo",[232,923]],["lineTo",[382,923]],["lineTo",[404,1490]],["lineTo",[210,1490]],["closePath",[]],["moveTo",[572,923]],["lineTo",[722,923]],["lineTo",[744,1490]],["lineTo",[550,1490]],["closePath",[]]]},{"char":"#","glyph":83,"commands":[["moveTo",[711,0]],["lineTo",[864,0]],["lineTo",[931,411]],["lineTo",[1150,411]],["lineTo",[1175,561]],["lineTo",[956,561]],["lineTo",[1016,927]],["lineTo",[1238,927]],["lineTo",[1261,1079]],["lineTo",[1041,1079]],["lineTo",[1108,1490]],["lineTo",[957,1490]],["lineTo",[889,1079]],["lineTo",[517,1079]],["lineTo",[584,1490]],["lineTo",[433,1490]],["lineTo",[366,1079]],["lineTo",[147,1079]],["lineTo",[122,927]],["lineTo",[341,927]],["lineTo",[281,561]],["lineTo",[59,561]],["lineTo",[34,411]],["lineTo",[256,411]],["lineTo",[189,0]],["lineTo",[340,0]],["lineTo",[407,411]],["lineTo",[779,411]],["closePath",[]],["moveTo",[432,561]],["lineTo",[492,927]],["lineTo",[864,927]],["lineTo",[804,561]],["closePath",[]]]},{"char":"$","glyph":61,"commands":[["moveTo",[597,-218]],["lineTo",[717,-218]],["lineTo",[717,-24]],["curveTo",[1024,-3,1198,177,1198,406]],["curveTo",[1198,684,936,774,776,818]],["lineTo",[717,834]],["lineTo",[717,1339]],["curveTo",[867,1324,977,1244,992,1114]],["lineTo",[1178,1114]],["curveTo",[1169,1327,986,1491,717,1509]],["lineTo",[717,1708]],["lineTo",[597,1708]],["lineTo",[597,1506]],["curveTo",[345,1481,158,1321,158,1091]],["curveTo",[158,893,298,770,538,702]],["lineTo",[597,685]],["lineTo",[597,147]],["curveTo",[448,163,324,241,311,390]],["lineTo",[116,390]],["curveTo",[132,156,309,-5,597,-24]],["closePath",[]],["moveTo",[717,147]],["lineTo",[717,651]],["lineTo",[718,651]],["curveTo",[877,606,1013,553,1013,409]],["curveTo",[1013,266,889,166,717,147]],["closePath",[]],["moveTo",[597,868]],["curveTo",[489,899,343,958,343,1100]],["curveTo",[343,1223,442,1317,597,1338]],["closePath",[]]]},{"char":"%","glyph":102,"commands":[["moveTo",[1490,-29]],["curveTo",[1679,-29,1781,110,1781,274]],["lineTo",[1781,352]],["curveTo",[1781,515,1682,654,1490,654]],["curveTo",[1302,654,1197,516,1197,352]],["lineTo",[1197,274]],["curveTo",[1197,110,1299,-29,1490,-29]],["closePath",[]],["moveTo",[523,831]],["curveTo",[712,831,814,970,814,1134]],["lineTo",[814,1212]],["curveTo",[814,1375,715,1514,523,1514]],["curveTo",[335,1514,230,1376,230,1212]],["lineTo",[230,1134]],["curveTo",[230,970,332,831,523,831]],["closePath",[]],["moveTo",[404,0]],["lineTo",[573,0]],["lineTo",[1597,1490]],["lineTo",[1428,1490]],["closePath",[]],["moveTo",[1490,102]],["curveTo",[1387,102,1350,185,1350,274]],["lineTo",[1350,352]],["curveTo",[1350,440,1390,524,1490,524]],["curveTo",[1594,524,1628,440,1628,352]],["lineTo",[1628,274]],["curveTo",[1628,185,1591,102,1490,102]],["closePath",[]],["moveTo",[523,962]],["curveTo",[420,962,383,1045,383,1134]],["lineTo",[383,1212]],["curveTo",[383,1300,423,1384,523,1384]],["curveTo",[627,1384,661,1300,661,1212]],["lineTo",[661,1134]],["curveTo",[661,1045,624,962,523,962]],["closePath",[]]]},{"char":"&","glyph":73,"commands":[["moveTo",[580,-20]],["curveTo",[716,-20,868,27,967,124]],["lineTo",[1070,0]],["lineTo",[1287,0]],["lineTo",[1079,252]],["curveTo",[1137,326,1199,450,1199,658]],["lineTo",[1026,658]],["curveTo",[1026,556,1003,466,965,391]],["lineTo",[646,778]],["lineTo",[754,858]],["curveTo",[870,944,952,1046,952,1183]],["curveTo",[952,1360,814,1504,590,1504]],["curveTo",[369,1504,225,1364,225,1167]],["curveTo",[225,1031,294,930,398,805]],["curveTo",[227,679,104,578,104,389]],["curveTo",[104,159,285,-20,580,-20]],["closePath",[]],["moveTo",[859,253]],["curveTo",[777,178,674,137,567,137]],["curveTo",[402,137,289,236,289,383]],["curveTo",[289,475,333,546,456,637]],["lineTo",[507,675]],["closePath",[]],["moveTo",[538,908]],["curveTo",[459,1004,406,1078,406,1168]],["curveTo",[406,1275,482,1349,595,1349]],["curveTo",[703,1349,771,1281,771,1186]],["curveTo",[771,1081,689,1020,635,980]],["closePath",[]]]},{"char":"'","glyph":88,"commands":[["moveTo",[232,923]],["lineTo",[382,923]],["lineTo",[404,1490]],["lineTo",[210,1490]],["closePath",[]]]},{"char":"(","glyph":76,"commands":[["moveTo",[218,607]],["curveTo",[218,315,306,10,476,-279]],["lineTo",[651,-279]],["curveTo",[479,76,401,342,401,607]],["curveTo",[401,899,496,1266,651,1581]],["lineTo",[476,1581]],["curveTo",[326,1334,218,932,218,607]],["closePath",[]]]},{"char":")","glyph":77,"commands":[["moveTo",[96,-279]],["lineTo",[271,-279]],["curveTo",[438,5,529,310,529,607]],["curveTo",[529,931,421,1335,271,1581]],["lineTo",[96,1581]],["curveTo",[251,1266,346,899,346,607]],["curveTo",[346,350,273,86,96,-279]],["closePath",[]]]},{"char":"*","glyph":101,"commands":[["moveTo",[441,652]],["lineTo",[585,652]],["lineTo",[573,969]],["lineTo",[840,798]],["lineTo",[912,924]],["lineTo",[630,1070]],["lineTo",[912,1216]],["lineTo",[840,1344]],["lineTo",[573,1173]],["lineTo",[585,1490]],["lineTo",[441,1490]],["lineTo",[454,1173]],["lineTo",[186,1344]],["lineTo",[114,1216]],["lineTo",[396,1070]],["lineTo",[114,924]],["lineTo",[186,798]],["lineTo",[454,969]],["closePath",[]]]},{"char":"+","glyph":97,"commands":[["moveTo",[593,100]],["lineTo",[762,100]],["lineTo",[762,504]],["lineTo",[1160,504]],["lineTo",[1160,663]],["lineTo",[762,663]],["lineTo",[762,1066]],["lineTo",[593,1066]],["lineTo",[593,663]],["lineTo",[195,663]],["lineTo",[195,504]],["lineTo",[593,504]],["closePath",[]]]},{"char":",","glyph":90,"commands":[["moveTo",[128,-359]],["lineTo",[266,-359]],["lineTo",[422,208]],["lineTo",[216,208]],["closePath",[]]]},{"char":"-","glyph":87,"commands":[["moveTo",[798,719]],["lineTo",[144,719]],["lineTo",[144,553]],["lineTo",[798,553]],["closePath",[]]]},{"char":".","glyph":91,"commands":[["moveTo",[295,-13]],["curveTo",[370,-13,430,47,430,122]],["curveTo",[430,197,370,257,295,257]],["curveTo",[220,257,160,197,160,122]],["curveTo",[160,47,220,-13,295,-13]],["closePath",[]]]},{"char":"/","glyph":84,"commands":[["moveTo",[692,1560]],["lineTo",[526,1560]],["lineTo",[46,-224]],["lineTo",[212,-224]],["closePath",[]]]},{"char":"0","glyph":63,"commands":[["moveTo",[646,-20]],["curveTo",[979,-20,1170,259,1170,744]],["curveTo",[1170,1227,976,1510,646,1510]],["curveTo",[316,1510,122,1227,122,744]],["curveTo",[122,258,314,-20,646,-20]],["closePath",[]],["moveTo",[646,146]],["curveTo",[428,146,305,366,305,744]],["curveTo",[305,1123,428,1345,646,1345]],["curveTo",[864,1345,987,1124,987,744]],["curveTo",[987,366,864,146,646,146]],["closePath",[]]]},{"char":"1","glyph":64,"commands":[["moveTo",[653,1490]],["lineTo",[420,1490]],["lineTo",[96,1251]],["lineTo",[96,1047]],["lineTo",[456,1314]],["lineTo",[466,1314]],["lineTo",[466,0]],["lineTo",[653,0]],["closePath",[]]]},{"char":"2","glyph":65,"commands":[["moveTo",[154,0]],["lineTo",[1103,0]],["lineTo",[1103,167]],["lineTo",[416,167]],["lineTo",[416,179]],["lineTo",[742,526]],["curveTo",[1005,806,1078,933,1078,1094]],["curveTo",[1078,1327,889,1510,623,1510]],["curveTo",[358,1510,158,1331,158,1067]],["lineTo",[340,1067]],["curveTo",[340,1235,448,1345,618,1345]],["curveTo",[777,1345,898,1248,898,1092]],["curveTo",[898,955,814,853,649,674]],["lineTo",[154,137]],["closePath",[]]]},{"char":"3","glyph":66,"commands":[["moveTo",[635,-20]],["curveTo",[927,-20,1143,161,1143,405]],["curveTo",[1143,593,1032,731,842,762]],["lineTo",[842,774]],["curveTo",[994,819,1089,942,1089,1110]],["curveTo",[1089,1322,919,1510,641,1510]],["curveTo",[379,1510,165,1349,157,1115]],["lineTo",[340,1115]],["curveTo",[346,1260,485,1345,637,1345]],["curveTo",[799,1345,903,1248,903,1100]],["curveTo",[903,946,783,846,609,846]],["lineTo",[488,846]],["lineTo",[488,681]],["lineTo",[609,681]],["curveTo",[830,681,955,568,955,407]],["curveTo",[955,252,819,147,632,147]],["curveTo",[462,147,328,234,317,376]],["lineTo",[125,376]],["curveTo",[135,140,343,-20,635,-20]],["closePath",[]]]},{"char":"4","glyph":67,"commands":[["moveTo",[120,303]],["lineTo",[821,303]],["lineTo",[821,0]],["lineTo",[1003,0]],["lineTo",[1003,303]],["lineTo",[1205,303]],["lineTo",[1205,470]],["lineTo",[1003,470]],["lineTo",[1003,1490]],["lineTo",[772,1490]],["lineTo",[120,458]],["closePath",[]],["moveTo",[822,470]],["lineTo",[323,470]],["lineTo",[323,482]],["lineTo",[810,1252]],["lineTo",[822,1252]],["closePath",[]]]},{"char":"5","glyph":68,"commands":[["moveTo",[626,-20]],["curveTo",[913,-20,1123,190,1123,476]],["curveTo",[1123,764,922,975,649,975]],["curveTo",[546,975,447,939,386,888]],["lineTo",[378,888]],["lineTo",[429,1323]],["lineTo",[1053,1323]],["lineTo",[1053,1490]],["lineTo",[269,1490]],["lineTo",[181,752]],["lineTo",[359,730]],["curveTo",[418,775,522,808,614,808]],["curveTo",[801,808,937,665,937,473]],["curveTo",[937,284,806,147,626,147]],["curveTo",[476,147,354,243,342,376]],["lineTo",[158,376]],["curveTo",[168,148,364,-20,626,-20]],["closePath",[]]]},{"char":"6","glyph":69,"commands":[["moveTo",[646,-20]],["curveTo",[950,-20,1148,203,1148,481]],["curveTo",[1148,769,936,974,681,974]],["curveTo",[526,974,394,900,314,776]],["lineTo",[302,776]],["curveTo",[302,1134,440,1342,669,1342]],["curveTo",[824,1342,917,1244,950,1115]],["lineTo",[1136,1115]],["curveTo",[1100,1349,919,1510,669,1510]],["curveTo",[331,1510,122,1212,122,688]],["curveTo",[122,139,397,-20,646,-20]],["closePath",[]],["moveTo",[646,147]],["curveTo",[463,147,329,303,329,479]],["curveTo",[329,653,470,809,651,809]],["curveTo",[831,809,964,663,964,481]],["curveTo",[964,295,825,147,646,147]],["closePath",[]]]},{"char":"7","glyph":70,"commands":[["moveTo",[200,0]],["lineTo",[400,0]],["lineTo",[1061,1313]],["lineTo",[1061,1490]],["lineTo",[98,1490]],["lineTo",[98,1323]],["lineTo",[862,1323]],["lineTo",[862,1311]],["closePath",[]]]},{"char":"8","glyph":71,"commands":[["moveTo",[633,-20]],["curveTo",[933,-20,1145,153,1145,396]],["curveTo",[1145,584,1016,744,854,774]],["lineTo",[854,782]],["curveTo",[995,819,1089,956,1089,1114]],["curveTo",[1089,1341,894,1510,633,1510]],["curveTo",[368,1510,176,1341,176,1114]],["curveTo",[176,956,268,819,413,782]],["lineTo",[413,774]],["curveTo",[246,744,122,584,122,396]],["curveTo",[122,153,330,-20,633,-20]],["closePath",[]],["moveTo",[633,145]],["curveTo",[432,145,309,248,309,406]],["curveTo",[309,571,445,689,633,689]],["curveTo",[817,689,955,571,955,406]],["curveTo",[955,248,830,145,633,145]],["closePath",[]],["moveTo",[633,848]],["curveTo",[472,848,360,949,360,1100]],["curveTo",[360,1249,468,1346,633,1346]],["curveTo",[795,1346,905,1249,905,1100]],["curveTo",[905,949,790,848,633,848]],["closePath",[]]]},{"char":"9","glyph":72,"commands":[["moveTo",[603,-20]],["curveTo",[940,-20,1148,277,1148,804]],["curveTo",[1148,1352,873,1506,628,1510]],["curveTo",[322,1515,122,1288,122,1010]],["curveTo",[122,723,334,517,591,517]],["curveTo",[747,517,878,594,958,716]],["lineTo",[970,716]],["curveTo",[970,355,830,148,603,148]],["curveTo",[446,148,353,247,322,379]],["lineTo",[134,379]],["curveTo",[169,141,350,-20,603,-20]],["closePath",[]],["moveTo",[619,683]],["curveTo",[439,683,308,828,308,1011]],["curveTo",[308,1195,445,1343,625,1343]],["curveTo",[808,1343,943,1186,943,1015]],["curveTo",[943,840,800,683,619,683]],["closePath",[]]]},{"char":":","glyph":92,"commands":[["moveTo",[295,-13]],["curveTo",[370,-13,430,47,430,122]],["curveTo",[430,197,370,257,295,257]],["curveTo",[220,257,160,197,160,122]],["curveTo",[160,47,220,-13,295,-13]],["closePath",[]],["moveTo",[295,796]],["curveTo",[370,796,430,856,430,931]],["curveTo",[430,1006,370,1066,295,1066]],["curveTo",[220,1066,160,1006,160,931]],["curveTo",[160,856,220,796,295,796]],["closePath",[]]]},{"char":";","glyph":93,"commands":[["moveTo",[128,-359]],["lineTo",[266,-359]],["lineTo",[422,208]],["lineTo",[216,208]],["closePath",[]],["moveTo",[323,796]],["curveTo",[398,796,458,856,458,931]],["curveTo",[458,1006,398,1066,323,1066]],["curveTo",[248,1066,188,1006,188,931]],["curveTo",[188,856,248,796,323,796]],["closePath",[]]]},{"char":"<","glyph":94,"commands":[["moveTo",[174,520]],["lineTo",[1118,44]],["lineTo",[1118,238]],["lineTo",[406,576]],["lineTo",[406,588]],["lineTo",[1118,926]],["lineTo",[1118,1122]],["lineTo",[174,646]],["closePath",[]]]},{"char":"=","glyph":96,"commands":[["moveTo",[226,283]],["lineTo",[1130,283]],["lineTo",[1130,449]],["lineTo",[226,449]],["closePath",[]],["moveTo",[226,717]],["lineTo",[1130,717]],["lineTo",[1130,883]],["lineTo",[226,883]],["closePath",[]]]},{"char":">","glyph":95,"commands":[["moveTo",[1181,520]],["lineTo",[1181,646]],["lineTo",[237,1122]],["lineTo",[237,926]],["lineTo",[949,588]],["lineTo",[949,576]],["lineTo",[237,238]],["lineTo",[237,44]],["closePath",[]]]},{"char":"?","glyph":75,"commands":[["moveTo",[383,449]],["lineTo",[564,449]],["lineTo",[564,460]],["curveTo",[564,637,613,686,742,765]],["curveTo",[871,844,945,956,945,1117]],["curveTo",[945,1345,777,1510,518,1510]],["curveTo",[282,1510,92,1366,82,1115]],["lineTo",[272,1115]],["curveTo",[283,1281,400,1351,518,1351]],["curveTo",[653,1351,761,1263,761,1124]],["curveTo",[761,1008,693,927,605,874]],["curveTo",[467,791,383,733,383,460]],["closePath",[]],["moveTo",[476,-13]],["curveTo",[550,-13,610,47,610,121]],["curveTo",[610,196,550,256,476,256]],["curveTo",[401,256,341,196,341,121]],["curveTo",[341,47,401,-13,476,-13]],["closePath",[]]]},{"char":"@","glyph":82,"commands":[["moveTo",[1044,-395]],["curveTo",[1195,-395,1384,-352,1476,-313]],["lineTo",[1433,-173]],["curveTo",[1374,-193,1210,-250,1051,-250]],["curveTo",[555,-250,289,28,289,520]],["curveTo",[289,1005,559,1294,1017,1294]],["curveTo",[1479,1294,1697,973,1697,642]],["curveTo",[1697,316,1662,171,1516,171]],["curveTo",[1430,171,1385,241,1385,321]],["lineTo",[1385,980]],["lineTo",[1227,980]],["lineTo",[1227,896]],["lineTo",[1217,896]],["curveTo",[1181,947,1093,1004,965,1004]],["curveTo",[726,1004,552,803,552,535]],["curveTo",[552,253,689,37,955,37]],["curveTo",[1106,37,1193,112,1228,198]],["lineTo",[1236,198]],["curveTo",[1254,74,1408,30,1512,30]],["curveTo",[1820,30,1856,355,1856,640]],["curveTo",[1856,1046,1571,1438,1023,1438]],["curveTo",[458,1438,122,1094,122,525]],["curveTo",[122,-63,449,-395,1044,-395]],["closePath",[]],["moveTo",[962,190]],["curveTo",[776,190,707,358,707,543]],["curveTo",[707,710,803,853,967,853]],["curveTo",[1141,853,1233,782,1233,543]],["curveTo",[1233,306,1155,190,962,190]],["closePath",[]]]},{"char":"A","glyph":1,"commands":[["moveTo",[52,0]],["lineTo",[254,0]],["lineTo",[398,416]],["lineTo",[1012,416]],["lineTo",[1161,0]],["lineTo",[1361,0]],["lineTo",[813,1490]],["lineTo",[593,1490]],["closePath",[]],["moveTo",[456,582]],["lineTo",[561,884]],["curveTo",[593,976,639,1118,702,1338]],["curveTo",[765,1122,809,984,845,884]],["lineTo",[953,582]],["closePath",[]]]},{"char":"B","glyph":3,"commands":[["moveTo",[180,0]],["lineTo",[725,0]],["curveTo",[1076,0,1224,172,1224,397]],["curveTo",[1224,631,1061,759,922,768]],["lineTo",[922,782]],["curveTo",[1052,817,1166,904,1166,1095]],["curveTo",[1166,1313,1019,1490,708,1490]],["lineTo",[180,1490]],["closePath",[]],["moveTo",[370,168]],["lineTo",[370,676]],["lineTo",[730,676]],["curveTo",[921,676,1041,548,1041,399]],["curveTo",[1041,270,952,168,720,168]],["closePath",[]],["moveTo",[370,836]],["lineTo",[370,1322]],["lineTo",[704,1322]],["curveTo",[897,1322,984,1220,984,1091]],["curveTo",[984,937,858,836,698,836]],["closePath",[]]]},{"char":"C","glyph":4,"commands":[["moveTo",[783,-20]],["curveTo",[1088,-20,1332,166,1384,469]],["lineTo",[1194,469]],["curveTo",[1152,266,978,156,783,156]],["curveTo",[516,156,307,363,307,744]],["curveTo",[307,1126,515,1334,783,1334]],["curveTo",[977,1334,1152,1225,1194,1021]],["lineTo",[1384,1021]],["curveTo",[1331,1328,1085,1510,783,1510]],["curveTo",[401,1510,122,1217,122,744]],["curveTo",[122,272,401,-20,783,-20]],["closePath",[]]]},{"char":"D","glyph":5,"commands":[["moveTo",[645,0]],["curveTo",[1099,0,1356,282,1356,748]],["curveTo",[1356,1210,1099,1490,664,1490]],["lineTo",[180,1490]],["lineTo",[180,0]],["closePath",[]],["moveTo",[370,168]],["lineTo",[370,1322]],["lineTo",[651,1322]],["curveTo",[996,1322,1172,1106,1172,748]],["curveTo",[1172,386,996,168,633,168]],["closePath",[]]]},{"char":"E","glyph":6,"commands":[["moveTo",[180,0]],["lineTo",[1097,0]],["lineTo",[1097,168]],["lineTo",[370,168]],["lineTo",[370,668]],["lineTo",[1039,668]],["lineTo",[1039,836]],["lineTo",[370,836]],["lineTo",[370,1322]],["lineTo",[1088,1322]],["lineTo",[1088,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"F","glyph":7,"commands":[["moveTo",[180,0]],["lineTo",[370,0]],["lineTo",[370,644]],["lineTo",[1013,644]],["lineTo",[1013,812]],["lineTo",[370,812]],["lineTo",[370,1322]],["lineTo",[1081,1322]],["lineTo",[1081,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"G","glyph":8,"commands":[["moveTo",[789,-20]],["curveTo",[1144,-20,1400,216,1400,586]],["lineTo",[1400,749]],["lineTo",[828,749]],["lineTo",[828,583]],["lineTo",[1214,583]],["curveTo",[1209,321,1038,156,789,156]],["curveTo",[515,156,307,364,307,744]],["curveTo",[307,1127,516,1334,778,1334]],["curveTo",[989,1334,1132,1214,1193,1023]],["lineTo",[1389,1023]],["curveTo",[1335,1312,1092,1510,777,1510]],["curveTo",[395,1510,122,1217,122,744]],["curveTo",[122,273,391,-20,789,-20]],["closePath",[]]]},{"char":"H","glyph":9,"commands":[["moveTo",[180,0]],["lineTo",[370,0]],["lineTo",[370,678]],["lineTo",[1152,678]],["lineTo",[1152,0]],["lineTo",[1342,0]],["lineTo",[1342,1490]],["lineTo",[1152,1490]],["lineTo",[1152,846]],["lineTo",[370,846]],["lineTo",[370,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"I","glyph":10,"commands":[["moveTo",[370,1490]],["lineTo",[180,1490]],["lineTo",[180,0]],["lineTo",[370,0]],["closePath",[]]]},{"char":"J","glyph":11,"commands":[["moveTo",[545,-20]],["curveTo",[808,-20,989,140,989,426]],["lineTo",[989,1490]],["lineTo",[799,1490]],["lineTo",[799,426]],["curveTo",[799,244,697,147,545,147]],["curveTo",[392,147,290,244,290,426]],["lineTo",[290,506]],["lineTo",[100,506]],["lineTo",[100,426]],["curveTo",[100,140,282,-20,545,-20]],["closePath",[]]]},{"char":"K","glyph":12,"commands":[["moveTo",[180,0]],["lineTo",[370,0]],["lineTo",[370,489]],["lineTo",[585,716]],["lineTo",[1094,0]],["lineTo",[1319,0]],["lineTo",[716,845]],["lineTo",[1318,1490]],["lineTo",[1069,1490]],["lineTo",[624,1009]],["curveTo",[533,911,450,813,367,706]],["lineTo",[370,980]],["lineTo",[370,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"L","glyph":13,"commands":[["moveTo",[180,0]],["lineTo",[1060,0]],["lineTo",[1060,168]],["lineTo",[370,168]],["lineTo",[370,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"M","glyph":14,"commands":[["moveTo",[180,0]],["lineTo",[361,0]],["lineTo",[361,851]],["curveTo",[361,966,357,1142,354,1284]],["curveTo",[413,1109,471,935,504,851]],["lineTo",[842,0]],["lineTo",[1007,0]],["lineTo",[1340,851]],["curveTo",[1374,938,1436,1117,1490,1287]],["curveTo",[1486,1132,1483,961,1483,851]],["lineTo",[1483,0]],["lineTo",[1670,0]],["lineTo",[1670,1490]],["lineTo",[1399,1490]],["lineTo",[1037,550]],["curveTo",[1008,475,962,328,927,211]],["curveTo",[892,332,845,478,817,550]],["lineTo",[450,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"N","glyph":15,"commands":[["moveTo",[180,0]],["lineTo",[371,0]],["lineTo",[371,850]],["curveTo",[371,922,364,1071,356,1262]],["curveTo",[472,1055,529,955,597,848]],["lineTo",[1137,0]],["lineTo",[1363,0]],["lineTo",[1363,1490]],["lineTo",[1173,1490]],["lineTo",[1173,576]],["curveTo",[1173,496,1174,369,1186,225]],["curveTo",[1118,360,1050,474,1010,537]],["lineTo",[404,1490]],["lineTo",[180,1490]],["closePath",[]]]},{"char":"O","glyph":16,"commands":[["moveTo",[784,-20]],["curveTo",[1166,-20,1444,272,1444,744]],["curveTo",[1444,1217,1166,1510,784,1510]],["curveTo",[402,1510,122,1217,122,744]],["curveTo",[122,272,402,-20,784,-20]],["closePath",[]],["moveTo",[784,156]],["curveTo",[516,156,307,364,307,744]],["curveTo",[307,1126,516,1334,784,1334]],["curveTo",[1050,1334,1259,1127,1259,744]],["curveTo",[1259,363,1050,156,784,156]],["closePath",[]]]},{"char":"P","glyph":18,"commands":[["moveTo",[180,0]],["lineTo",[370,0]],["lineTo",[370,539]],["lineTo",[691,539]],["curveTo",[1038,539,1198,750,1198,1016]],["curveTo",[1198,1281,1038,1490,690,1490]],["lineTo",[180,1490]],["closePath",[]],["moveTo",[370,706]],["lineTo",[370,1323]],["lineTo",[680,1323]],["curveTo",[918,1323,1009,1192,1009,1016]],["curveTo",[1009,839,918,706,682,706]],["closePath",[]]]},{"char":"Q","glyph":19,"commands":[["moveTo",[784,-20]],["curveTo",[880,-20,969,-2,1049,33]],["lineTo",[1178,-140]],["lineTo",[1376,-140]],["lineTo",[1184,115]],["curveTo",[1345,245,1444,462,1444,744]],["curveTo",[1444,1217,1166,1510,784,1510]],["curveTo",[402,1510,122,1217,122,744]],["curveTo",[122,272,402,-20,784,-20]],["closePath",[]],["moveTo",[722,466]],["lineTo",[938,181]],["curveTo",[890,165,838,156,784,156]],["curveTo",[516,156,307,364,307,744]],["curveTo",[307,1126,516,1334,784,1334]],["curveTo",[1050,1334,1259,1127,1259,744]],["curveTo",[1259,522,1188,359,1076,261]],["lineTo",[920,466]],["closePath",[]]]},{"char":"R","glyph":20,"commands":[["moveTo",[180,0]],["lineTo",[370,0]],["lineTo",[370,578]],["lineTo",[691,578]],["curveTo",[703,578,714,578,726,579]],["lineTo",[1036,0]],["lineTo",[1256,0]],["lineTo",[922,613]],["curveTo",[1110,679,1198,832,1198,1030]],["curveTo",[1198,1296,1038,1490,690,1490]],["lineTo",[180,1490]],["closePath",[]],["moveTo",[370,747]],["lineTo",[370,1323]],["lineTo",[680,1323]],["curveTo",[917,1323,1009,1207,1009,1030]],["curveTo",[1009,855,917,747,682,747]],["closePath",[]]]},{"char":"S","glyph":21,"commands":[["moveTo",[657,-26]],["curveTo",[1001,-26,1198,162,1198,406]],["curveTo",[1198,684,936,774,776,818]],["lineTo",[627,859]],["curveTo",[518,889,343,944,343,1100]],["curveTo",[343,1240,471,1342,664,1342]],["curveTo",[841,1342,975,1259,992,1114]],["lineTo",[1178,1114]],["curveTo",[1169,1339,964,1510,670,1510]],["curveTo",[382,1510,158,1343,158,1091]],["curveTo",[158,893,298,770,538,702]],["lineTo",[718,651]],["curveTo",[877,606,1013,553,1013,409]],["curveTo",[1013,250,859,144,657,144]],["curveTo",[482,144,326,221,311,390]],["lineTo",[116,390]],["curveTo",[133,140,334,-26,657,-26]],["closePath",[]]]},{"char":"T","glyph":22,"commands":[["moveTo",[98,1322]],["lineTo",[567,1322]],["lineTo",[567,0]],["lineTo",[757,0]],["lineTo",[757,1322]],["lineTo",[1224,1322]],["lineTo",[1224,1490]],["lineTo",[98,1490]],["closePath",[]]]},{"char":"U","glyph":23,"commands":[["moveTo",[763,-24]],["curveTo",[1114,-24,1344,200,1344,507]],["lineTo",[1344,1490]],["lineTo",[1154,1490]],["lineTo",[1154,522]],["curveTo",[1154,308,1006,152,763,152]],["curveTo",[519,152,370,308,370,522]],["lineTo",[370,1490]],["lineTo",[180,1490]],["lineTo",[180,507]],["curveTo",[180,200,410,-24,763,-24]],["closePath",[]]]},{"char":"V","glyph":25,"commands":[["moveTo",[600,0]],["lineTo",[819,0]],["lineTo",[1361,1490]],["lineTo",[1159,1490]],["lineTo",[852,606]],["curveTo",[820,514,773,372,710,154]],["curveTo",[648,367,600,514,567,606]],["lineTo",[252,1490]],["lineTo",[52,1490]],["closePath",[]]]},{"char":"W","glyph":26,"commands":[["moveTo",[458,0]],["lineTo",[680,0]],["lineTo",[935,944]],["curveTo",[964,1052,986,1153,1008,1275]],["curveTo",[1031,1144,1055,1039,1081,944]],["lineTo",[1337,0]],["lineTo",[1558,0]],["lineTo",[1966,1490]],["lineTo",[1770,1490]],["lineTo",[1535,576]],["curveTo",[1503,450,1473,321,1447,183]],["curveTo",[1419,321,1389,450,1356,576]],["lineTo",[1117,1490]],["lineTo",[901,1490]],["lineTo",[660,576]],["curveTo",[626,448,597,317,570,177]],["curveTo",[543,317,514,448,482,576]],["lineTo",[246,1490]],["lineTo",[52,1490]],["closePath",[]]]},{"char":"X","glyph":27,"commands":[["moveTo",[57,0]],["lineTo",[273,0]],["lineTo",[536,378]],["curveTo",[613,488,650,546,699,640]],["curveTo",[749,542,785,487,860,378]],["lineTo",[1121,0]],["lineTo",[1340,0]],["lineTo",[811,770]],["lineTo",[1312,1490]],["lineTo",[1097,1490]],["lineTo",[878,1174]],["curveTo",[790,1047,754,989,701,885]],["curveTo",[647,990,612,1046,525,1174]],["lineTo",[310,1490]],["lineTo",[90,1490]],["lineTo",[591,763]],["closePath",[]]]},{"char":"Y","glyph":28,"commands":[["moveTo",[600,0]],["lineTo",[790,0]],["lineTo",[790,610]],["lineTo",[1338,1490]],["lineTo",[1119,1490]],["lineTo",[825,1009]],["curveTo",[777,930,737,862,697,766]],["curveTo",[657,859,617,927,567,1009]],["lineTo",[272,1490]],["lineTo",[52,1490]],["lineTo",[600,610]],["closePath",[]]]},{"char":"Z","glyph":29,"commands":[["moveTo",[130,0]],["lineTo",[1166,0]],["lineTo",[1166,168]],["lineTo",[640,168]],["curveTo",[541,168,443,167,345,163]],["curveTo",[399,234,453,307,502,379]],["lineTo",[1158,1354]],["lineTo",[1158,1490]],["lineTo",[122,1490]],["lineTo",[122,1322]],["lineTo",[645,1322]],["curveTo",[744,1322,844,1323,943,1327]],["curveTo",[892,1260,841,1192,796,1125]],["lineTo",[130,134]],["closePath",[]]]},{"char":"[","glyph":78,"commands":[["moveTo",[226,-279]],["lineTo",[624,-279]],["lineTo",[624,-126]],["lineTo",[398,-126]],["lineTo",[398,1428]],["lineTo",[624,1428]],["lineTo",[624,1581]],["lineTo",[226,1581]],["closePath",[]]]},{"char":"\\","glyph":86,"commands":[["moveTo",[526,-224]],["lineTo",[692,-224]],["lineTo",[212,1560]],["lineTo",[46,1560]],["closePath",[]]]},{"char":"]","glyph":79,"commands":[["moveTo",[123,-279]],["lineTo",[521,-279]],["lineTo",[521,1581]],["lineTo",[123,1581]],["lineTo",[123,1428]],["lineTo",[349,1428]],["lineTo",[349,-126]],["lineTo",[123,-126]],["closePath",[]]]},{"char":"^","glyph":100,"commands":[["moveTo",[82,814]],["lineTo",[261,814]],["lineTo",[476,1285]],["lineTo",[489,1285]],["lineTo",[706,814]],["lineTo",[883,814]],["lineTo",[582,1443]],["lineTo",[383,1443]],["closePath",[]]]},{"char":"_","glyph":99,"commands":[["moveTo",[934,0]],["lineTo",[0,0]],["lineTo",[0,-166]],["lineTo",[934,-166]],["closePath",[]]]},{"char":"`","glyph":103,"commands":[["moveTo",[343,1264]],["lineTo",[491,1264]],["lineTo",[351,1558]],["lineTo",[150,1558]],["closePath",[]]]},{"char":"a","glyph":30,"commands":[["moveTo",[471,-26]],["curveTo",[660,-26,765,73,803,152]],["lineTo",[815,152]],["lineTo",[815,0]],["lineTo",[992,0]],["lineTo",[992,737]],["curveTo",[992,1086,723,1132,573,1132]],["curveTo",[400,1132,208,1070,122,867]],["lineTo",[295,810]],["curveTo",[330,888,417,974,578,974]],["curveTo",[733,974,812,894,812,755]],["lineTo",[812,749]],["curveTo",[812,666,724,672,525,647]],["curveTo",[318,621,90,574,90,318]],["curveTo",[90,98,259,-26,471,-26]],["closePath",[]],["moveTo",[502,133]],["curveTo",[368,133,271,193,271,309]],["curveTo",[271,436,386,478,517,495]],["curveTo",[586,504,782,524,812,559]],["lineTo",[812,404]],["curveTo",[812,267,701,133,502,133]],["closePath",[]]]},{"char":"b","glyph":32,"commands":[["moveTo",[677,-24]],["curveTo",[956,-24,1150,200,1150,556]],["curveTo",[1150,910,955,1132,676,1132]],["curveTo",[458,1132,389,998,352,939]],["lineTo",[338,939]],["lineTo",[338,1490]],["lineTo",[158,1490]],["lineTo",[158,0]],["lineTo",[332,0]],["lineTo",[332,173]],["lineTo",[352,173]],["curveTo",[389,111,464,-24,677,-24]],["closePath",[]],["moveTo",[650,137]],["curveTo",[447,137,336,309,336,558]],["curveTo",[336,805,444,971,650,971]],["curveTo",[862,971,967,790,967,558]],["curveTo",[967,323,859,137,650,137]],["closePath",[]]]},{"char":"c","glyph":33,"commands":[["moveTo",[613,-24]],["curveTo",[832,-24,1010,95,1070,294]],["lineTo",[898,343]],["curveTo",[865,222,763,137,613,137]],["curveTo",[390,137,287,332,287,552]],["curveTo",[287,775,390,971,613,971]],["curveTo",[760,971,860,889,893,774]],["lineTo",[1066,823]],["curveTo",[1005,1018,829,1132,613,1132]],["curveTo",[309,1132,104,902,104,552]],["curveTo",[104,207,309,-24,613,-24]],["closePath",[]]]},{"char":"d","glyph":34,"commands":[["moveTo",[577,-24]],["curveTo",[790,-24,865,111,902,173]],["lineTo",[922,173]],["lineTo",[922,0]],["lineTo",[1096,0]],["lineTo",[1096,1490]],["lineTo",[916,1490]],["lineTo",[916,939]],["lineTo",[902,939]],["curveTo",[865,998,796,1132,578,1132]],["curveTo",[299,1132,104,910,104,556]],["curveTo",[104,200,298,-24,577,-24]],["closePath",[]],["moveTo",[604,137]],["curveTo",[395,137,287,323,287,558]],["curveTo",[287,790,392,971,604,971]],["curveTo",[810,971,918,805,918,558]],["curveTo",[918,309,807,137,604,137]],["closePath",[]]]},{"char":"e","glyph":35,"commands":[["moveTo",[628,-24]],["curveTo",[857,-24,1024,91,1076,260]],["lineTo",[902,308]],["curveTo",[860,194,761,136,629,136]],["curveTo",[431,136,295,264,286,498]],["lineTo",[1094,498]],["lineTo",[1094,573]],["curveTo",[1094,986,844,1132,607,1132]],["curveTo",[302,1132,104,893,104,550]],["curveTo",[104,206,304,-24,628,-24]],["closePath",[]],["moveTo",[287,650]],["curveTo",[298,821,415,972,607,972]],["curveTo",[791,972,894,840,911,650]],["closePath",[]]]},{"char":"f","glyph":37,"commands":[["moveTo",[678,1118]],["lineTo",[420,1118]],["lineTo",[420,1227]],["curveTo",[420,1335,468,1393,579,1393]],["curveTo",[629,1393,657,1382,676,1376]],["lineTo",[726,1530]],["curveTo",[699,1542,643,1560,558,1560]],["curveTo",[395,1560,240,1461,240,1267]],["lineTo",[240,1118]],["lineTo",[20,1118]],["lineTo",[20,964]],["lineTo",[240,964]],["lineTo",[240,0]],["lineTo",[420,0]],["lineTo",[420,964]],["lineTo",[678,964]],["closePath",[]]]},{"char":"g","glyph":38,"commands":[["moveTo",[611,-442]],["curveTo",[883,-442,1098,-318,1098,-29]],["lineTo",[1098,1118]],["lineTo",[923,1118]],["lineTo",[923,939]],["lineTo",[906,939]],["curveTo",[868,999,796,1132,580,1132]],["curveTo",[301,1132,104,910,104,564]],["curveTo",[104,213,308,18,576,18]],["curveTo",[792,18,863,141,901,205]],["lineTo",[918,205]],["lineTo",[918,-19]],["curveTo",[918,-203,789,-285,611,-285]],["curveTo",[414,-285,344,-185,297,-120]],["lineTo",[151,-214]],["curveTo",[220,-337,363,-442,611,-442]],["closePath",[]],["moveTo",[606,178]],["curveTo",[396,178,287,338,287,568]],["curveTo",[287,791,393,971,606,971]],["curveTo",[812,971,920,805,920,568]],["curveTo",[920,324,809,178,606,178]],["closePath",[]]]},{"char":"h","glyph":39,"commands":[["moveTo",[338,670]],["curveTo",[338,861,459,970,620,970]],["curveTo",[776,970,872,867,872,695]],["lineTo",[872,0]],["lineTo",[1053,0]],["lineTo",[1053,710]],["curveTo",[1053,993,900,1132,674,1132]],["curveTo",[528,1132,411,1073,338,924]],["lineTo",[338,1490]],["lineTo",[158,1490]],["lineTo",[158,0]],["lineTo",[338,0]],["closePath",[]]]},{"char":"i","glyph":40,"commands":[["moveTo",[158,0]],["lineTo",[338,0]],["lineTo",[338,1118]],["lineTo",[158,1118]],["closePath",[]],["moveTo",[249,1301]],["curveTo",[318,1301,375,1354,375,1420]],["curveTo",[375,1486,318,1539,249,1539]],["curveTo",[181,1539,124,1486,124,1420]],["curveTo",[124,1354,181,1301,249,1301]],["closePath",[]]]},{"char":"j","glyph":41,"commands":[["moveTo",[157,1118]],["lineTo",[157,-80]],["curveTo",[157,-191,111,-252,7,-252]],["lineTo",[-26,-252]],["lineTo",[-26,-418]],["lineTo",[10,-418]],["curveTo",[227,-418,339,-293,338,-80]],["lineTo",[338,1118]],["closePath",[]],["moveTo",[248,1301]],["curveTo",[317,1301,374,1354,374,1420]],["curveTo",[374,1486,317,1539,248,1539]],["curveTo",[180,1539,123,1486,123,1420]],["curveTo",[123,1354,180,1301,248,1301]],["closePath",[]]]},{"char":"k","glyph":42,"commands":[["moveTo",[158,0]],["lineTo",[338,0]],["lineTo",[338,412]],["lineTo",[456,523]],["lineTo",[865,0]],["lineTo",[1096,0]],["lineTo",[593,638]],["lineTo",[1061,1118]],["lineTo",[838,1118]],["lineTo",[360,625]],["lineTo",[338,625]],["lineTo",[338,1490]],["lineTo",[158,1490]],["closePath",[]]]},{"char":"l","glyph":43,"commands":[["moveTo",[338,1490]],["lineTo",[158,1490]],["lineTo",[158,0]],["lineTo",[338,0]],["closePath",[]]]},{"char":"m","glyph":44,"commands":[["moveTo",[158,0]],["lineTo",[338,0]],["lineTo",[338,704]],["curveTo",[338,882,457,974,588,974]],["curveTo",[714,974,807,891,807,767]],["lineTo",[807,0]],["lineTo",[987,0]],["lineTo",[987,730]],["curveTo",[987,871,1079,974,1230,974]],["curveTo",[1350,974,1455,910,1455,749]],["lineTo",[1455,0]],["lineTo",[1636,0]],["lineTo",[1636,754]],["curveTo",[1636,1013,1478,1138,1284,1138]],["curveTo",[1122,1138,985,1051,932,910]],["curveTo",[892,1048,794,1138,647,1138]],["curveTo",[515,1138,389,1064,334,913]],["lineTo",[333,1118]],["lineTo",[158,1118]],["closePath",[]]]},{"char":"n","glyph":45,"commands":[["moveTo",[338,670]],["curveTo",[338,861,459,970,620,970]],["curveTo",[776,970,872,867,872,695]],["lineTo",[872,0]],["lineTo",[1052,0]],["lineTo",[1052,710]],["curveTo",[1052,993,900,1132,674,1132]],["curveTo",[524,1132,404,1069,332,911]],["lineTo",[331,1118]],["lineTo",[158,1118]],["lineTo",[158,0]],["lineTo",[338,0]],["closePath",[]]]},{"char":"o","glyph":46,"commands":[["moveTo",[613,-24]],["curveTo",[918,-24,1124,207,1124,552]],["curveTo",[1124,902,918,1132,613,1132]],["curveTo",[309,1132,104,902,104,552]],["curveTo",[104,207,309,-24,613,-24]],["closePath",[]],["moveTo",[613,137]],["curveTo",[390,137,287,332,287,552]],["curveTo",[287,775,390,971,613,971]],["curveTo",[839,971,942,774,942,552]],["curveTo",[942,332,839,137,613,137]],["closePath",[]]]},{"char":"p","glyph":48,"commands":[["moveTo",[158,-418]],["lineTo",[338,-418]],["lineTo",[338,173]],["lineTo",[352,173]],["curveTo",[389,111,464,-24,677,-24]],["curveTo",[956,-24,1150,200,1150,556]],["curveTo",[1150,910,955,1132,676,1132]],["curveTo",[458,1132,389,998,352,939]],["lineTo",[332,939]],["lineTo",[332,1118]],["lineTo",[158,1118]],["closePath",[]],["moveTo",[650,137]],["curveTo",[447,137,336,309,336,558]],["curveTo",[336,805,444,971,650,971]],["curveTo",[862,971,967,790,967,558]],["curveTo",[967,323,859,137,650,137]],["closePath",[]]]},{"char":"q","glyph":49,"commands":[["moveTo",[1096,-418]],["lineTo",[1096,1118]],["lineTo",[922,1118]],["lineTo",[922,939]],["lineTo",[902,939]],["curveTo",[865,998,796,1132,578,1132]],["curveTo",[299,1132,104,910,104,556]],["curveTo",[104,200,298,-24,577,-24]],["curveTo",[790,-24,865,111,902,173]],["lineTo",[916,173]],["lineTo",[916,-418]],["closePath",[]],["moveTo",[604,137]],["curveTo",[395,137,287,323,287,558]],["curveTo",[287,790,392,971,604,971]],["curveTo",[810,971,918,805,918,558]],["curveTo",[918,309,807,137,604,137]],["closePath",[]]]},{"char":"r","glyph":50,"commands":[["moveTo",[158,0]],["lineTo",[338,0]],["lineTo",[338,700]],["curveTo",[338,851,456,960,617,960]],["curveTo",[665,960,714,952,725,950]],["lineTo",[725,1131]],["curveTo",[704,1132,660,1134,634,1134]],["curveTo",[501,1134,385,1059,344,946]],["lineTo",[332,946]],["lineTo",[332,1118]],["lineTo",[158,1118]],["closePath",[]]]},{"char":"s","glyph":51,"commands":[["moveTo",[538,-24]],["curveTo",[800,-24,979,119,979,315]],["curveTo",[979,468,884,569,681,617]],["lineTo",[512,657]],["curveTo",[381,688,324,734,324,817]],["curveTo",[324,910,424,979,557,979]],["curveTo",[701,979,764,901,795,821]],["lineTo",[958,863]],["curveTo",[907,1020,786,1132,556,1132]],["curveTo",[318,1132,142,998,142,806]],["curveTo",[142,649,239,549,442,501]],["lineTo",[628,457]],["curveTo",[738,431,792,380,792,303]],["curveTo",[792,211,692,134,536,134]],["curveTo",[402,134,311,189,279,312]],["lineTo",[108,271]],["curveTo",[148,78,308,-24,538,-24]],["closePath",[]]]},{"char":"t","glyph":53,"commands":[["moveTo",[598,1118]],["lineTo",[368,1118]],["lineTo",[368,1384]],["lineTo",[188,1384]],["lineTo",[188,1118]],["lineTo",[20,1118]],["lineTo",[20,964]],["lineTo",[188,964]],["lineTo",[188,276]],["curveTo",[188,93,302,-14,495,-14]],["curveTo",[541,-14,592,-7,629,6]],["lineTo",[592,158]],["curveTo",[562,152,523,146,500,146]],["curveTo",[405,146,368,190,368,290]],["lineTo",[368,964]],["lineTo",[598,964]],["closePath",[]]]},{"char":"u","glyph":54,"commands":[["moveTo",[537,-14]],["curveTo",[684,-14,806,46,879,209]],["lineTo",[879,0]],["lineTo",[1053,0]],["lineTo",[1053,1118]],["lineTo",[872,1118]],["lineTo",[872,448]],["curveTo",[872,257,751,148,591,148]],["curveTo",[435,148,338,251,338,423]],["lineTo",[338,1118]],["lineTo",[158,1118]],["lineTo",[158,408]],["curveTo",[158,125,311,-14,537,-14]],["closePath",[]]]},{"char":"v","glyph":56,"commands":[["moveTo",[481,0]],["lineTo",[670,0]],["lineTo",[1097,1118]],["lineTo",[900,1118]],["lineTo",[662,455]],["curveTo",[627,358,601,260,576,167]],["curveTo",[550,260,524,358,489,455]],["lineTo",[251,1118]],["lineTo",[54,1118]],["closePath",[]]]},{"char":"w","glyph":57,"commands":[["moveTo",[409,0]],["lineTo",[588,0]],["lineTo",[733,506]],["curveTo",[769,631,804,771,838,913]],["curveTo",[872,770,906,634,943,506]],["lineTo",[1088,0]],["lineTo",[1267,0]],["lineTo",[1606,1118]],["lineTo",[1415,1118]],["lineTo",[1279,630]],["curveTo",[1245,508,1208,367,1171,196]],["curveTo",[1136,369,1100,511,1068,630]],["lineTo",[936,1118]],["lineTo",[744,1118]],["lineTo",[610,630]],["curveTo",[576,506,540,363,504,193]],["curveTo",[467,371,429,513,397,630]],["lineTo",[261,1118]],["lineTo",[70,1118]],["closePath",[]]]},{"char":"x","glyph":58,"commands":[["moveTo",[65,0]],["lineTo",[273,0]],["lineTo",[439,251]],["curveTo",[489,327,523,395,557,458]],["curveTo",[594,395,631,327,680,251]],["lineTo",[844,0]],["lineTo",[1053,0]],["lineTo",[661,564]],["lineTo",[1035,1118]],["lineTo",[829,1118]],["lineTo",[687,901]],["curveTo",[635,822,601,750,565,683]],["curveTo",[526,750,488,822,437,901]],["lineTo",[298,1118]],["lineTo",[88,1118]],["lineTo",[458,574]],["closePath",[]]]},{"char":"y","glyph":59,"commands":[["moveTo",[140,-405]],["curveTo",[159,-414,208,-426,272,-426]],["curveTo",[430,-426,538,-345,606,-167]],["lineTo",[1097,1118]],["lineTo",[901,1118]],["lineTo",[661,455]],["curveTo",[626,358,600,261,575,168]],["curveTo",[549,261,524,357,489,455]],["lineTo",[251,1118]],["lineTo",[54,1118]],["lineTo",[481,-6]],["lineTo",[449,-99]],["curveTo",[394,-259,315,-283,205,-254]],["lineTo",[186,-249]],["closePath",[]]]},{"char":"z","glyph":60,"commands":[["moveTo",[126,0]],["lineTo",[1005,0]],["lineTo",[1005,167]],["lineTo",[376,167]],["lineTo",[376,178]],["lineTo",[985,975]],["lineTo",[985,1118]],["lineTo",[146,1118]],["lineTo",[146,951]],["lineTo",[753,951]],["lineTo",[753,940]],["lineTo",[126,134]],["closePath",[]]]},{"char":"{","glyph":80,"commands":[["moveTo",[750,-279]],["lineTo",[750,-130]],["curveTo",[580,-130,528,-54,528,115]],["lineTo",[528,399]],["curveTo",[528,516,491,606,323,641]],["lineTo",[323,661]],["curveTo",[491,695,528,784,528,901]],["lineTo",[528,1187]],["curveTo",[528,1356,581,1431,750,1430]],["lineTo",[750,1581]],["curveTo",[464,1581,362,1463,362,1190]],["lineTo",[362,960]],["curveTo",[362,814,314,750,178,742]],["lineTo",[144,742]],["lineTo",[144,560]],["lineTo",[177,560]],["curveTo",[314,551,362,487,362,340]],["lineTo",[362,112]],["curveTo",[362,-162,464,-279,750,-279]],["closePath",[]]]},{"char":"|","glyph":85,"commands":[["moveTo",[419,1970]],["lineTo",[262,1970]],["lineTo",[262,-480]],["lineTo",[419,-480]],["closePath",[]]]},{"char":"}","glyph":81,"commands":[["moveTo",[123,-279]],["curveTo",[408,-279,510,-162,510,112]],["lineTo",[510,340]],["curveTo",[510,497,565,560,729,561]],["lineTo",[729,741]],["lineTo",[722,741]],["curveTo",[564,743,510,804,510,960]],["lineTo",[510,1190]],["curveTo",[510,1463,408,1581,123,1581]],["lineTo",[123,1430]],["curveTo",[293,1430,345,1355,345,1187]],["lineTo",[345,901]],["curveTo",[345,784,382,695,549,661]],["lineTo",[549,641]],["curveTo",[382,606,345,515,345,399]],["lineTo",[345,115]],["curveTo",[345,-54,293,-130,123,-130]],["closePath",[]]]},{"char":"~","glyph":98,"commands":[["moveTo",[168,439]],["lineTo",[332,439]],["curveTo",[330,566,387,616,465,616]],["curveTo",[515,616,548,594,620,532]],["curveTo",[723,443,796,400,897,400]],["curveTo",[1044,400,1190,493,1187,740]],["lineTo",[1023,740]],["curveTo",[1023,624,972,563,890,563]],["curveTo",[839,563,805,586,735,647]],["curveTo",[627,741,556,780,458,780]],["curveTo",[309,780,164,687,168,439]],["closePath",[]]]},{"char":"Ä","glyph":2,"commands":[["moveTo",[52,0]],["lineTo",[254,0]],["lineTo",[398,416]],["lineTo",[1012,416]],["lineTo",[1161,0]],["lineTo",[1361,0]],["lineTo",[813,1490]],["lineTo",[593,1490]],["closePath",[]],["moveTo",[456,582]],["lineTo",[561,884]],["curveTo",[593,976,639,1118,702,1338]],["curveTo",[765,1122,809,984,845,884]],["lineTo",[953,582]],["closePath",[]],["moveTo",[496,1673]],["curveTo",[561,1673,614,1727,614,1792]],["curveTo",[614,1857,561,1910,496,1910]],["curveTo",[430,1910,377,1857,377,1792]],["curveTo",[377,1727,430,1673,496,1673]],["closePath",[]],["moveTo",[917,1673]],["curveTo",[982,1673,1035,1727,1035,1792]],["curveTo",[1035,1857,982,1910,917,1910]],["curveTo",[851,1910,798,1857,798,1792]],["curveTo",[798,1727,851,1673,917,1673]],["closePath",[]]]},{"char":"Ö","glyph":17,"commands":[["moveTo",[784,-20]],["curveTo",[1166,-20,1444,272,1444,744]],["curveTo",[1444,1217,1166,1510,784,1510]],["curveTo",[402,1510,122,1217,122,744]],["curveTo",[122,272,402,-20,784,-20]],["closePath",[]],["moveTo",[784,156]],["curveTo",[516,156,307,364,307,744]],["curveTo",[307,1126,516,1334,784,1334]],["curveTo",[1050,1334,1259,1127,1259,744]],["curveTo",[1259,363,1050,156,784,156]],["closePath",[]],["moveTo",[574,1673]],["curveTo",[639,1673,692,1727,692,1792]],["curveTo",[692,1857,639,1910,574,1910]],["curveTo",[508,1910,455,1857,455,1792]],["curveTo",[455,1727,508,1673,574,1673]],["closePath",[]],["moveTo",[995,1673]],["curveTo",[1060,1673,1113,1727,1113,1792]],["curveTo",[1113,1857,1060,1910,995,1910]],["curveTo",[929,1910,876,1857,876,1792]],["curveTo",[876,1727,929,1673,995,1673]],["closePath",[]]]},{"char":"Ü","glyph":24,"commands":[["moveTo",[763,-24]],["curveTo",[1114,-24,1344,200,1344,507]],["lineTo",[1344,1490]],["lineTo",[1154,1490]],["lineTo",[1154,522]],["curveTo",[1154,308,1006,152,763,152]],["curveTo",[519,152,370,308,370,522]],["lineTo",[370,1490]],["lineTo",[180,1490]],["lineTo",[180,507]],["curveTo",[180,200,410,-24,763,-24]],["closePath",[]],["moveTo",[553,1673]],["curveTo",[618,1673,671,1727,671,1792]],["curveTo",[671,1857,618,1910,553,1910]],["curveTo",[487,1910,434,1857,434,1792]],["curveTo",[434,1727,487,1673,553,1673]],["closePath",[]],["moveTo",[974,1673]],["curveTo",[1039,1673,1092,1727,1092,1792]],["curveTo",[1092,1857,1039,1910,974,1910]],["curveTo",[908,1910,855,1857,855,1792]],["curveTo",[855,1727,908,1673,974,1673]],["closePath",[]]]},{"char":"ä","glyph":31,"commands":[["moveTo",[471,-26]],["curveTo",[660,-26,765,73,803,152]],["lineTo",[815,152]],["lineTo",[815,0]],["lineTo",[992,0]],["lineTo",[992,737]],["curveTo",[992,1086,723,1132,573,1132]],["curveTo",[400,1132,208,1070,122,867]],["lineTo",[295,810]],["curveTo",[330,888,417,974,578,974]],["curveTo",[733,974,812,894,812,755]],["lineTo",[812,749]],["curveTo",[812,666,724,672,525,647]],["curveTo",[318,621,90,574,90,318]],["curveTo",[90,98,259,-26,471,-26]],["closePath",[]],["moveTo",[502,133]],["curveTo",[368,133,271,193,271,309]],["curveTo",[271,436,386,478,517,495]],["curveTo",[586,504,782,524,812,559]],["lineTo",[812,404]],["curveTo",[812,267,701,133,502,133]],["closePath",[]],["moveTo",[348,1301]],["curveTo",[413,1301,466,1355,466,1420]],["curveTo",[466,1485,413,1538,348,1538]],["curveTo",[282,1538,229,1485,229,1420]],["curveTo",[229,1355,282,1301,348,1301]],["closePath",[]],["moveTo",[769,1301]],["curveTo",[834,1301,887,1355,887,1420]],["curveTo",[887,1485,834,1538,769,1538]],["curveTo",[703,1538,650,1485,650,1420]],["curveTo",[650,1355,703,1301,769,1301]],["closePath",[]]]},{"char":"ö","glyph":47,"commands":[["moveTo",[613,-24]],["curveTo",[918,-24,1124,207,1124,552]],["curveTo",[1124,902,918,1132,613,1132]],["curveTo",[309,1132,104,902,104,552]],["curveTo",[104,207,309,-24,613,-24]],["closePath",[]],["moveTo",[613,137]],["curveTo",[390,137,287,332,287,552]],["curveTo",[287,775,390,971,613,971]],["curveTo",[839,971,942,774,942,552]],["curveTo",[942,332,839,137,613,137]],["closePath",[]],["moveTo",[403,1301]],["curveTo",[468,1301,521,1355,521,1420]],["curveTo",[521,1485,468,1538,403,1538]],["curveTo",[337,1538,284,1485,284,1420]],["curveTo",[284,1355,337,1301,403,1301]],["closePath",[]],["moveTo",[824,1301]],["curveTo",[889,1301,942,1355,942,1420]],["curveTo",[942,1485,889,1538,824,1538]],["curveTo",[758,1538,705,1485,705,1420]],["curveTo",[705,1355,758,1301,824,1301]],["closePath",[]]]},{"char":"ü","glyph":55,"commands":[["moveTo",[537,-14]],["curveTo",[684,-14,806,46,879,209]],["lineTo",[879,0]],["lineTo",[1053,0]],["lineTo",[1053,1118]],["lineTo",[872,1118]],["lineTo",[872,448]],["curveTo",[872,257,751,148,591,148]],["curveTo",[435,148,338,251,338,423]],["lineTo",[338,1118]],["lineTo",[158,1118]],["lineTo",[158,408]],["curveTo",[158,125,311,-14,537,-14]],["closePath",[]],["moveTo",[395,1301]],["curveTo",[460,1301,513,1355,513,1420]],["curveTo",[513,1485,460,1538,395,1538]],["curveTo",[329,1538,276,1485,276,1420]],["curveTo",[276,1355,329,1301,395,1301]],["closePath",[]],["moveTo",[816,1301]],["curveTo",[881,1301,934,1355,934,1420]],["curveTo",[934,1485,881,1538,816,1538]],["curveTo",[750,1538,697,1485,697,1420]],["curveTo",[697,1355,750,1301,816,1301]],["closePath",[]]]},{"char":"ß","glyph":52,"commands":[["moveTo",[180,0]],["lineTo",[362,0]],["lineTo",[362,1104]],["curveTo",[362,1245,471,1343,621,1343]],["curveTo",[778,1343,875,1234,875,1097]],["curveTo",[875,965,785,841,641,841]],["lineTo",[550,841]],["lineTo",[550,671]],["lineTo",[679,671]],["curveTo",[854,671,964,563,964,412]],["curveTo",[964,269,864,167,709,167]],["lineTo",[544,167]],["lineTo",[544,0]],["lineTo",[709,0]],["curveTo",[973,0,1146,173,1146,410]],["curveTo",[1146,615,1016,756,823,775]],["lineTo",[823,789]],["curveTo",[965,838,1057,959,1057,1125]],["curveTo",[1057,1364,867,1510,616,1510]],["curveTo",[360,1510,180,1357,180,1107]],["closePath",[]]]},{"char":"€","glyph":62,"commands":[["moveTo",[1070,963]],["lineTo",[442,963]],["curveTo",[499,1206,654,1334,847,1334]],["curveTo",[975,1334,1081,1278,1170,1198]],["lineTo",[1243,1357]],["curveTo",[1135,1458,997,1510,847,1510]],["curveTo",[547,1510,320,1306,253,963]],["lineTo",[105,963]],["lineTo",[52,818]],["lineTo",[235,818]],["curveTo",[234,794,233,769,233,744]],["curveTo",[233,720,234,696,235,672]],["lineTo",[105,672]],["lineTo",[52,525]],["lineTo",[253,525]],["curveTo",[320,183,547,-20,847,-20]],["curveTo",[996,-20,1138,29,1239,125]],["lineTo",[1164,282]],["curveTo",[1082,213,976,156,847,156]],["curveTo",[655,156,500,283,443,525]],["lineTo",[876,525]],["lineTo",[943,672]],["lineTo",[421,672]],["curveTo",[420,695,419,719,419,744]],["curveTo",[419,769,420,794,421,818]],["lineTo",[1002,818]],["closePath",[]]]},{"char":"é","glyph":36,"commands":[["moveTo",[628,-24]],["curveTo",[857,-24,1024,91,1076,260]],["lineTo",[902,308]],["curveTo",[860,194,761,136,629,136]],["curveTo",[431,136,295,264,286,498]],["lineTo",[1094,498]],["lineTo",[1094,573]],["curveTo",[1094,986,844,1132,607,1132]],["curveTo",[302,1132,104,893,104,550]],["curveTo",[104,206,304,-24,628,-24]],["closePath",[]],["moveTo",[287,650]],["curveTo",[298,821,415,972,607,972]],["curveTo",[791,972,894,840,911,650]],["closePath",[]],["moveTo",[524,1264]],["lineTo",[671,1264]],["lineTo",[865,1558]],["lineTo",[664,1558]],["closePath",[]]]}]}
\ No newline at end of file
diff --git a/tests/CADability.Tests/Files/Fonts/InterCidTest.otf b/tests/CADability.Tests/Files/Fonts/InterCidTest.otf
new file mode 100644
index 00000000..0e4fba29
Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/InterCidTest.otf differ
diff --git a/tests/CADability.Tests/Files/Fonts/make_cff_test_fonts.py b/tests/CADability.Tests/Files/Fonts/make_cff_test_fonts.py
new file mode 100644
index 00000000..f2625605
--- /dev/null
+++ b/tests/CADability.Tests/Files/Fonts/make_cff_test_fonts.py
@@ -0,0 +1,187 @@
+# Writes the CFF test fonts of TrueTypeFontTests and their reference data with fontTools (pip install fonttools):
+#
+# InterCffTest.otf name-keyed CFF font
+# InterCidTest.otf the same glyphs as a CID-keyed CFF font with two font dicts (FDArray, FDSelect)
+# InterCffTest.reference.json the outlines as fontTools draws them, the same for both fonts
+#
+# Both are made from a subset of Inter Regular (SIL Open Font License 1.1, https://rsms.me/inter), which uses only the
+# general charstring operators and no subroutines. To test the whole Type 2 charstring interpreter, the charstrings are
+# rewritten without changing the outlines:
+# - the specialized operators (hvcurveto, vhcurveto, hhcurveto, vvcurveto, hlineto, vlineto, rcurveline, rlinecurve)
+# by fontTools' specializer,
+# - flex for two consecutive curves of some glyphs,
+# - the body of a third of the glyphs moved into a local subroutine, of another third into a global one.
+#
+# Run from this directory with the path of Inter-Regular.otf: python make_cff_test_fonts.py /usr/share/fonts/opentype/inter/Inter-Regular.otf
+import copy, json, sys
+from fontTools.ttLib import TTFont
+from fontTools.subset import Subsetter, Options
+from fontTools.cffLib import specializer, SubrsIndex, FDArrayIndex, FontDict, FDSelect
+from fontTools.misc.psCharStrings import T2CharString
+from fontTools.pens.recordingPen import RecordingPen
+
+UNICODES = list(range(0x20, 0x7F)) + [0xC4, 0xD6, 0xDC, 0xE4, 0xF6, 0xFC, 0xDF, 0x20AC, 0xE9]
+
+
+def bias(count):
+ return 107 if count < 1240 else 1131 if count < 33900 else 32768
+
+
+def subset(path):
+ options = Options()
+ options.layout_features = []
+ font = TTFont(path)
+ subsetter = Subsetter(options)
+ subsetter.populate(unicodes=UNICODES)
+ subsetter.subset(font)
+ for tag in ('GDEF', 'GPOS', 'GSUB'): # they name glyphs, which the CID font renames
+ if tag in font:
+ del font[tag]
+ return font
+
+
+def rewrite(program, index):
+ """The program with specialized operators and, for some glyphs, flex instead of two curves."""
+ commands = specializer.programToCommands(program)
+ if index % 4 == 0:
+ for i in range(len(commands) - 1):
+ a, b = commands[i], commands[i + 1]
+ if a[0] == 'rrcurveto' and b[0] == 'rrcurveto' and len(a[1]) == 6 and len(b[1]) == 6:
+ commands[i:i + 2] = [('flex', a[1] + b[1] + [50])]
+ break
+ return specializer.specializeProgram(specializer.commandsToProgram(commands))
+
+
+def body(program):
+ assert program[-1] == 'endchar'
+ return program[:-1]
+
+
+def draw_all(font):
+ glyphs = font.getGlyphSet()
+ cmap = font.getBestCmap()
+ result = []
+ for code in UNICODES:
+ name = cmap[code]
+ pen = RecordingPen()
+ glyphs[name].draw(pen)
+ result.append({'char': chr(code), 'glyph': font.getGlyphID(name),
+ 'commands': [[op, [c for p in pts for c in p]] for op, pts in pen.value]})
+ return result
+
+
+def name_keyed(font):
+ cff = font['CFF '].cff
+ top = cff.topDictIndex[0]
+ private = top.Private
+ private.Subrs = SubrsIndex()
+ order = font.getGlyphOrder()
+ reference = draw_all(font) # before rewriting: the outlines must not change
+ local, glob = [], []
+ for index, name in enumerate(order):
+ cs = top.CharStrings[name]
+ cs.decompile()
+ program = rewrite(cs.program, index)
+ if index % 3 == 1:
+ local.append(body(program) + ['return'])
+ program = [len(local) - 1, 'callsubr', 'endchar'] # bias is added below
+ elif index % 3 == 2:
+ glob.append(body(program) + ['return'])
+ program = [len(glob) - 1, 'callgsubr', 'endchar']
+ cs.program = program
+ for p in local:
+ private.Subrs.append(T2CharString(program=p, private=private, globalSubrs=cff.GlobalSubrs))
+ for p in glob:
+ cff.GlobalSubrs.append(T2CharString(program=p, private=private, globalSubrs=cff.GlobalSubrs))
+ for name in order: # the subroutine numbers are stored without the bias
+ cs = top.CharStrings[name]
+ if len(cs.program) == 3 and cs.program[1] in ('callsubr', 'callgsubr'):
+ count = len(local) if cs.program[1] == 'callsubr' else len(glob)
+ cs.program[0] -= bias(count)
+ return reference
+
+
+def to_cid(font):
+ """Converts the name-keyed font to a CID-keyed one with two font dicts: even glyphs use the first, odd glyphs the second,
+ each with its own private dict and local subroutines."""
+ cmap_tables = font['cmap'].tables # read the tables that name glyphs before the glyphs are renamed
+ metrics = dict(font['hmtx'].metrics)
+ cff = font['CFF '].cff
+ top = cff.topDictIndex[0]
+ order = font.getGlyphOrder()
+ new_names = ['.notdef'] + ['cid%05d' % i for i in range(1, len(order))]
+ rename = dict(zip(order, new_names))
+ programs = []
+ for name in order:
+ cs = top.CharStrings[name]
+ cs.decompile()
+ programs.append(copy.copy(cs.program))
+ old_private = top.Private
+ old_local = [s.program if s.program is not None else (s.decompile() or s.program) for s in old_private.Subrs]
+ fd_array = FDArrayIndex()
+ privates = []
+ for k in range(2):
+ fd = FontDict()
+ private = copy.copy(old_private)
+ private.Subrs = SubrsIndex()
+ fd.Private = private
+ fd.FontName = 'InterCidTest-%d' % k
+ fd_array.append(fd)
+ privates.append(private)
+ # local subroutines move to the font dict of the glyph that calls them
+ local_bias = bias(len(old_local))
+ for gid, program in enumerate(programs):
+ fd = gid % 2
+ if len(program) == 3 and program[1] == 'callsubr':
+ subr = old_local[program[0] + local_bias]
+ privates[fd].Subrs.append(T2CharString(program=list(subr), private=privates[fd], globalSubrs=cff.GlobalSubrs))
+ program[0] = len(privates[fd].Subrs) - 1 # bias added below
+ for gid, program in enumerate(programs):
+ fd = gid % 2
+ if len(program) == 3 and program[1] == 'callsubr':
+ program[0] -= bias(len(privates[fd].Subrs))
+ top.ROS = ('Adobe', 'Identity', 0)
+ top.CIDCount = len(order)
+ top.FDArray = fd_array
+ select = FDSelect()
+ select.format = 3
+ select.gidArray = [gid % 2 for gid in range(len(order))]
+ top.FDSelect = select
+ del top.rawDict['Private']
+ del top.Private
+ charstrings = top.CharStrings
+ new_dict = {}
+ for gid, name in enumerate(order):
+ cs = T2CharString(program=programs[gid], private=privates[gid % 2], globalSubrs=cff.GlobalSubrs)
+ cs.fdSelectIndex = gid % 2
+ new_dict[rename[name]] = cs
+ charstrings.charStrings = new_dict
+ charstrings.charStringsAreIndexed = False
+ charstrings.charStringsIndex = None
+ top.charset = new_names
+ for table in cmap_tables:
+ table.cmap = {code: rename[name] for code, name in table.cmap.items()}
+ font['hmtx'].metrics = {rename[name]: value for name, value in metrics.items()}
+ font.setGlyphOrder(new_names)
+ font['post'].formatType = 3.0
+
+
+def main(path):
+ font = subset(path)
+ reference = name_keyed(font)
+ font.save('InterCffTest.otf')
+ check = TTFont('InterCffTest.otf')
+ assert draw_all(check) == reference, 'rewriting the charstrings changed the outlines'
+ json.dump({'glyphs': reference}, open('InterCffTest.reference.json', 'w', encoding='utf-8'), ensure_ascii=False, separators=(',', ':'))
+
+ font = TTFont('InterCffTest.otf')
+ to_cid(font)
+ font.save('InterCidTest.otf')
+ check = TTFont('InterCidTest.otf')
+ top = check['CFF '].cff.topDictIndex[0]
+ assert hasattr(top, 'ROS') and len(top.FDArray) == 2, 'not a CID-keyed font with two font dicts'
+ assert [g['commands'] for g in draw_all(check)] == [g['commands'] for g in reference], 'the CID font has other outlines'
+
+
+if __name__ == '__main__':
+ main(sys.argv[1])
diff --git a/tests/CADability.Tests/FontProviderTests.cs b/tests/CADability.Tests/FontProviderTests.cs
index 8e2b7be0..c364a077 100644
--- a/tests/CADability.Tests/FontProviderTests.cs
+++ b/tests/CADability.Tests/FontProviderTests.cs
@@ -130,7 +130,7 @@ public void UnderlineAndStrikeoutAddBars()
public void MissingStyleFallsBackToAnExistingFace()
{
ManagedFontProvider provider = new ManagedFontProvider(new[] { FontDirectory() }, false);
- CollectionAssert.AreEqual(new[] { "Liberation Sans" }, provider.GetFamilyNames());
+ CollectionAssert.Contains(provider.GetFamilyNames(), "Liberation Sans");
FontFace bold = provider.GetFace("liberation sans", true, false);
Assert.IsNotNull(bold, "the family name is not case sensitive");
Assert.IsFalse(bold.IsBold, "only the regular face is there");