diff --git a/CADability.Forms/CadCanvas.cs b/CADability.Forms/CadCanvas.cs index b37d69694..67259ff74 100644 --- a/CADability.Forms/CadCanvas.cs +++ b/CADability.Forms/CadCanvas.cs @@ -61,6 +61,8 @@ private static CADability.Substitutes.PaintEventArgs Subst(System.Windows.Forms. private string currenToolTip; private string currentCursor; private Action callbackCollapsed; + // texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider + static CadCanvas() { GdiFontProvider.MakeDefault(); } public CadCanvas() : base() { toolTip = new ToolTip(); diff --git a/CADability.Forms/CadControl.cs b/CADability.Forms/CadControl.cs index d0fdb7d58..0f38fee6d 100644 --- a/CADability.Forms/CadControl.cs +++ b/CADability.Forms/CadControl.cs @@ -23,6 +23,8 @@ public partial class CadControl : UserControl, ICommandHandler #endregion PRIVATE FIELDS + // texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider + static CadControl() { GdiFontProvider.MakeDefault(); } /// /// Default constructor /// diff --git a/CADability.Forms/CadFrame.cs b/CADability.Forms/CadFrame.cs index 8417224d5..ca8df7a5a 100644 --- a/CADability.Forms/CadFrame.cs +++ b/CADability.Forms/CadFrame.cs @@ -20,6 +20,9 @@ namespace CADability.Forms /// public class CadFrame : FrameImpl, IUIService { + // texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider + static CadFrame() { GdiFontProvider.MakeDefault(); } + #region PRIVATE FIELDS private ICommandHandler commandHandler; diff --git a/CADability.Forms/GdiFontProvider.cs b/CADability.Forms/GdiFontProvider.cs new file mode 100644 index 000000000..2875cd91d --- /dev/null +++ b/CADability.Forms/GdiFontProvider.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; + +namespace CADability.Forms +{ + /// + /// A that gets the fonts from GDI and GDI+, as CADability did before issue #412: the glyph + /// outlines from , the widths + /// and the kerning from gdi32. It knows every font Windows knows (also OpenType fonts with CFF outlines) and + /// synthesizes bold and italic faces a family does not have. As soon as one of the main classes of CADability.Forms is + /// used (, , , or + /// ), this provider becomes the default, so that Windows Forms applications show their texts + /// as before. An application that sets itself keeps its choice, e.g. + /// FontProvider.Current = new ManagedFontProvider(); for the same fonts as on the other platforms. + /// + public class GdiFontProvider : FontProvider + { + private static int madeDefault; + /// + /// Makes a the default font provider, unless the application has chosen one. Called by the + /// static constructors of the main classes of CADability.Forms; only the first call does something. + /// + internal static void MakeDefault() + { + if (Interlocked.Exchange(ref madeDefault, 1) == 0) SetDefault(() => new GdiFontProvider()); + } + + private readonly Dictionary<(string family, bool bold, bool italic), GdiFontFace> faces = new Dictionary<(string, bool, bool), GdiFontFace>(); + private HashSet familyNames; // upper case + + /// + public override string[] GetFamilyNames() + { + return FontFamily.Families.Select(f => f.Name).ToArray(); + } + /// + public override FontFace GetFace(string familyName, bool bold, bool italic) + { + if (string.IsNullOrEmpty(familyName)) return null; + lock (faces) + { + if (familyNames == null) familyNames = new HashSet(GetFamilyNames().Select(n => n.ToUpperInvariant())); + if (!familyNames.Contains(familyName.ToUpperInvariant())) return null; + if (!faces.TryGetValue((familyName.ToUpperInvariant(), bold, italic), out GdiFontFace face)) + { + face = new GdiFontFace(new FontFamily(familyName), bold, italic); + faces[(familyName.ToUpperInvariant(), bold, italic)] = face; + } + return face; + } + } + /// + /// The generic sans serif family of GDI+, as before issue #412. + /// + public override FontFace GetDefaultFace(bool bold, bool italic) + { + using (FontFamily sansSerif = new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif)) + { + return GetFace(sansSerif.Name, bold, italic); + } + } + } + + /// + /// A face of the . + /// + internal sealed class GdiFontFace : FontFace + { + private readonly FontFamily family; + private readonly FontStyle style; // bold and italic only + private readonly int em; + private readonly Dictionary widths = new Dictionary(); + private Dictionary<(int, int), double> kerning; + + public GdiFontFace(FontFamily family, bool bold, bool italic) + { + this.family = family; + FontStyle requested = (bold ? FontStyle.Bold : FontStyle.Regular) | (italic ? FontStyle.Italic : FontStyle.Regular); + style = requested; + if (!family.IsStyleAvailable(style)) + { + if (family.IsStyleAvailable(FontStyle.Regular)) style = FontStyle.Regular; + else if (family.IsStyleAvailable(FontStyle.Bold)) style = FontStyle.Bold; + else if (family.IsStyleAvailable(FontStyle.Italic)) style = FontStyle.Italic; + } + em = family.GetEmHeight(style); + if (em == 0) em = 1000; + } + public override string FamilyName => family.Name; + public override bool IsBold => (style & FontStyle.Bold) != 0; + public override bool IsItalic => (style & FontStyle.Italic) != 0; + public override int EmHeight => em; + public override int CellAscent => family.GetCellAscent(style); + public override int CellDescent => family.GetCellDescent(style); + public override int LineSpacing => family.GetLineSpacing(style); + + public override double GetAdvanceWidth(int codePoint) + { + lock (widths) + { + if (widths.TryGetValue(codePoint, out double width)) return width; + WithSelectedFont(hdc => + { + if (codePoint <= 0xFFFF) + { + ABC[] abc = new ABC[1]; + GetCharABCWidths(hdc, (uint)codePoint, (uint)codePoint, abc); + width = (abc[0].abcA + abc[0].abcB + abc[0].abcC) / (double)em; + } + else + { // GetCharABCWidths only takes UTF-16 chars: measure the surrogate pair as a string + string s = char.ConvertFromUtf32(codePoint); + GetTextExtentPoint32(hdc, s, s.Length, out SIZE size); + width = size.cx / (double)em; + } + }); + widths[codePoint] = width; + return width; + } + } + public override double GetKerning(int left, int right) + { + lock (widths) + { + if (kerning == null) + { + Dictionary<(int, int), double> pairs = new Dictionary<(int, int), double>(); + WithSelectedFont(hdc => + { + int num = GetKerningPairs(hdc, 0, null); + if (num > 0) + { + KERNINGPAIR[] kp = new KERNINGPAIR[num]; + GetKerningPairs(hdc, num, kp); + for (int i = 0; i < kp.Length; ++i) pairs[((ushort)kp[i].wFirst, (ushort)kp[i].wSecond)] = kp[i].iKernAmount / (double)em; + } + }); + kerning = pairs; + } + kerning.TryGetValue((left, right), out double res); + return res; + } + } + public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types) + { + FontStyle fs = style | (underline ? FontStyle.Underline : FontStyle.Regular) | (strikeout ? FontStyle.Strikeout : FontStyle.Regular); + using (GraphicsPath path = new GraphicsPath()) + using (StringFormat sf = (StringFormat)StringFormat.GenericTypographic.Clone()) + { + sf.LineAlignment = StringAlignment.Near; + sf.Alignment = StringAlignment.Near; + path.AddString(AsString(codePoint), family, (int)fs, 1.0f, new PointF(0.0f, 0.0f), sf); + if (path.PointCount == 0) + { + points = new PointF[0]; + types = new byte[0]; + } + else + { + points = path.PathPoints; + types = path.PathTypes; + } + } + } + + // a lone surrogate (not a valid code point) is passed on as the char it is + private static string AsString(int codePoint) => codePoint >= 0xD800 && codePoint <= 0xDFFF ? ((char)codePoint).ToString() : char.ConvertFromUtf32(codePoint); + + /// + /// Selects this face with the em height as pixel size into a device context for the duration of . + /// The device context only lives during the call, so nothing is left to a finalizer. + /// + private void WithSelectedFont(Action action) + { + IntPtr hdc = CreateCompatibleDC(IntPtr.Zero); + try + { + using (Font font = new Font(family, em, style, GraphicsUnit.Pixel)) + { + IntPtr hfont = font.ToHfont(); + IntPtr oldFont = SelectObject(hdc, hfont); + try + { + action(hdc); + } + finally + { + SelectObject(hdc, oldFont); + DeleteObject(hfont); + } + } + } + finally + { + DeleteDC(hdc); + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct ABC + { + public int abcA; + public uint abcB; + public int abcC; + } + [StructLayout(LayoutKind.Sequential)] + private struct KERNINGPAIR + { + public short wFirst; + public short wSecond; + public int iKernAmount; + } + [DllImport("gdi32.dll", EntryPoint = "GetCharABCWidthsW")] + private static extern bool GetCharABCWidths(IntPtr hdc, uint uFirstChar, uint uLastChar, [Out] ABC[] lpabc); + [StructLayout(LayoutKind.Sequential)] + private struct SIZE + { + public int cx; + public int cy; + } + [DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32W", CharSet = CharSet.Unicode)] + private static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int c, out SIZE psizl); + [DllImport("gdi32.dll", EntryPoint = "GetKerningPairsW")] + private static extern int GetKerningPairs(IntPtr hdc, int cPairs, [Out] KERNINGPAIR[] lpkrnpair); + [DllImport("gdi32.dll")] + private static extern IntPtr CreateCompatibleDC(IntPtr hdc); + [DllImport("gdi32.dll")] + private static extern bool DeleteDC(IntPtr hdc); + [DllImport("gdi32.dll")] + private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); + [DllImport("gdi32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + } +} diff --git a/CADability.Forms/PaintToGDI.cs b/CADability.Forms/PaintToGDI.cs index 6f16beaaf..939090140 100644 --- a/CADability.Forms/PaintToGDI.cs +++ b/CADability.Forms/PaintToGDI.cs @@ -65,6 +65,8 @@ public void Dispose() LazyInitializer.EnsureInitialized(ref fontFamilyNames, () => new HashSet(FontFamily.Families.Select(f => f.Name.ToUpper()))); + // texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider + static PaintToGDI() { GdiFontProvider.MakeDefault(); } public PaintToGDI(Projection projection, Graphics graphics) : this() { diff --git a/CADability.Forms/PaintToOpenGL.cs b/CADability.Forms/PaintToOpenGL.cs index b5d132510..2efd92718 100644 --- a/CADability.Forms/PaintToOpenGL.cs +++ b/CADability.Forms/PaintToOpenGL.cs @@ -28,6 +28,9 @@ internal PaintToOpenGLException(String msg) /// public class PaintToOpenGL : IPaintTo3D, IPaintTo3DFlatText { + // texts get their fonts from GDI as before issue #412, unless the application has chosen a font provider + static PaintToOpenGL() { GdiFontProvider.MakeDefault(); } + #region IPaintTo3D data public static int debugNumTriangles = 0; public static Thread MainThread = null; diff --git a/CADability/Bitmap.cs b/CADability/Bitmap.cs index ecf9940b2..90b4bf21c 100644 --- a/CADability/Bitmap.cs +++ b/CADability/Bitmap.cs @@ -8,6 +8,7 @@ using Point = System.Drawing.Point; using System.Drawing.Drawing2D; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Threading; @@ -15,13 +16,16 @@ namespace CADability.GeoObject { /// /// A bitmap as a rectangular or rhombiod face in 3D space. This object is mainly used for illustration or - /// as a background in a model. It is a GeoObject and may be part of a model. It is defined by the bitmap - /// bits and the location in space. + /// as a background in a model. It is a GeoObject and may be part of a model. It is defined by the image and the + /// location in space. The image is kept as the bytes of a PNG, JPEG, GIF or BMP file (), which + /// is also what project files contain; the kernel does not decode it (issue #412). /// [Serializable()] public class Picture : IGeoObjectImpl, ISerializable { - private Bitmap bitmap; + private byte[] imageData; // the bytes of the image file, null for no image + private Bitmap gdiBitmap; // decoded from imageData with GDI+ for painting (Windows), created on demand + private bool gdiFailed; // GDI+ cannot decode imageData (no GDI+ on this platform, or a format GDI+ does not know) private GeoPoint location; private GeoVector directionWidth, directionHeight; #region polymorph construction @@ -57,7 +61,7 @@ public static Picture Construct() #endregion /// /// Protected constructor: raises . Use to create a picture and - /// to define it. + /// to define it. /// protected Picture() { @@ -68,39 +72,119 @@ protected Picture() // System.Diagnostics.Trace.WriteLine("Destruktor von Picture"); //} /// - /// Sets the Data of the Bitmap. + /// Sets the image and the position of the picture. + /// + /// The bytes of a PNG, JPEG, GIF or BMP file, e.g. from ; + /// null for no image. The array is kept, so it must not be changed afterwards. + /// Position where the lower left point of the image will be displayed + /// Vector specifying the baseline of the image + /// Vector specifying the left side of the image + /// The data is not a PNG, JPEG, GIF or BMP file + public void Set(byte[] imageData, GeoPoint location, GeoVector directionWidth, GeoVector directionHeight) + { + EncodedImage.Validate(imageData, nameof(imageData)); + using (new Changing(this)) + { + SetImage(imageData, null); + this.location = location; + this.directionWidth = directionWidth; + this.directionHeight = directionHeight; + } + } + /// + /// Sets the Data of the Bitmap. The bitmap is encoded into : PNG, JPEG, GIF and BMP images keep + /// their format, any other image becomes PNG. Needs GDI+, i.e. Windows. /// /// The bitmap to be displayed /// Position where the lower left point of the bitmap will be displayed /// Vector specifying the baseline of the bitmap /// Vector specifying the left side of the bitmap + [Obsolete("Use Set(byte[], GeoPoint, GeoVector, GeoVector) with the bytes of the image file. A GDI+ Bitmap only works on Windows (issue #412).")] public void Set(Bitmap bitmap, GeoPoint location, GeoVector directionWidth, GeoVector directionHeight) { using (new Changing(this)) { - this.bitmap = bitmap; + SetImage(EncodedImage.FromGdiImage(bitmap), bitmap); this.location = location; this.directionWidth = directionWidth; this.directionHeight = directionHeight; } } /// - /// The bitmap, the contents of the Picture + /// The image of the picture as the bytes of a PNG, JPEG, GIF or BMP file, null if the picture has no image. This is + /// what project files contain. The array must not be changed; set a new array instead. Setting it raises the change + /// events and is undoable. + /// + /// The data is not a PNG, JPEG, GIF or BMP file + public byte[] ImageData + { + get + { + return imageData; + } + set + { + EncodedImage.Validate(value, nameof(value)); + using (new Changing(this)) + { + SetImage(value, null); + } + } + } + /// + /// The width of the image in pixels, read from the header of ; 0 without an image. + /// + public int PixelWidth => EncodedImage.TryGetSize(imageData, out int width, out _) ? width : 0; + /// + /// The height of the image in pixels, read from the header of ; 0 without an image. + /// + public int PixelHeight => EncodedImage.TryGetSize(imageData, out _, out int height) ? height : 0; + /// + /// Width divided by height of the image in pixels, 1 without an image. + /// + private double AspectRatio => PixelWidth > 0 && PixelHeight > 0 ? PixelWidth / (double)PixelHeight : 1.0; + /// + /// The bitmap, the contents of the Picture, decoded from with GDI+. Setting it encodes the + /// bitmap into . Needs GDI+, i.e. Windows. /// + [Obsolete("Use ImageData, the bytes of the image file. A GDI+ Bitmap only works on Windows (issue #412).")] public Bitmap Bitmap { get { - return bitmap; + if (imageData != null && gdiBitmap == null) gdiBitmap = EncodedImage.ToGdiBitmap(imageData); + return gdiBitmap; } set { using (new Changing(this)) { - bitmap = value; + SetImage(EncodedImage.FromGdiImage(value), value); } } } + private void SetImage(byte[] data, Bitmap decoded) + { + imageData = data; + gdiBitmap = decoded; + gdiFailed = false; + } + /// + /// The GDI+ bitmap to paint, null where GDI+ is not available or cannot decode the image. + /// + private Bitmap BitmapForPainting() + { + if (gdiBitmap != null || imageData == null || gdiFailed) return gdiBitmap; + try + { + gdiBitmap = EncodedImage.ToGdiBitmap(imageData); + } + catch (Exception e) when (e is PlatformNotSupportedException || e is TypeInitializationException || e is DllNotFoundException || e is ArgumentException || e is ExternalException) + { + gdiFailed = true; // e.g. on Linux: the picture is painted as its outline + } + return gdiBitmap; + } /// /// Position of the lower left point of the bitmap /// @@ -165,7 +249,7 @@ public void SetHeight(double l, bool keepAspectRatio) if (l == 0.0) return; using (new Changing(this)) { - double ar = Bitmap.Width / (double)Bitmap.Height; + double ar = AspectRatio; directionHeight = l * directionHeight.Normalized; if (keepAspectRatio) { @@ -186,7 +270,7 @@ public void SetWidth(double l, bool keepAspectRatio) if (l == 0.0) return; using (new Changing(this)) { - double ar = Bitmap.Width / (double)Bitmap.Height; + double ar = AspectRatio; directionWidth = l * directionWidth.Normalized; if (keepAspectRatio) { @@ -195,10 +279,11 @@ public void SetWidth(double l, bool keepAspectRatio) } } /// - /// The path where the Bitmap is located. This value is not used by the Picture object and may have any content. + /// The path where the image file is located. This value is not used by the Picture object and may have any content. /// Usually it is the path and filename, especially when the object is being generated interactively - /// by CADability's user interface. Setting the value does NOT load the specified bitmap. This must be done - /// separately by setting the property + /// by CADability's user interface. Setting the value does NOT load the specified image. This must be done + /// separately by setting the property. When an old project file has a picture whose image + /// cannot be read, the picture is loaded from this path, if the file exists. /// public string Path { get; set; } @@ -209,8 +294,11 @@ public void SetWidth(double l, bool keepAspectRatio) /// /// The plane as a reference system for the shape /// The shape for the clip operation + /// Needs GDI+, i.e. Windows. The clipped image is stored as PNG. public void Clip(Plane plane, CompoundShape shape) { + Bitmap bitmap = EncodedImage.ToGdiBitmap(imageData); + if (bitmap == null) return; Plane pln = new Plane(location, directionWidth, directionHeight); CompoundShape prsh = shape.Project(plane, pln); ModOp2D m = ModOp2D.Scale(bitmap.Width / directionWidth.Length, -bitmap.Height / directionHeight.Length); @@ -227,6 +315,9 @@ public void Clip(Plane plane, CompoundShape shape) graphics.SetClip(rg, CombineMode.Replace); graphics.DrawImage(clone, new System.Drawing.Point(0, 0)); graphics.Dispose(); + clone.Dispose(); + rg.Dispose(); + SetImage(EncodedImage.FromGdiImage(bitmap), bitmap); // a bitmap made in memory: PNG } } @@ -235,9 +326,21 @@ public void Clip(Plane plane, CompoundShape shape) /// . For callers that record the undo themselves. Not used within CADability. /// /// the new bitmap + [Obsolete("Use SetImageDataNoUndo with the bytes of the image file. A GDI+ Bitmap only works on Windows (issue #412).")] public void SetBitmapNoUndo(Bitmap bmp) { - bitmap = bmp; + SetImage(EncodedImage.FromGdiImage(bmp), bmp); + } + /// + /// Replaces the image without raising the change events and without an undo step, in contrast to setting + /// . For callers that record the undo themselves. + /// + /// the bytes of a PNG, JPEG, GIF or BMP file, or null + /// The data is not a PNG, JPEG, GIF or BMP file + public void SetImageDataNoUndo(byte[] imageData) + { + EncodedImage.Validate(imageData, nameof(imageData)); + SetImage(imageData, null); } #region IGeoObject override /// @@ -255,8 +358,7 @@ public override IGeoObject Clone() { Picture res = Picture.Construct(); res.location = location; - if (bitmap != null) res.bitmap = bitmap.Clone() as Bitmap; - else res.bitmap = null; + res.imageData = imageData; // never changed in place, so it can be shared res.directionWidth = directionWidth; res.directionHeight = directionHeight; return res; @@ -269,7 +371,7 @@ public override void CopyGeometry(IGeoObject toCopyFrom) if (!(toCopyFrom is Picture from)) return; this.location = from.location; - this.bitmap = from.bitmap; + SetImage(from.imageData, from.gdiBitmap); this.directionWidth = from.directionWidth; this.directionHeight = from.directionHeight; } @@ -288,12 +390,18 @@ public override BoundingCube GetBoundingCube() /// public override void PaintTo3D(IPaintTo3D paintTo3D) { - paintTo3D.RectangularBitmap(bitmap, location, directionWidth, directionHeight); + Bitmap bitmap = BitmapForPainting(); + if (bitmap != null) paintTo3D.RectangularBitmap(bitmap, location, directionWidth, directionHeight); + else + { // no GDI+ (or no image): the outline shows where the picture is + paintTo3D.Polyline(new GeoPoint[] { location, location + directionWidth, location + directionWidth + directionHeight, location + directionHeight, location }); + } } /// public override void PrePaintTo3D(IPaintTo3D paintTo3D) { - paintTo3D.PrepareBitmap(bitmap); + Bitmap bitmap = BitmapForPainting(); + if (bitmap != null) paintTo3D.PrepareBitmap(bitmap); } /// public override void PrepareDisplayList(double precision) @@ -406,34 +514,95 @@ public override void FindSnapPoint(SnapPointFinder spf) protected Picture(SerializationInfo info, StreamingContext context) : base(info, context) { - bitmap = info.GetValue("Bitmap", typeof(Bitmap)) as Bitmap; location = (GeoPoint)info.GetValue("Location", typeof(GeoPoint)); directionWidth = (GeoVector)info.GetValue("DirectionWidth", typeof(GeoVector)); directionHeight = (GeoVector)info.GetValue("DirectionHeight", typeof(GeoVector)); Path = info.GetString("Path"); + imageData = ReadImage(info, Path, "Picture"); + } + /// + /// The image of a picture or icon from a project file: "ImageData" (the image file as Base64) since #412 step 7, or + /// "Bitmap" (a serialized GDI+ bitmap) in files of CADability 1.x up to then. When the image cannot be read, the file + /// at is loaded instead, if there is one. + /// + internal static byte[] ReadImage(SerializationInfo info, string path, string objectName) + { + byte[] data = null; + bool found = false; + foreach (SerializationEntry entry in info) + { + if (entry.Name == "ImageData") + { + found = entry.Value != null; // null: saved without an image + if (entry.Value is string base64) data = Convert.FromBase64String(base64); + else data = entry.Value as byte[]; + } + else if (entry.Name == "Bitmap") + { + found = true; + try + { + data = EncodedImage.FromSerialized(entry.Value); + } + catch (Exception e) when (!(e is ThreadAbortException) && !(e is OutOfMemoryException)) + { + data = null; + } + } + } + if (data != null && EncodedImage.TryGetSize(data, out _, out _)) return data; + if (!found) return null; // an object without an image + if (!string.IsNullOrEmpty(path)) + { + try + { + if (File.Exists(path)) + { + System.Diagnostics.Trace.TraceWarning($"{objectName}: the image in the project file cannot be read, it is loaded from \"{path}\"."); + return ReadImageFile(path); + } + } + catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is ArgumentException || e is NotSupportedException) + { + } + } + if (data != null) System.Diagnostics.Trace.TraceWarning($"{objectName}: the image in the project file is not a PNG, JPEG, GIF or BMP file and cannot be shown."); + else System.Diagnostics.Trace.TraceWarning($"{objectName}: the image in the project file cannot be read and there is no file \"{path}\"."); + return data; } /// public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); - info.AddValue("Bitmap", bitmap); + // Base64 rather than byte[]: JsonSerialize writes every element of an array as an object of its own + info.AddValue("ImageData", imageData == null ? null : Convert.ToBase64String(imageData)); info.AddValue("Location", location); info.AddValue("DirectionWidth", directionWidth); info.AddValue("DirectionHeight", directionHeight); info.AddValue("Path", Path); } #endregion - internal static Bitmap CopyFrom(string filePath) + /// + /// The bytes of an image file for . A PNG, JPEG, GIF or BMP file is taken as it is. Any other + /// image format GDI+ knows (e.g. TIFF) is converted to PNG where GDI+ is available. The file is closed when this + /// method returns. + /// + /// The file is not an image that can be used + internal static byte[] ReadImageFile(string filePath) { - //open file from the disk (file path is the path to the file to be opened) - using (FileStream fileStream = File.OpenRead(filePath)) + byte[] data = File.ReadAllBytes(filePath); + if (EncodedImage.TryGetSize(data, out _, out _)) return data; + try + { + using (MemoryStream stream = new MemoryStream(data)) + using (Bitmap bitmap = new Bitmap(stream)) + { + return EncodedImage.FromGdiImage(bitmap); + } + } + catch (Exception e) when (e is PlatformNotSupportedException || e is TypeInitializationException || e is DllNotFoundException || e is ArgumentException || e is ExternalException) { - //create new MemoryStream object - MemoryStream memStream = new MemoryStream(); - memStream.SetLength(fileStream.Length); - //read file to MemoryStream - fileStream.Read(memStream.GetBuffer(), 0, (int)fileStream.Length); - return new Bitmap(memStream); + throw new ArgumentException("\"" + filePath + "\" is not a PNG, JPEG, GIF or BMP file.", nameof(filePath), e); } } @@ -502,7 +671,7 @@ public ShowPropertyPicture(Picture picture, IFrame frame) : base(frame) path.SetContextMenu("MenuId.Picture.Path", this); keepAspectRatio = new BooleanProperty("Picture.KeepAspectRatio", "YesNo.Values"); - double p = picture.DirectionWidth.Length / picture.Bitmap.Width * picture.Bitmap.Height / picture.DirectionHeight.Length; + double p = picture.DirectionWidth.Length / Math.Max(1, picture.PixelWidth) * Math.Max(1, picture.PixelHeight) / picture.DirectionHeight.Length; keepAspectRatio.BooleanValue = Math.Abs(1.0 - p) < 1e-6; keepAspectRatio.BooleanChangedEvent += OnKeepAspectRatioChanged; rectangular = new BooleanProperty("Picture.Rectangular", "YesNo.Values"); @@ -514,11 +683,20 @@ public ShowPropertyPicture(Picture picture, IFrame frame) : base(frame) dirHeightHotSpot.Position = picture.Location + picture.DirectionHeight; } + /// + /// Width / height of the picture's image in pixels (), or height / width; 1 without + /// an image. + /// + internal static double PixelRatio(Picture picture, bool widthByHeight) + { + if (picture.PixelWidth <= 0 || picture.PixelHeight <= 0) return 1.0; + return widthByHeight ? picture.PixelWidth / (double)picture.PixelHeight : picture.PixelHeight / (double)picture.PixelWidth; + } private void OnKeepAspectRatioChanged(object sender, bool newValue) { if (keepAspectRatio.BooleanValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * PixelRatio(picture, false) * picture.DirectionHeight.Normalized; } } @@ -649,7 +827,7 @@ private void OnSetDirHeight(GeoVector v) } if (keepAspectRatio.BooleanValue) { - picture.DirectionWidth = picture.DirectionHeight.Length / picture.Bitmap.Height * picture.Bitmap.Width * picture.DirectionWidth.Normalized; + picture.DirectionWidth = picture.DirectionHeight.Length * PixelRatio(picture, true) * picture.DirectionWidth.Normalized; } } } @@ -672,7 +850,7 @@ private void OnSetDirWidth(GeoVector v) } if (keepAspectRatio.BooleanValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * PixelRatio(picture, false) * picture.DirectionHeight.Normalized; } } } @@ -724,8 +902,7 @@ bool ICommandHandler.OnCommand(string menuId) case "MenuId.Picture.Path.Reload": try { - Bitmap pix = new Bitmap(picture.Path); - picture.Bitmap = pix; + picture.ImageData = Picture.ReadImageFile(picture.Path); } catch (Exception e) { @@ -741,8 +918,7 @@ bool ICommandHandler.OnCommand(string menuId) { try { - Bitmap pix = new Bitmap(fileName); - picture.Bitmap = pix; + picture.ImageData = Picture.ReadImageFile(fileName); picture.Path = fileName; } catch (Exception e) diff --git a/CADability/CffOutlines.cs b/CADability/CffOutlines.cs new file mode 100644 index 000000000..bbf60c473 --- /dev/null +++ b/CADability/CffOutlines.cs @@ -0,0 +1,617 @@ +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, and of the "CFF2" table of variable OpenType fonts. Name-keyed and CID-keyed fonts (FDArray, FDSelect) + /// are supported, with local and global subroutines, hint masks and the flex operators; CFF2 adds the blend and vsindex + /// operators, which vary the outline with the item variation store of the table. Hints are skipped, they do not change the + /// outline. Not supported: 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 + private readonly bool cff2; + private readonly ItemVariationStore variationStore; // CFF2 only + private readonly List defaultVsIndex = new List(); // CFF2: the vsindex of the private dict per font dict + + /// + /// 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)); + } + + /// + /// Reads a "CFF2" table, the outlines of a variable OpenType font. + /// + /// the font file + /// the start of the "CFF2" table + /// its length + /// from the "head" table, to check the FontMatrix + /// the variations of the font, for the item variation store; null when the font has no "fvar" + public CffOutlines(byte[] data, int offset, int length, int unitsPerEm, FontVariations variations) + { + this.data = data; + cff2 = true; + int end = offset + length; + if (length < 5 || data[offset] != 2) throw new InvalidDataException("not a CFF2 table"); + int headerSize = data[offset + 2], topDictLength = U16(offset + 3); + Dictionary top = ReadDict(offset + headerSize, topDictLength); + int pos = offset + headerSize + topDictLength; + globalSubrs = ReadIndex(ref pos, end); + if (!top.TryGetValue(17, out double[] charStringsOffset)) throw new InvalidDataException("CFF2 font without charstrings"); + 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) + { + double s = fontMatrix[0] * unitsPerEm; + if (Math.Abs(s - 1.0) > 1e-3) scale = s; + } + if (!top.TryGetValue(1236, out double[] fdArrayOffset)) throw new InvalidDataException("CFF2 font without FDArray"); + int fdPos = offset + (int)fdArrayOffset[0]; + foreach (var fd in ReadIndex(ref fdPos, end)) + { + Dictionary fontDict = ReadDict(fd.start, fd.length); + localSubrs.Add(ReadLocalSubrs(offset, fontDict, end, out int vsIndex)); + defaultVsIndex.Add(vsIndex); + } + if (localSubrs.Count == 0) throw new InvalidDataException("CFF2 font without font dicts"); + if (top.TryGetValue(1237, out double[] fdSelectOffset)) fdSelect = ReadFdSelect(offset + (int)fdSelectOffset[0], charStrings.Count, end); + if (top.TryGetValue(24, out double[] vstore) && variations != null) + { // the variation store starts with its length (uint16) + variationStore = variations.ReadItemVariationStore(offset + (int)vstore[0] + 2); + } + } + + /// + /// Sends the outline of to . + /// + public void Draw(int glyph, IGlyphOutlineSink sink, double[] coords = null) + { + 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"); + } + Interpreter interpreter = new Interpreter(this, localSubrs[fd], scale == 1.0 ? sink : new ScalingSink(sink, scale)); + if (cff2) interpreter.SetVariation(fd < defaultVsIndex.Count ? defaultVsIndex[fd] : 0, coords); + interpreter.Run(charStrings[glyph]); + } + + private List<(int start, int length)> ReadLocalSubrs(int cffOffset, Dictionary dict, int end) + { + return ReadLocalSubrs(cffOffset, dict, end, out _); + } + private List<(int start, int length)> ReadLocalSubrs(int cffOffset, Dictionary dict, int end, out int vsIndex) + { + vsIndex = 0; + 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(22, out double[] vs) && vs.Length > 0) vsIndex = (int)vs[0]; // CFF2: vsindex + 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 if (format == 4) + { // CFF2: 32 bit glyph ids + int nRanges = (int)U32(pos + 1); + for (int i = 0; i < nRanges; ++i) + { + int r = pos + 5 + 6 * i; + int first = (int)U32(r), fd = U16(r + 4), next = (int)U32(r + 6); + 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)>(); + int countSize = cff2 ? 4 : 2; // CFF2 counts with 32 bits + if (pos + countSize > end) throw new InvalidDataException("CFF INDEX exceeds the table"); + int count = cff2 ? (int)U32(pos) : U16(pos); + if (count == 0) + { + pos += countSize; + return res; + } + if (count < 0) throw new InvalidDataException("invalid CFF INDEX"); + int offSize = data[pos + countSize]; + if (offSize < 1 || offSize > 4) throw new InvalidDataException("invalid CFF INDEX"); + int offsets = pos + countSize + 1; + 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 <= 27) + { // operators; 22 (vsindex), 23 (blend) and 24 (vstore) exist in CFF2 only. The values of a blended operand + // are not needed for the outlines: they are replaced by those of the default instance (dropped) + 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 uint U32(int pos) => (uint)((data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]); + 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); + private int vsIndex; // CFF2 + private double[] coords; // CFF2, normalized, null for the default instance + private double[] scalars; // CFF2: the region factors of the item variation data vsIndex + + public Interpreter(CffOutlines font, List<(int start, int length)> local, IGlyphOutlineSink sink) + { + this.font = font; + this.local = local; + this.sink = sink; + } + /// + /// CFF2: the initial vsindex (from the private dict) and the variation coordinates. A CFF2 charstring has no width. + /// + public void SetVariation(int vsIndex, double[] coords) + { + this.vsIndex = vsIndex; + this.coords = coords; + haveWidth = true; + } + private double[] Scalars() + { + if (scalars != null) return scalars; + ItemVariationStore store = font.variationStore; + if (store == null || vsIndex < 0 || vsIndex >= store.DataCount) return scalars = new double[0]; + scalars = store.GetScalars(vsIndex, coords ?? new double[0]); + return scalars; + } + private void Blend() + { + int n = (int)Pop(); + int k = Scalars().Length; + int total = n * (k + 1); + if (n < 0 || total > stack.Count) throw new InvalidDataException("invalid CFF2 blend"); + int first = stack.Count - total; + for (int i = 0; i < n; ++i) + { + double v = stack[first + i]; + for (int j = 0; j < k; ++j) v += stack[first + n + i * k + j] * scalars[j]; + stack[first + i] = v; + } + stack.RemoveRange(first + n, n * k); + } + 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; + case 15: // vsindex (CFF2) + vsIndex = (int)Pop(); + scalars = null; + stack.Clear(); + break; + case 16: // blend (CFF2) + Blend(); + 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/ConstrPicture.cs b/CADability/ConstrPicture.cs index 93f43cdb5..a22ded96b 100644 --- a/CADability/ConstrPicture.cs +++ b/CADability/ConstrPicture.cs @@ -231,15 +231,14 @@ GeoPoint OnGetPosition() void OnSetFileName(string val) { fileName = val; // save the name - System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(fileName); // load the bitmap - picture.Bitmap = bmp; // set the bitmap to the picture object + picture.ImageData = Picture.ReadImageFile(fileName); // load the image picture.Path = fileName; // set the name to the picture object base.ActiveObject = picture; // set the active object: // now that we have a bitmap we set this object as the active object, which means // that is will be displayed while the location is positioned and at the end of the // action it will automatically be inserted into the model - widthValue = picture.Bitmap.PhysicalDimension.Width; - heightValue = picture.Bitmap.PhysicalDimension.Height; + widthValue = picture.PixelWidth; + heightValue = picture.PixelHeight; } /// @@ -259,7 +258,7 @@ private bool OnSetWidth(double l) picture.DirectionWidth = widthValue * picture.DirectionWidth.Normalized; if (keepAspectRatioValue) { - picture.DirectionHeight = l * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = l * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; heightValue = picture.DirectionHeight.Length; } return true; @@ -278,7 +277,7 @@ private bool OnSetHeight(double l) picture.DirectionHeight = heightValue * picture.DirectionHeight.Normalized; if (keepAspectRatioValue) { - picture.DirectionWidth = l * picture.Bitmap.Width / picture.Bitmap.Height * picture.DirectionWidth.Normalized; + picture.DirectionWidth = l * ShowPropertyPicture.PixelRatio(picture, true) * picture.DirectionWidth.Normalized; widthValue = picture.DirectionWidth.Length; } return true; @@ -302,7 +301,7 @@ private bool OnSetDirWidth(GeoVector v) } if (keepAspectRatioValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; } return true; } @@ -323,7 +322,7 @@ private bool OnSetDirHeight(GeoVector v) } if (keepAspectRatioValue) { - picture.DirectionWidth = picture.DirectionHeight.Length / picture.Bitmap.Height * picture.Bitmap.Width * picture.DirectionWidth.Normalized; + picture.DirectionWidth = picture.DirectionHeight.Length * ShowPropertyPicture.PixelRatio(picture, true) * picture.DirectionWidth.Normalized; } return true; } @@ -338,7 +337,7 @@ void OnKeepAspectRatioChanged(bool NewValue) keepAspectRatioValue = NewValue; if (NewValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; } } diff --git a/CADability/ConstrPicturePoint2Directions.cs b/CADability/ConstrPicturePoint2Directions.cs index 2222d0c5f..b9f7abf5b 100644 --- a/CADability/ConstrPicturePoint2Directions.cs +++ b/CADability/ConstrPicturePoint2Directions.cs @@ -131,15 +131,14 @@ public override void OnSetAction() void OnSetFileName(string val) { fileName = val; // save the name - Bitmap bmp = Picture.CopyFrom(fileName); // load the bitmap - picture.Bitmap = bmp; // set the bitmap to the picture object + picture.ImageData = Picture.ReadImageFile(fileName); // load the image picture.Path = fileName; // set the name to the picture object base.ActiveObject = picture; // set the active object: // now that we have a bitmap we set this object as the active object, which means // that is will be displayed while the location is positioned and at the end of the // action it will automatically be inserted into the model - widthValue = picture.Bitmap.PhysicalDimension.Width; - heightValue = picture.Bitmap.PhysicalDimension.Height; + widthValue = picture.PixelWidth; + heightValue = picture.PixelHeight; } /// @@ -231,7 +230,7 @@ private bool OnSetDirWidth(GeoVector v) } if (keepAspectRatioValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; } return true; } @@ -261,7 +260,7 @@ private bool OnSetDirHeight(GeoVector v) } if (keepAspectRatioValue) { - picture.DirectionWidth = picture.DirectionHeight.Length / picture.Bitmap.Height * picture.Bitmap.Width * picture.DirectionWidth.Normalized; + picture.DirectionWidth = picture.DirectionHeight.Length * ShowPropertyPicture.PixelRatio(picture, true) * picture.DirectionWidth.Normalized; } return true; } @@ -286,7 +285,7 @@ void OnKeepAspectRatioChanged(bool NewValue) keepAspectRatioValue = NewValue; if (NewValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; dirHeight.Optional = true; } else dirHeight.Optional = false; @@ -374,7 +373,7 @@ public override void OnActivate(Action OldActiveAction, bool SettingAction) // picture.DirectionWidth = widthValue * picture.DirectionWidth.Normalized; // if (keepAspectRatioValue) // { - // picture.DirectionHeight = l * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + // picture.DirectionHeight = l * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; // heightValue = picture.DirectionHeight.Length; // } // return true; @@ -390,7 +389,7 @@ public override void OnActivate(Action OldActiveAction, bool SettingAction) // picture.DirectionHeight = heightValue * picture.DirectionHeight.Normalized; // if (keepAspectRatioValue) // { - // picture.DirectionWidth = l * picture.Bitmap.Width / picture.Bitmap.Height * picture.DirectionWidth.Normalized; + // picture.DirectionWidth = l * ShowPropertyPicture.PixelRatio(picture, true) * picture.DirectionWidth.Normalized; // widthValue = picture.DirectionWidth.Length; // } // return true; diff --git a/CADability/ConstrPicturePointWidthHeight.cs b/CADability/ConstrPicturePointWidthHeight.cs index 2273f0278..a23b3a5e3 100644 --- a/CADability/ConstrPicturePointWidthHeight.cs +++ b/CADability/ConstrPicturePointWidthHeight.cs @@ -125,15 +125,14 @@ GeoVector GetAngle() void OnSetFileName(string val) { fileName = val; // save the name - Bitmap bmp = Picture.CopyFrom(fileName); // load the bitmap - picture.Bitmap = bmp; // set the bitmap to the picture object + picture.ImageData = Picture.ReadImageFile(fileName); // load the image picture.Path = fileName; // set the name to the picture object base.ActiveObject = picture; // set the active object: // now that we have a bitmap we set this object as the active object, which means // that is will be displayed while the location is positioned and at the end of the // action it will automatically be inserted into the model - widthValue = picture.Bitmap.PhysicalDimension.Width; - heightValue = picture.Bitmap.PhysicalDimension.Height; + widthValue = picture.PixelWidth; + heightValue = picture.PixelHeight; picture.DirectionWidth = ActiveDrawingPlane.DirectionX; picture.DirectionHeight = ActiveDrawingPlane.DirectionY; } @@ -234,7 +233,7 @@ private bool OnSetWidth(double l) picture.DirectionWidth = widthValue * picture.DirectionWidth.Normalized; if (keepAspectRatioValue) { - picture.DirectionHeight = l * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = l * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; heightValue = picture.DirectionHeight.Length; } return true; @@ -255,7 +254,7 @@ private bool OnSetHeight(double l) picture.DirectionHeight = heightValue * picture.DirectionHeight.Normalized; if (keepAspectRatioValue) { - picture.DirectionWidth = l * picture.Bitmap.Width / picture.Bitmap.Height * picture.DirectionWidth.Normalized; + picture.DirectionWidth = l * ShowPropertyPicture.PixelRatio(picture, true) * picture.DirectionWidth.Normalized; widthValue = picture.DirectionWidth.Length; } return true; @@ -294,7 +293,7 @@ void OnKeepAspectRatioChanged(bool NewValue) keepAspectRatioValue = NewValue; if (NewValue) { - picture.DirectionHeight = picture.DirectionWidth.Length * picture.Bitmap.Height / picture.Bitmap.Width * picture.DirectionHeight.Normalized; + picture.DirectionHeight = picture.DirectionWidth.Length * ShowPropertyPicture.PixelRatio(picture, false) * picture.DirectionHeight.Normalized; height.Optional = true; } else height.Optional = false; diff --git a/CADability/DimensionStyle.cs b/CADability/DimensionStyle.cs index a0d7e4214..41fd15c17 100644 --- a/CADability/DimensionStyle.cs +++ b/CADability/DimensionStyle.cs @@ -1,6 +1,7 @@ using CADability.GeoObject; using CADability.UserInterface; using System; +using System.Linq; using System.Runtime.Serialization; namespace CADability.Attribute @@ -2056,9 +2057,7 @@ public override IPropertyEntry[] SubItems options.AddSubEntry(new CheckProperty(this, "DimRadiusBend", "DimensionStyle.DimRadiusBend")); ShowPropertyGroup font = new ShowPropertyGroup("DimensionStyle.Font"); - System.Drawing.FontFamily[] families = System.Drawing.FontFamily.Families; - string[] choices = new string[families.Length]; - for (int i = 0; i < families.Length; i++) choices[i] = families[i].Name; + string[] choices = FontProvider.Current.GetFamilyNames().OrderBy(n => n, StringComparer.CurrentCultureIgnoreCase).ToArray(); MultipleChoiceProperty multipropfont = new MultipleChoiceProperty("DimensionStyle.TextFont", choices, TextFont); multipropfont.ValueChangedEvent += new ValueChangedDelegate(FontValueChanged); font.AddSubEntry(multipropfont); diff --git a/CADability/EncodedImage.cs b/CADability/EncodedImage.cs new file mode 100644 index 000000000..8753fc4a1 --- /dev/null +++ b/CADability/EncodedImage.cs @@ -0,0 +1,195 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace CADability +{ + /// + /// Helpers for images kept as the bytes of an image file (PNG, JPEG, GIF or BMP), as and + /// keep them. The kernel never decodes the pixels: it only reads the size from the header. + /// Decoding is the job of the front end, which on Windows uses GDI+ (issue #412). + /// + internal static class EncodedImage + { + /// + /// The format of the image file in : "png", "jpeg", "gif", "bmp", or null for anything else. + /// + public static string FormatOf(byte[] data) + { + if (data == null) return null; + if (data.Length >= 8 && data[0] == 0x89 && data[1] == 'P' && data[2] == 'N' && data[3] == 'G' && data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A) return "png"; + if (data.Length >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF) return "jpeg"; + if (data.Length >= 6 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F' && data[3] == '8' && (data[4] == '7' || data[4] == '9') && data[5] == 'a') return "gif"; + if (data.Length >= 2 && data[0] == 'B' && data[1] == 'M') return "bmp"; + return null; + } + + /// + /// Reads the size in pixels from the header of a PNG, JPEG, GIF or BMP file. False for any other data, also for a + /// damaged or truncated header. + /// + public static bool TryGetSize(byte[] data, out int width, out int height) + { + width = height = 0; + try + { + switch (FormatOf(data)) + { + case "png": // the IHDR chunk follows the signature + if (data.Length < 24 || data[12] != 'I' || data[13] != 'H' || data[14] != 'D' || data[15] != 'R') return false; + width = BigEndian32(data, 16); + height = BigEndian32(data, 20); + break; + case "gif": // the logical screen descriptor + if (data.Length < 10) return false; + width = data[6] | data[7] << 8; + height = data[8] | data[9] << 8; + break; + case "bmp": + if (data.Length < 26) return false; + int headerSize = LittleEndian32(data, 14); + if (headerSize == 12) + { // BITMAPCOREHEADER of OS/2 1.x + width = data[18] | data[19] << 8; + height = data[20] | data[21] << 8; + } + else + { + width = LittleEndian32(data, 18); + height = Math.Abs(LittleEndian32(data, 22)); // negative for a top-down bitmap + } + break; + case "jpeg": + if (!JpegSize(data, out width, out height)) return false; + break; + default: + return false; + } + } + catch (IndexOutOfRangeException) + { + return false; + } + return width > 0 && height > 0; + } + + private static bool JpegSize(byte[] data, out int width, out int height) + { + width = height = 0; + int pos = 2; + while (pos + 4 <= data.Length) + { + if (data[pos] != 0xFF) return false; + int marker = data[pos + 1]; + if (marker == 0xFF) { ++pos; continue; } // fill byte + pos += 2; + if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) continue; // markers without length + if (marker == 0xD9 || marker == 0xDA) return false; // end of image or start of scan before a frame header + int length = data[pos] << 8 | data[pos + 1]; + // SOF0 to SOF15 are the frame headers, except DHT (C4), JPG (C8) and DAC (CC) + if (marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC) + { + height = data[pos + 3] << 8 | data[pos + 4]; + width = data[pos + 5] << 8 | data[pos + 6]; + return true; + } + pos += length; + } + return false; + } + private static int BigEndian32(byte[] data, int pos) => data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]; + private static int LittleEndian32(byte[] data, int pos) => data[pos] | data[pos + 1] << 8 | data[pos + 2] << 16 | data[pos + 3] << 24; + + /// + /// Checks that is a PNG, JPEG, GIF or BMP file whose size can be read, and throws an + /// if not. Null is allowed and means "no image". + /// + public static void Validate(byte[] data, string parameterName) + { + if (data != null && !TryGetSize(data, out _, out _)) + throw new ArgumentException("The data is not a PNG, JPEG, GIF or BMP file.", parameterName); + } + + /// + /// The bytes of an image file read from . The file is closed when this method returns. + /// + public static byte[] ReadFile(string path) + { + byte[] data = File.ReadAllBytes(path); + Validate(data, nameof(path)); + return data; + } + + /// + /// The image file bytes of an image read from an old project file: the "Data" that a serialized + /// System.Drawing.Bitmap contains (see ), or a System.Drawing.Image that + /// the formatter created itself (on Windows, for files read without the binder). Null if there is no image. + /// + public static byte[] FromSerialized(object value) + { + if (value is LegacyImageData legacy) return legacy.Data; + if (value is System.Drawing.Image image) return FromGdiImage(image); + return null; + } + + /// + /// Encodes a GDI+ image (Windows only): PNG, JPEG, GIF and BMP images keep their format, everything else (e.g. a + /// bitmap created in memory, TIFF or a metafile) becomes PNG. + /// + public static byte[] FromGdiImage(System.Drawing.Image image) + { + if (image == null) return null; + System.Drawing.Imaging.ImageFormat format = image.RawFormat; + if (!format.Equals(System.Drawing.Imaging.ImageFormat.Png) && !format.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) + && !format.Equals(System.Drawing.Imaging.ImageFormat.Gif) && !format.Equals(System.Drawing.Imaging.ImageFormat.Bmp)) + format = System.Drawing.Imaging.ImageFormat.Png; + using (MemoryStream stream = new MemoryStream()) + { + image.Save(stream, format); + return stream.ToArray(); + } + } + + /// + /// Decodes the image file bytes with GDI+ (Windows only). The bitmap does not depend on a stream, so it may be kept. + /// + public static System.Drawing.Bitmap ToGdiBitmap(byte[] data) + { + if (data == null) return null; + using (MemoryStream stream = new MemoryStream(data)) + using (System.Drawing.Bitmap decoded = new System.Drawing.Bitmap(stream)) + { + // GDI+ needs the stream as long as a bitmap made from a stream lives; the copy does not + return new System.Drawing.Bitmap(decoded); + } + } + } + + /// + /// Stands for a System.Drawing.Bitmap in a project file written by CADability 1.x: a serialized GDI+ image + /// contains nothing but its image file bytes as "Data". Reading these into this class instead of a GDI+ bitmap makes + /// old files with pictures and icons open on every platform (issue #412). + /// + [Serializable] + internal sealed class LegacyImageData : ISerializable + { + public byte[] Data { get; } + + private LegacyImageData(SerializationInfo info, StreamingContext context) + { + Data = info.GetValue("Data", typeof(byte[])) as byte[]; + } + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Data", Data, typeof(byte[])); + } + + /// + /// True for the types of GDI+ images that CADability 1.x wrote into project files. + /// + public static bool IsGdiImageType(string typeName) + { + return typeName == "System.Drawing.Bitmap" || typeName == "System.Drawing.Image"; + } + } +} diff --git a/CADability/FontProvider.cs b/CADability/FontProvider.cs new file mode 100644 index 000000000..59ce798db --- /dev/null +++ b/CADability/FontProvider.cs @@ -0,0 +1,439 @@ +using CADability.GeoObject; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using Path = System.IO.Path; + +namespace CADability +{ + /// + /// Provides the fonts for and dimensions: the names of the font families, their metrics and the outlines + /// of the glyphs. The kernel gets all fonts from , which works without any user interface. The default + /// is a , which reads the TrueType font files of the system itself and therefore works on + /// every platform. CADability.Forms contains a provider based on GDI (GdiFontProvider), which gives the fonts exactly as + /// CADability showed them on Windows before; as soon as CADability.Forms is used, it makes that provider the default, + /// unless the application has set itself. + /// + public abstract class FontProvider + { + private static FontProvider current; + private static bool chosenByApplication; + private static readonly object sync = new object(); + /// + /// The font provider the kernel uses. Setting it clears the cached glyphs, so that texts are rebuilt with the new + /// provider, like : set it before texts are painted, or rebuild the display lists of + /// the views afterwards (). A provider set here is kept: + /// CADability.Forms only replaces the default with its GDI provider while the application has not set one. + /// + public static FontProvider Current + { + get + { + FontProvider res = Volatile.Read(ref current); + if (res != null) return res; + lock (sync) + { + if (current == null) current = new ManagedFontProvider(); + return current; + } + } + set + { + if (value == null) throw new ArgumentNullException(nameof(value)); + lock (sync) + { + chosenByApplication = true; + current = value; + } + ClearCaches(); + } + } + /// + /// Makes the provider made by the default, unless the application has set + /// itself. CADability.Forms calls this to use GDI for the fonts, as CADability did before issue #412. + /// + internal static void SetDefault(Func create) + { + lock (sync) + { + if (chosenByApplication) return; + current = create(); + } + ClearCaches(); + } + private static void ClearCaches() + { // outside the lock: the caches have locks of their own, and a thread holding one of them may ask for Current + FontCache.GlobalFontCache.Clear(); + Text.ResetFontFamilyNames(); + } + /// + /// The names of all font families this provider knows, e.g. for a list to choose from. + /// + public abstract string[] GetFamilyNames(); + /// + /// Returns the face of the font family (not case sensitive) that matches the requested + /// style best, or null if the provider does not know this family. + /// + /// The name of the font family, e.g. "Arial" + /// Bold requested + /// Italic requested + public abstract FontFace GetFace(string familyName, bool bold, bool italic); + /// + /// Returns the face to use for a family that does not know. The default implementation tries some + /// common sans serif fonts and then takes the first family. Returns null when there is no font at all. + /// + public virtual FontFace GetDefaultFace(bool bold, bool italic) + { + foreach (string name in new[] { "Microsoft Sans Serif", "Arial", "Liberation Sans", "DejaVu Sans", "Helvetica", "Noto Sans", "FreeSans" }) + { + FontFace face = GetFace(name, bold, italic); + if (face != null) return face; + } + string first = GetFamilyNames().OrderBy(n => n, StringComparer.OrdinalIgnoreCase).FirstOrDefault(); + return first == null ? null : GetFace(first, bold, italic); + } + } + + /// + /// A face of a font family (e.g. "Arial Bold") as a provides it. Metrics are in font units, + /// widths and kerning in units of the em height, and the glyph outlines are Bezier curves, so that all providers produce + /// the same exact geometry. + /// + public abstract class FontFace + { + /// + /// The name of the font family. + /// + public abstract string FamilyName { get; } + /// + /// True if this face is bold. + /// + public abstract bool IsBold { get; } + /// + /// True if this face is italic. + /// + public abstract bool IsItalic { get; } + /// + /// The height of the em square in font units (e.g. 2048 for most TrueType fonts). + /// + public abstract int EmHeight { get; } + /// + /// The ascent of the character cell in font units. + /// + public abstract int CellAscent { get; } + /// + /// The descent of the character cell in font units, positive downwards. + /// + public abstract int CellDescent { get; } + /// + /// The distance between the baselines of two lines in font units. + /// + public abstract int LineSpacing { get; } + /// + /// The advance width of the character in units of the em height. Characters are Unicode + /// code points, so that characters outside the Basic Multilingual Plane (e.g. most emoji), which take two UTF-16 + /// chars (a surrogate pair) in a string, are one character here. + /// + public abstract double GetAdvanceWidth(int codePoint); + /// + /// The kerning between two characters (Unicode code points) in units of the em height, usually negative; 0 if there + /// is none. + /// + public abstract double GetKerning(int left, int right); + /// + /// Returns the outline of the character (Unicode code point) in the layout of a GDI+ GraphicsPath after + /// AddString with an em size of 1 and StringFormat.GenericTypographic: in units of the em height, y + /// downwards, the top of the character cell at 0 and the baseline at /. + /// The point types are those of GDI+ (0 start, 1 line, 3 cubic Bezier, 0x80 closes the figure). Curves must be returned + /// as Bezier segments and not be flattened. Underline and strikeout are added as rectangles when requested. + /// + public abstract void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types); + } + + /// + /// 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. The named instances of a + /// variable font (TrueType with "gvar" or OpenType with "CFF2") are faces of their own, with family names as GDI makes + /// them, e.g. "Bahnschrift Light Condensed". Bitmap fonts are left out. A bold or italic face that a family does not have + /// is synthesized from the closest face, as GDI+ does it (see ). + /// + public class ManagedFontProvider : FontProvider + { + private sealed class Entry + { + public string FileName; // or + public byte[] Data; + public int Index; // in a collection + public string Family; + public bool Bold, Italic; + public double[] Coordinates; // the named instance of a variable font, null for a font that is not variable + public ManagedFontFace Face; // loaded on demand + public readonly FontFace[] Synthetic = new FontFace[4]; // with synthetic bold (1) and/or italic (2), made on demand + } + private readonly object locker = new object(); + private readonly List directories = new List(); + private readonly List added = new List(); + private Dictionary> families; // upper case family name -> faces, built on demand + + /// + /// Creates a provider for the fonts installed on the system. + /// + public ManagedFontProvider() : this(null, true) + { + } + /// + /// Creates a provider for the fonts in (with their subdirectories) and, if + /// is true, for the fonts installed on the system. + /// + public ManagedFontProvider(IEnumerable fontDirectories, bool includeSystemFonts) + { + if (fontDirectories != null) directories.AddRange(fontDirectories); + if (includeSystemFonts) directories.AddRange(SystemFontDirectories()); + } + /// + /// The directories where the fonts of the system are installed on the current platform. + /// + public static IEnumerable SystemFontDirectories() + { + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string fonts = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); // Windows + if (!string.IsNullOrEmpty(fonts)) yield return fonts; + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if (!string.IsNullOrEmpty(localAppData)) yield return Path.Combine(localAppData, "Microsoft", "Windows", "Fonts"); // fonts installed per user + yield return "/usr/share/fonts"; + yield return "/usr/local/share/fonts"; + yield return "/System/Library/Fonts"; + yield return "/Library/Fonts"; + if (!string.IsNullOrEmpty(home)) + { + yield return Path.Combine(home, ".fonts"); + yield return Path.Combine(home, ".local", "share", "fonts"); + yield return Path.Combine(home, "Library", "Fonts"); + } + } + /// + /// Adds the fonts of a font file (.ttf, .ttc, .otf or .otc). + /// + public void AddFontFile(string fileName) + { + TrueTypeFont[] fonts = TrueTypeFont.ReadHeaders(fileName); + lock (locker) + { + for (int i = 0; i < fonts.Length; ++i) AddEntry(new Entry { FileName = fileName, Index = i }, fonts[i], added); + families = null; + } + } + /// + /// Adds the fonts of a font file given as its content, e.g. an embedded resource. + /// + public void AddFontData(byte[] data) + { + TrueTypeFont[] fonts = TrueTypeFont.Load(data); + lock (locker) + { + for (int i = 0; i < fonts.Length; ++i) AddEntry(new Entry { Data = data, Index = i }, fonts[i], added); + families = null; + } + } + /// + public override string[] GetFamilyNames() + { + lock (locker) + { + return Families().Values.Select(l => l[0].Family).ToArray(); + } + } + /// + public override FontFace GetFace(string familyName, bool bold, bool italic) + { + if (string.IsNullOrEmpty(familyName)) return null; + Entry best; + lock (locker) + { + if (!Families().TryGetValue(familyName.ToUpperInvariant(), out List faces) && !TruncatedFamily(familyName, out faces)) return null; + // the exact style, then the same weight, then regular, then any face + best = faces.FirstOrDefault(f => f.Bold == bold && f.Italic == italic) + ?? faces.FirstOrDefault(f => f.Bold == bold) + ?? faces.FirstOrDefault(f => !f.Bold && !f.Italic) + ?? faces[0]; + if (best.Face == null) + { + TrueTypeFont[] fonts = best.Data != null ? TrueTypeFont.Load(best.Data) : TrueTypeFont.Load(best.FileName); + TrueTypeFont font = fonts[best.Index]; + if (best.Coordinates != null) font = font.WithVariation(best.Coordinates); + best.Face = new ManagedFontFace(font, best.Family, best.Bold, best.Italic); + } + // a style the family does not have is synthesized from the closest face, as GDI+ does it + int synthetic = (bold && !best.Bold ? 1 : 0) | (italic && !best.Italic ? 2 : 0); + if (synthetic == 0) return best.Face; + if (best.Synthetic[synthetic] == null) best.Synthetic[synthetic] = new SyntheticFontFace(best.Face, (synthetic & 1) != 0, (synthetic & 2) != 0); + return best.Synthetic[synthetic]; + } + } + + private static void AddEntry(Entry entry, TrueTypeFont font, List list) + { + if (!font.HasOutlines || string.IsNullOrEmpty(font.FamilyName)) return; // bitmap fonts cannot be used + NamedInstance[] instances = font.NamedInstances; + if (instances.Length == 0) + { + entry.Family = font.FamilyName; + entry.Bold = font.IsBold; + entry.Italic = font.IsItalic; + list.Add(entry); + return; + } + // a variable font: every named instance is a face, named like GDI names it + foreach (NamedInstance instance in instances) + { + InstanceFamily(font.TypographicFamilyName, font.InstanceName(instance), out string family, out bool bold, out bool italic); + italic |= font.IsItalic; // the instances of an italic font are italic, whether their names say so or not + list.Add(new Entry + { + FileName = entry.FileName, + Data = entry.Data, + Index = entry.Index, + Family = family, + Bold = bold, + Italic = italic, + Coordinates = instance.Coordinates, + }); + } + } + /// + /// The family and style of a named instance of a variable font, as GDI presents them: "Bold" and "Italic" (or "Oblique") + /// in the name of the instance become the style, "Regular" is left out, and the other words are added to the family + /// name. "Light Condensed" of "Bahnschrift" is the family "Bahnschrift Light Condensed", "Bold" is "Bahnschrift" bold. + /// + internal static void InstanceFamily(string baseFamily, string instanceName, out string family, out bool bold, out bool italic) + { + bold = italic = false; + List rest = new List(); + foreach (string word in (instanceName ?? "").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (word.Equals("Bold", StringComparison.OrdinalIgnoreCase)) bold = true; + else if (word.Equals("Italic", StringComparison.OrdinalIgnoreCase) || word.Equals("Oblique", StringComparison.OrdinalIgnoreCase)) italic = true; + else if (!word.Equals("Regular", StringComparison.OrdinalIgnoreCase)) rest.Add(word); + } + family = rest.Count == 0 ? baseFamily : baseFamily + " " + string.Join(" ", rest); + } + /// + /// GDI cuts family names to 31 characters (LF_FACESIZE), e.g. "Bahnschrift SemiBold SemiConden", and texts saved with + /// GDI keep that name. Such a name stands for the only family whose name starts with it. + /// + private bool TruncatedFamily(string familyName, out List faces) + { + faces = null; + if (familyName.Length != GdiFaceNameLength) return false; + string prefix = familyName.ToUpperInvariant(); + List candidates = Families().Keys.Where(name => name.StartsWith(prefix, StringComparison.Ordinal)).Take(2).ToList(); + if (candidates.Count != 1) return false; + faces = Families()[candidates[0]]; + return true; + } + private const int GdiFaceNameLength = 31; + private Dictionary> Families() + { + if (families != null) return families; + List all = new List(); + HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (string directory in directories) + { + if (!Directory.Exists(directory)) continue; + IEnumerable files; + try + { + files = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToArray(); + } + catch (Exception e) when (e is IOException || e is UnauthorizedAccessException) + { + continue; + } + foreach (string file in files) + { + string ext = Path.GetExtension(file).ToLowerInvariant(); + if (ext != ".ttf" && ext != ".ttc" && ext != ".otf" && ext != ".otc") continue; + if (!seen.Add(Path.GetFullPath(file))) continue; + try + { + TrueTypeFont[] fonts = TrueTypeFont.ReadHeaders(file); + for (int i = 0; i < fonts.Length; ++i) AddEntry(new Entry { FileName = file, Index = i }, fonts[i], all); + } + catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is InvalidDataException || e is IndexOutOfRangeException || e is ArgumentException) + { // a broken or unreadable font file is left out + } + } + } + all.AddRange(added); + families = new Dictionary>(); + foreach (Entry e in all) + { + string key = e.Family.ToUpperInvariant(); + if (!families.TryGetValue(key, out List list)) families[key] = list = new List(); + if (!list.Any(f => f.Bold == e.Bold && f.Italic == e.Italic)) list.Add(e); // the first file of a style wins + } + return families; + } + } + + /// + /// A face read by the . + /// + internal sealed class ManagedFontFace : FontFace + { + private readonly TrueTypeFont font; + private readonly object locker = new object(); + private readonly string family; + private readonly bool bold, italic; + public ManagedFontFace(TrueTypeFont font) : this(font, font.FamilyName, font.IsBold, font.IsItalic) + { + } + /// the font, for a variable font the instance + /// the family name, for the instance of a variable font the name of the instance's family + /// bold face + /// italic face + public ManagedFontFace(TrueTypeFont font, string family, bool bold, bool italic) + { + this.font = font; + this.family = family; + this.bold = bold; + this.italic = italic; + } + internal TrueTypeFont Font => font; + public override string FamilyName => family; + public override bool IsBold => bold; + public override bool IsItalic => italic; + public override int EmHeight => font.UnitsPerEm; + public override int CellAscent => font.CellAscent; + public override int CellDescent => font.CellDescent; + public override int LineSpacing => font.LineSpacing; + public override double GetAdvanceWidth(int codePoint) + { + return font.GetAdvanceWidth(font.GetGlyphIndex(codePoint)) / (double)font.UnitsPerEm; + } + public override double GetKerning(int left, int right) + { + lock (locker) + { // the kerning pairs are read on the first call + return font.GetKerning(font.GetGlyphIndex(left), font.GetGlyphIndex(right)) / (double)font.UnitsPerEm; + } + } + /// + /// Like , with the strokes made wider by + /// and higher by (in units of the em height) for a synthetic bold + /// face, see . + /// + internal void GetGlyphPath(int codePoint, bool underline, bool strikeout, double boldX, double boldY, out PointF[] points, out byte[] types) + { + font.GetPathData(font.GetGlyphIndex(codePoint), underline, strikeout, boldX * font.UnitsPerEm, boldY * font.UnitsPerEm, out points, out types); + } + public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types) + { + font.GetPathData(font.GetGlyphIndex(codePoint), underline, strikeout, out points, out types); + } + } +} diff --git a/CADability/FontVariations.cs b/CADability/FontVariations.cs new file mode 100644 index 000000000..3c257aaa7 --- /dev/null +++ b/CADability/FontVariations.cs @@ -0,0 +1,596 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace CADability +{ + /// + /// An axis of a variable font ("fvar"), e.g. the weight "wght" from 200 to 900 with the default 400. + /// + internal struct VariationAxis + { + public string Tag; + public double Min, Default, Max; + } + /// + /// A named instance of a variable font ("fvar"), e.g. "Light Condensed" with its coordinates in user units. + /// + internal struct NamedInstance + { + public int SubfamilyNameId; + public string SubfamilyName; + public double[] Coordinates; + } + + /// + /// The variation data of a variable OpenType font: the axes and named instances ("fvar"), the axis mapping ("avar"), the + /// glyph variations of TrueType outlines ("gvar", with the interpolation of untouched points), the advance width variations + /// ("HVAR"), the variations of the font wide metrics ("MVAR"), the item variation store that CFF2 outlines use for their + /// blend operator and the names of the instances from the style attributes ("STAT"). + /// + internal sealed class FontVariations + { + private readonly byte[] data; + public VariationAxis[] Axes { get; } + public NamedInstance[] Instances { get; } + private readonly List<(double from, double to)>[] avar; // per axis, null without avar + private readonly int gvarOffset = -1, gvarArray, gvarGlyphCount, sharedTuples, sharedTupleCount; + private readonly bool gvarLongOffsets; + private readonly ItemVariationStore hvarStore; + private readonly int hvarAdvanceMap = -1; + private readonly ItemVariationStore mvarStore; + private readonly Dictionary mvarRecords = new Dictionary(); + private readonly int statOffset = -1; + private readonly Func nameOf; + + /// the font file + /// offset and length of the tables of the font + /// the name for a name ID + public FontVariations(byte[] data, Dictionary tables, Func nameOf) + { + this.data = data; + this.nameOf = nameOf; + var fvar = tables["fvar"]; + int f = fvar.offset; + int axesOffset = f + U16(f + 4), axisCount = U16(f + 8), axisSize = U16(f + 10), instanceCount = U16(f + 12), instanceSize = U16(f + 14); + if (axisCount == 0 || axisSize < 20 || instanceSize < 4 + 4 * axisCount) throw new InvalidDataException("invalid fvar table"); + Axes = new VariationAxis[axisCount]; + for (int i = 0; i < axisCount; ++i) + { + int a = axesOffset + i * axisSize; + Axes[i] = new VariationAxis + { + Tag = System.Text.Encoding.ASCII.GetString(data, a, 4), + Min = Fixed(a + 4), + Default = Fixed(a + 8), + Max = Fixed(a + 12), + }; + } + List instances = new List(); + int instancesOffset = axesOffset + axisCount * axisSize; + for (int i = 0; i < instanceCount; ++i) + { + int p = instancesOffset + i * instanceSize; + if (p + instanceSize > fvar.offset + fvar.length) break; + double[] coords = new double[axisCount]; + for (int k = 0; k < axisCount; ++k) coords[k] = Fixed(p + 4 + 4 * k); + int nameId = U16(p); + instances.Add(new NamedInstance { SubfamilyNameId = nameId, SubfamilyName = nameOf(nameId) ?? "", Coordinates = coords }); + } + Instances = instances.ToArray(); + if (tables.TryGetValue("avar", out var av) && U16(av.offset) == 1) + { + int count = U16(av.offset + 6); + if (count == axisCount) + { + avar = new List<(double, double)>[axisCount]; + int p = av.offset + 8; + for (int i = 0; i < axisCount; ++i) + { + int maps = U16(p); + p += 2; + avar[i] = new List<(double, double)>(); + for (int k = 0; k < maps; ++k, p += 4) avar[i].Add((F2Dot14(p), F2Dot14(p + 2))); + } + } + } + if (tables.TryGetValue("gvar", out var gv)) + { + int g = gv.offset; + if (U16(g + 4) != axisCount) throw new InvalidDataException("gvar and fvar have a different number of axes"); + gvarOffset = g; + sharedTupleCount = U16(g + 6); + sharedTuples = g + (int)U32(g + 8); + gvarGlyphCount = U16(g + 12); + gvarLongOffsets = (U16(g + 14) & 1) != 0; + gvarArray = g + (int)U32(g + 16); + } + if (tables.TryGetValue("HVAR", out var hv)) + { + int h = hv.offset; + hvarStore = new ItemVariationStore(data, h + (int)U32(h + 4), axisCount); + uint map = U32(h + 8); + if (map != 0) hvarAdvanceMap = h + (int)map; + } + if (tables.TryGetValue("MVAR", out var mv) && mv.length >= 12) + { + int m = mv.offset; + int recordSize = U16(m + 6), recordCount = U16(m + 8), store = U16(m + 10); + if (store != 0 && recordSize >= 8) + { + mvarStore = new ItemVariationStore(data, m + store, axisCount); + for (int i = 0; i < recordCount; ++i) + { + int r = m + 12 + i * recordSize; + mvarRecords[System.Text.Encoding.ASCII.GetString(data, r, 4)] = (U16(r + 4), U16(r + 6)); + } + } + } + if (tables.TryGetValue("STAT", out var st) && st.length >= 18) statOffset = st.offset; + } + + /// + /// The change of a font wide metric from "MVAR" at the normalized coordinates, e.g. "strs" for the size of the strikeout + /// line, 0 if the font does not vary it. + /// + public double GetMetricDelta(string tag, double[] coords) + { + if (mvarStore == null || coords == null || !mvarRecords.TryGetValue(tag, out var record)) return 0.0; + return mvarStore.GetDelta(record.outer, record.inner, coords); + } + + /// + /// The name of an instance (coordinates in user units) as the style attributes of "STAT" make it, e.g. "Display Light" + /// for wght 300 and opsz 36 of Segoe UI Variable: the names of the axis values of the instance in the axis ordering of + /// "STAT", without the elidable ones like "Regular". This is how Windows names the instances, while the subfamily name + /// in "fvar" may have another order ("Light Display"). Null if the font has no "STAT" table or an axis has no value + /// for the instance. + /// + public string StatInstanceName(double[] userCoordinates) + { + if (statOffset < 0) return null; + try + { + int t = statOffset; + int designAxisSize = U16(t + 4), designAxisCount = U16(t + 6), designAxes = t + (int)U32(t + 8); + int valueCount = U16(t + 12), valueOffsets = t + (int)U32(t + 14); + // the coordinate of the instance on every design axis (NaN for an axis "fvar" does not have) + double[] coordinate = new double[designAxisCount]; + int[] ordering = new int[designAxisCount]; + for (int i = 0; i < designAxisCount; ++i) + { + int a = designAxes + i * designAxisSize; + string tag = System.Text.Encoding.ASCII.GetString(data, a, 4); + ordering[i] = U16(a + 6); + coordinate[i] = double.NaN; + for (int k = 0; k < Axes.Length; ++k) if (Axes[k].Tag == tag) coordinate[i] = userCoordinates[k]; + } + bool Matches(int axis, double value) => axis < designAxisCount && Math.Abs(coordinate[axis] - value) < 1e-3; + string[] names = new string[designAxisCount]; + bool[] resolved = new bool[designAxisCount]; + List<(int axis, int nameId, bool elidable)> single = new List<(int, int, bool)>(); + for (int v = 0; v < valueCount; ++v) + { + int p = valueOffsets + U16(valueOffsets + 2 * v); + int format = U16(p); + if (format == 4) + { // a combination of values on several axes, e.g. "Condensed Bold", which wins over the single values + int count = U16(p + 2), flags = U16(p + 4), nameId = U16(p + 6); + List axes = new List(); + bool all = count > 0; + for (int k = 0; k < count; ++k) + { + int axis = U16(p + 8 + 6 * k); + if (!Matches(axis, Fixed(p + 10 + 6 * k))) all = false; + axes.Add(axis); + } + if (!all || axes.Any(axis => resolved[axis])) continue; + int first = axes.OrderBy(axis => ordering[axis]).First(); + foreach (int axis in axes) resolved[axis] = true; + names[first] = (flags & 2) != 0 ? "" : nameOf(nameId); + continue; + } + if (format < 1 || format > 3) continue; + int ax = U16(p + 2), fl = U16(p + 4), id = U16(p + 6); + if (ax >= designAxisCount) continue; + bool match; + if (double.IsNaN(coordinate[ax])) match = false; // an axis without variation: see below + else if (format == 2) match = Fixed(p + 12) <= coordinate[ax] && coordinate[ax] <= Fixed(p + 16); + else match = Matches(ax, Fixed(p + 8)); + if (match && !resolved[ax]) + { + resolved[ax] = true; + names[ax] = (fl & 2) != 0 ? "" : nameOf(id); + } + single.Add((ax, id, (fl & 2) != 0)); + } + for (int i = 0; i < designAxisCount; ++i) + { + if (resolved[i]) continue; + if (!double.IsNaN(coordinate[i])) return null; // an axis of "fvar" without a value for this instance + // an axis the font does not vary, e.g. "ital" of an upright font: its only value, if it has exactly one + List<(int axis, int nameId, bool elidable)> values = single.Where(x => x.axis == i).ToList(); + if (values.Count == 1 && !values[0].elidable) names[i] = nameOf(values[0].nameId); + } + for (int i = 0; i < designAxisCount; ++i) if (resolved[i] && names[i] == null) return null; // a name is missing + IEnumerable parts = Enumerable.Range(0, designAxisCount).OrderBy(i => ordering[i]).Select(i => names[i]).Where(n => !string.IsNullOrEmpty(n)); + string res = string.Join(" ", parts); + return res.Length == 0 ? "Regular" : res; + } + catch (IndexOutOfRangeException) + { + return null; // a broken "STAT" table: the names of "fvar" are used + } + } + + /// + /// The normalized coordinates (-1 to 1, with the "avar" mapping applied) for coordinates in user units, e.g. wght 300. + /// + public double[] Normalize(double[] userCoordinates) + { + double[] res = new double[Axes.Length]; + for (int i = 0; i < Axes.Length; ++i) + { + VariationAxis a = Axes[i]; + double v = userCoordinates != null && i < userCoordinates.Length ? userCoordinates[i] : a.Default; + v = Math.Max(a.Min, Math.Min(a.Max, v)); + double n = 0.0; + if (v < a.Default && a.Default > a.Min) n = (v - a.Default) / (a.Default - a.Min); + else if (v > a.Default && a.Max > a.Default) n = (v - a.Default) / (a.Max - a.Default); + if (avar != null) n = Map(avar[i], n); + res[i] = n; + } + return res; + } + private static double Map(List<(double from, double to)> map, double v) + { + if (map.Count == 0) return v; + if (v <= map[0].from) return v + map[0].to - map[0].from; + for (int i = 1; i < map.Count; ++i) + { + if (v <= map[i].from) + { + var (f0, t0) = map[i - 1]; + var (f1, t1) = map[i]; + if (f1 == f0) return t1; + return t0 + (v - f0) * (t1 - t0) / (f1 - f0); + } + } + return v + map[map.Count - 1].to - map[map.Count - 1].from; + } + + /// + /// The change of the advance width of a glyph from "HVAR", or NaN if the font has no "HVAR" table. + /// + public double GetAdvanceDelta(int glyph, double[] coords) + { + if (hvarStore == null) return double.NaN; + int outer = 0, inner = glyph; + if (hvarAdvanceMap >= 0) (outer, inner) = DeltaSetIndex(hvarAdvanceMap, glyph); + return hvarStore.GetDelta(outer, inner, coords); + } + /// + /// The item variation store of a CFF2 table, which its blend operator uses. + /// + public ItemVariationStore ReadItemVariationStore(int offset) + { + return new ItemVariationStore(data, offset, Axes.Length); + } + + /// + /// Adds the "gvar" deltas of a glyph to and : one delta per point and four for the + /// phantom points at the end. For a simple glyph are the last point indices of the contours + /// and the untouched points of a contour are interpolated (IUP); a composite glyph has one point per component and no IUP. + /// + public void AddGlyphDeltas(int glyph, double[] x, double[] y, int[] contourEnds, double[] coords, double[] dx, double[] dy) + { + if (gvarOffset < 0 || glyph >= gvarGlyphCount) return; + int start, end; + if (gvarLongOffsets) + { + start = (int)U32(gvarOffset + 20 + 4 * glyph); + end = (int)U32(gvarOffset + 20 + 4 * glyph + 4); + } + else + { + start = 2 * U16(gvarOffset + 20 + 2 * glyph); + end = 2 * U16(gvarOffset + 20 + 2 * glyph + 2); + } + if (end <= start) return; + int gvd = gvarArray + start; + int tupleCount = U16(gvd), countFlags = tupleCount; + tupleCount &= 0x0FFF; + int pos = gvd + U16(gvd + 2); // the serialized data + int header = gvd + 4; + int pointCount = x.Length; + int[] sharedPoints = null; + if ((countFlags & 0x8000) != 0) sharedPoints = ReadPoints(ref pos); + int axisCount = Axes.Length; + double[] peak = new double[axisCount], startTuple = new double[axisCount], endTuple = new double[axisCount]; + for (int t = 0; t < tupleCount; ++t) + { + int size = U16(header), index = U16(header + 2); + header += 4; + if ((index & 0x8000) != 0) + { + for (int a = 0; a < axisCount; ++a) peak[a] = F2Dot14(header + 2 * a); + header += 2 * axisCount; + } + else + { + int shared = index & 0x0FFF; + if (shared >= sharedTupleCount) throw new InvalidDataException("invalid shared tuple index"); + for (int a = 0; a < axisCount; ++a) peak[a] = F2Dot14(sharedTuples + 2 * (shared * axisCount + a)); + } + bool intermediate = (index & 0x4000) != 0; + if (intermediate) + { + for (int a = 0; a < axisCount; ++a) startTuple[a] = F2Dot14(header + 2 * a); + for (int a = 0; a < axisCount; ++a) endTuple[a] = F2Dot14(header + 2 * (axisCount + a)); + header += 4 * axisCount; + } + int tupleData = pos; + pos += size; + double scalar = Scalar(coords, peak, intermediate ? startTuple : null, intermediate ? endTuple : null); + if (scalar == 0.0) continue; + int p = tupleData; + int[] points = (index & 0x2000) != 0 ? ReadPoints(ref p) : sharedPoints; + int n = points == null ? pointCount : points.Length; // null: all points + double[] deltaX = ReadDeltas(ref p, n), deltaY = ReadDeltas(ref p, n); + if (points == null) + { + for (int i = 0; i < pointCount; ++i) + { + dx[i] += scalar * deltaX[i]; + dy[i] += scalar * deltaY[i]; + } + continue; + } + double[] tx = new double[pointCount], ty = new double[pointCount]; + bool[] touched = new bool[pointCount]; + for (int i = 0; i < points.Length; ++i) + { + int k = points[i]; + if (k < 0 || k >= pointCount) continue; + tx[k] += deltaX[i]; + ty[k] += deltaY[i]; + touched[k] = true; + } + if (contourEnds != null) InterpolateUntouched(x, y, contourEnds, touched, tx, ty); + for (int i = 0; i < pointCount; ++i) + { + dx[i] += scalar * tx[i]; + dy[i] += scalar * ty[i]; + } + } + } + + /// + /// The interpolation of untouched points (IUP): every point of a contour without a delta of its own gets a delta from the + /// two nearest touched points of its contour, separately for x and y. + /// + internal static void InterpolateUntouched(double[] x, double[] y, int[] contourEnds, bool[] touched, double[] dx, double[] dy) + { + int start = 0; + foreach (int end in contourEnds) + { + List refs = new List(); + for (int i = start; i <= end; ++i) if (touched[i]) refs.Add(i); + if (refs.Count > 0 && refs.Count <= end - start) + { + for (int r = 0; r < refs.Count; ++r) + { + int r1 = refs[r], r2 = refs[(r + 1) % refs.Count]; + // the untouched points after r1 up to r2 (cyclic in the contour) + for (int i = Next(r1, start, end); i != r2; i = Next(i, start, end)) + { + dx[i] = Interpolate(x[i], x[r1], x[r2], dx[r1], dx[r2]); + dy[i] = Interpolate(y[i], y[r1], y[r2], dy[r1], dy[r2]); + } + } + } + start = end + 1; + } + } + private static int Next(int i, int start, int end) => i == end ? start : i + 1; + private static double Interpolate(double v, double c1, double c2, double d1, double d2) + { + if (c1 == c2) return d1 == d2 ? d1 : 0.0; + if (c1 > c2) + { + (c1, c2) = (c2, c1); + (d1, d2) = (d2, d1); + } + if (v <= c1) return d1; + if (v >= c2) return d2; + return d1 + (v - c1) * (d2 - d1) / (c2 - c1); + } + + /// + /// The factor of a variation tuple at the coordinates (all normalized), as fontTools' supportScalar computes it. + /// + internal static double Scalar(double[] coords, double[] peak, double[] start, double[] end) + { + double scalar = 1.0; + for (int i = 0; i < peak.Length; ++i) + { + double p = peak[i]; + if (p == 0.0) continue; + double s = start != null ? start[i] : Math.Min(p, 0.0), e = end != null ? end[i] : Math.Max(p, 0.0); + if (s > p || p > e) continue; + if (s < 0.0 && e > 0.0) continue; + double v = i < coords.Length ? coords[i] : 0.0; + if (v == p) continue; + if (v <= s || e <= v) return 0.0; + if (v < p) scalar *= (v - s) / (p - s); + else scalar *= (e - v) / (e - p); + } + return scalar; + } + + private int[] ReadPoints(ref int pos) + { + int count = data[pos++]; + if (count == 0) return null; // all points + if ((count & 0x80) != 0) count = ((count & 0x7F) << 8) | data[pos++]; + int[] res = new int[count]; + int n = 0, last = 0; + while (n < count) + { + int control = data[pos++]; + int run = (control & 0x7F) + 1; + bool words = (control & 0x80) != 0; + for (int i = 0; i < run && n < count; ++i) + { + int d; + if (words) { d = U16(pos); pos += 2; } + else d = data[pos++]; + last += d; + res[n++] = last; + } + } + return res; + } + private double[] ReadDeltas(ref int pos, int count) + { + double[] res = new double[count]; + int n = 0; + while (n < count) + { + int control = data[pos++]; + int run = (control & 0x3F) + 1; + int kind = control & 0xC0; + for (int i = 0; i < run && n < count; ++i) + { + if (kind == 0x80) res[n++] = 0; + else if (kind == 0x40) { res[n++] = S16(pos); pos += 2; } + else if (kind == 0xC0) { res[n++] = (int)U32(pos); pos += 4; } + else res[n++] = (sbyte)data[pos++]; + } + } + return res; + } + private (int outer, int inner) DeltaSetIndex(int map, int index) + { + int format = data[map], entryFormat = data[map + 1]; + int count = format == 0 ? U16(map + 2) : (int)U32(map + 2); + int entries = map + (format == 0 ? 4 : 6); + if (count == 0) return (0, index); + if (index >= count) index = count - 1; + int size = ((entryFormat >> 4) & 3) + 1, innerBits = (entryFormat & 0xF) + 1; + int entry = 0; + for (int k = 0; k < size; ++k) entry = (entry << 8) | data[entries + index * size + k]; + return (entry >> innerBits, entry & ((1 << innerBits) - 1)); + } + + private int U16(int pos) => (data[pos] << 8) | data[pos + 1]; + private short S16(int pos) => (short)((data[pos] << 8) | data[pos + 1]); + private uint U32(int pos) => (uint)((data[pos] << 24) | (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3]); + private double Fixed(int pos) => (int)U32(pos) / 65536.0; + private double F2Dot14(int pos) => S16(pos) / 16384.0; + } + + /// + /// An item variation store ("HVAR", "MVAR", "CFF2"): rows of deltas for the regions of the variation space. + /// + internal sealed class ItemVariationStore + { + private readonly double[][] regionStart, regionPeak, regionEnd; // [region][axis] + private readonly int[][] regionIndexes; // per item variation data + private readonly int[][][] deltas; // [data][item][region] + + public ItemVariationStore(byte[] data, int offset, int axisCount) + { + int U16(int p) => (data[p] << 8) | data[p + 1]; + int U32(int p) => (data[p] << 24) | (data[p + 1] << 16) | (data[p + 2] << 8) | data[p + 3]; + double F2Dot14(int p) => (short)U16(p) / 16384.0; + int regionList = offset + U32(offset + 2); + int dataCount = U16(offset + 6); + int axes = U16(regionList), regionCount = U16(regionList + 2); + if (axes != axisCount) throw new InvalidDataException("the item variation store has another number of axes"); + regionStart = new double[regionCount][]; + regionPeak = new double[regionCount][]; + regionEnd = new double[regionCount][]; + for (int r = 0; r < regionCount; ++r) + { + regionStart[r] = new double[axes]; + regionPeak[r] = new double[axes]; + regionEnd[r] = new double[axes]; + for (int a = 0; a < axes; ++a) + { + int p = regionList + 4 + 6 * (r * axes + a); + regionStart[r][a] = F2Dot14(p); + regionPeak[r][a] = F2Dot14(p + 2); + regionEnd[r][a] = F2Dot14(p + 4); + } + } + regionIndexes = new int[dataCount][]; + deltas = new int[dataCount][][]; + for (int d = 0; d < dataCount; ++d) + { + int ivd = offset + U32(offset + 8 + 4 * d); + int itemCount = U16(ivd), wordDeltaCount = U16(ivd + 2), regionIndexCount = U16(ivd + 4); + bool longWords = (wordDeltaCount & 0x8000) != 0; + int words = wordDeltaCount & 0x7FFF; + regionIndexes[d] = new int[regionIndexCount]; + for (int k = 0; k < regionIndexCount; ++k) + { + regionIndexes[d][k] = U16(ivd + 6 + 2 * k); + if (regionIndexes[d][k] >= regionCount) throw new InvalidDataException("invalid region index"); + } + int p = ivd + 6 + 2 * regionIndexCount; + deltas[d] = new int[itemCount][]; + for (int i = 0; i < itemCount; ++i) + { + int[] row = new int[regionIndexCount]; + for (int k = 0; k < regionIndexCount; ++k) + { + if (k < words) + { + if (longWords) { row[k] = U32(p); p += 4; } + else { row[k] = (short)U16(p); p += 2; } + } + else + { + if (longWords) { row[k] = (short)U16(p); p += 2; } + else { row[k] = (sbyte)data[p]; p += 1; } + } + } + deltas[d][i] = row; + } + } + } + /// + /// The number of item variation data subtables. + /// + public int DataCount => deltas.Length; + /// + /// The factors of the regions of the item variation data at the coordinates. + /// + public double[] GetScalars(int outer, double[] coords) + { + if (outer < 0 || outer >= regionIndexes.Length) throw new InvalidDataException("invalid item variation data index"); + int[] regions = regionIndexes[outer]; + double[] res = new double[regions.Length]; + for (int k = 0; k < regions.Length; ++k) + { + int r = regions[k]; + res[k] = FontVariations.Scalar(coords, regionPeak[r], regionStart[r], regionEnd[r]); + } + return res; + } + /// + /// The delta of the item (, ) at the coordinates. + /// + public double GetDelta(int outer, int inner, double[] coords) + { + if (outer < 0 || outer >= deltas.Length || inner < 0 || inner >= deltas[outer].Length) return 0.0; + double[] scalars = GetScalars(outer, coords); + int[] row = deltas[outer][inner]; + double res = 0.0; + for (int k = 0; k < row.Length; ++k) res += row[k] * scalars[k]; + return res; + } + } +} diff --git a/CADability/GDI2DView.cs b/CADability/GDI2DView.cs index d62957521..b23bed040 100644 --- a/CADability/GDI2DView.cs +++ b/CADability/GDI2DView.cs @@ -552,8 +552,7 @@ bool ICommandHandler.OnCommand(string MenuId) } return true; case "MenuId.GDI2DView.Print": - if (project.printDocument == null) project.printDocument = new PrintDocument(); - PrintSinglePage(project.printDocument); + PrintSinglePage(project.PrintDocument); return true; case "MenuId.Zoom.Total": ZoomToModelExtent(1.1); diff --git a/CADability/ImportSVG.cs b/CADability/ImportSVG.cs index fb57433eb..e4444ec83 100644 --- a/CADability/ImportSVG.cs +++ b/CADability/ImportSVG.cs @@ -10,7 +10,6 @@ using System.Runtime.InteropServices.ComTypes; using CADability.Attribute; using MathNet.Numerics.LinearAlgebra.Factorization; -using static CADability.PrintToGDI; using System.Numerics; namespace CADability diff --git a/CADability/ImportStep.cs b/CADability/ImportStep.cs index 955480b2b..f3cd3d4f7 100644 --- a/CADability/ImportStep.cs +++ b/CADability/ImportStep.cs @@ -4076,7 +4076,7 @@ private object CreateEntity(Item item) child.parameter["_transformation"] = new Item(Item.ItemType.created, transformation); child.parameter["_referred"] = relatedProduct; relatingProduct.parameter["_children"].lval.Add(child); - products.Add(relatingProduct); + lock (products) products.Add(relatingProduct); // the parallel import adds products on several threads } #if DEBUG //System.Diagnostics.Trace.WriteLine(SubTree(item.definingIndex, 5)); @@ -4590,7 +4590,7 @@ private object CreateEntity(Item item) // System.Diagnostics.Trace.WriteLine(up[i]); //} #endif - mappedItems.Add(item); + lock (mappedItems) mappedItems.Add(item); // the parallel import creates mapped items on several threads string name = item.parameter["name"].sval; object mo = CreateEntity(item.parameter["mapping_source"].parameter["mapping_origin"]); object mr = CreateEntity(item.parameter["mapping_source"].parameter["mapped_representation"]); diff --git a/CADability/JsonSerialize.cs b/CADability/JsonSerialize.cs index 711c647af..b6a53a311 100644 --- a/CADability/JsonSerialize.cs +++ b/CADability/JsonSerialize.cs @@ -1097,8 +1097,13 @@ private void CreateDeserializer(string typeName, string assemblyName, int ti) } Type tp = null; - if (assemblyName == null && typeName.StartsWith("System.Drawing.")) assemblyName = "System.Drawing"; // this is for old files, where System.Drawing objects where handled different - if (assemblyName == null) tp = Type.GetType(typeName); + // a GDI+ bitmap of a picture or icon in a file of CADability 1.x: only its image file bytes are read, so that + // the file opens without GDI+ (issue #412) + if (LegacyImageData.IsGdiImageType(typeName)) tp = typeof(LegacyImageData); + // the page settings in a file of CADability 1.x: their values are read into a PageSetup (issue #412) + if (LegacyPrintingData.IsPrintingType(typeName)) tp = typeof(LegacyPrintingData); + if (tp == null && assemblyName == null && typeName.StartsWith("System.Drawing.")) assemblyName = "System.Drawing"; // this is for old files, where System.Drawing objects where handled different + if (tp == null && assemblyName == null) tp = Type.GetType(typeName); if (tp == null) { if (loadedAssemblies.TryGetValue(assemblyName, out Assembly assembly)) tp = assembly.GetType(typeName); diff --git a/CADability/Layout.cs b/CADability/Layout.cs index 0a6ca0f74..7dd504dee 100644 --- a/CADability/Layout.cs +++ b/CADability/Layout.cs @@ -21,9 +21,15 @@ public class Layout : ISerializable private double paperWidth; private double paperHeight; /// - /// The printer settings. + /// The printer settings. Not used by CADability any more and not saved; see . /// - public PageSettings pageSettings; // kommt vom PrintDocument, kann auch null sein, ist Serializable + [Obsolete("Use PageSetup. The GDI+ PageSettings cannot be saved on .NET Core (issue #412).")] + public PageSettings pageSettings; // kommt vom PrintDocument, kann auch null sein + /// + /// The page settings of this layout for printing, saved with the layout. Null if the layout has none: then the + /// page settings of the project () are used. + /// + public PageSetup PageSetup { get; set; } internal Project project; /// /// Creates an empty Layout @@ -344,8 +350,11 @@ protected Layout(SerializationInfo info, StreamingContext context) case "paperHeight": paperHeight = (double)e.Value; break; - case "PageSettings": - pageSettings = e.Value as PageSettings; + case "PageSetup": + PageSetup = e.Value as PageSetup; + break; + case "PageSettings": // 1.x: a serialized GDI+ PageSettings + if (PageSetup == null) PageSetup = PageSetup.FromSerialized(e.Value); break; } } @@ -358,7 +367,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.AddValue("Patches", patches, typeof(LayoutPatch[])); info.AddValue("paperWidth", paperWidth, typeof(double)); info.AddValue("paperHeight", paperHeight, typeof(double)); - if (pageSettings != null) info.AddValue("PageSettings", pageSettings); + if (PageSetup != null) info.AddValue("PageSetup", PageSetup); } #endregion diff --git a/CADability/LayoutView.cs b/CADability/LayoutView.cs index 7a5e69276..88d888b1d 100644 --- a/CADability/LayoutView.cs +++ b/CADability/LayoutView.cs @@ -84,21 +84,15 @@ public LayoutView(Layout layout, Project project) this.layout = layout; screenToLayout = layoutToScreen = ModOp2D.Null; base.resourceIdInternal = "LayoutView"; - // printDocument = project.printDocument; printDocument = new PrintDocument(); - - if (printDocument != null) + // the page settings of the layout, else those of the project, else the orientation of the layout's paper + PageSetup pageSetup = layout.PageSetup ?? project.DefaultPageSetup; + try { - if (layout.pageSettings != null) - { - printDocument.DefaultPageSettings = layout.pageSettings; - } - else - { - if (project.printDocument != null) printDocument.DefaultPageSettings = (PageSettings)project.printDocument.DefaultPageSettings.Clone(); - else printDocument.DefaultPageSettings.Landscape = layout.PaperWidth > layout.PaperHeight; - } + if (pageSetup != null) pageSetup.ApplyTo(printDocument); + else printDocument.DefaultPageSettings.Landscape = layout.PaperWidth > layout.PaperHeight; } + catch (InvalidPrinterException) { } // no printer installed } event ScrollPositionChanged IView.ScrollPositionChangedEvent @@ -1047,27 +1041,19 @@ private void PaintFlatVisibleLayer(IPaintTo3D iPaintTo3D, IGeoObject go, Hashtab private void OnPrint() { - if (printDocument == null) printDocument = project.printDocument; - if (printDocument == null) - printDocument = new PrintDocument(); + if (printDocument == null) printDocument = project.PrintDocument; Print(printDocument); } private void OnPrintSetup() { //psd.AllowPrinter = true; //psd.EnableMetric = true; - if (printDocument == null) - { - if (project.printDocument == null) project.printDocument = new PrintDocument(); - printDocument = project.printDocument; - } - if (layout.pageSettings != null) - { - printDocument.DefaultPageSettings = layout.pageSettings; - } - - if (Frame.UIService.ShowPageSetupDlg(ref printDocument, layout.pageSettings, out int width, out int height, out bool landscape) == DialogResult.OK) + if (printDocument == null) printDocument = project.PrintDocument; + if (layout.PageSetup != null) layout.PageSetup.ApplyTo(printDocument); + + if (Frame.UIService.ShowPageSetupDlg(ref printDocument, printDocument.DefaultPageSettings, out int width, out int height, out bool landscape) == DialogResult.OK) { + layout.PageSetup = PageSetup.FromPageSettings(printDocument.DefaultPageSettings); // saved with the layout //psd.Document.OriginAtMargins = false; if (landscape) @@ -1088,11 +1074,7 @@ private void OnPrintSetup() } private void OnSelectPrinter() { - if (printDocument == null) - { - if (project.printDocument == null) project.printDocument = new PrintDocument(); - printDocument = project.printDocument; - } + if (printDocument == null) printDocument = project.PrintDocument; //PrintDialog printDialog = new PrintDialog(); //printDialog.Document = printDocument; //printDialog.AllowSomePages = false; @@ -1102,6 +1084,7 @@ private void OnSelectPrinter() if (Frame.UIService.ShowPrintDlg(ref printDocument) == DialogResult.OK) { // printDocument = printDialog.Document; update in ShowPrintDlg + layout.PageSetup = PageSetup.FromPageSettings(printDocument.DefaultPageSettings); // the printer is saved with the layout } } diff --git a/CADability/PageSetup.cs b/CADability/PageSetup.cs new file mode 100644 index 000000000..68d60f33f --- /dev/null +++ b/CADability/PageSetup.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace CADability +{ + /// + /// The page settings of a or a for printing: the paper, its orientation, the + /// margins and the printer. This is what project files contain instead of the GDI+ PageSettings, so that they + /// can be written and read on every platform (issue #412). All lengths are in millimeters. + /// + [Serializable] + public sealed class PageSetup : ISerializable + { + /// + /// The name of the paper, e.g. "A4" or "Letter"; null if the printer's default paper is used. + /// + public string PaperName { get; set; } + /// + /// The paper kind of the printing system (the DMPAPER number of Windows, e.g. 9 for A4), 0 for a custom paper or + /// if it is not known. It only helps to find the same paper on a printer again. + /// + public int PaperKind { get; set; } + /// + /// The width of the paper in portrait orientation in mm; 0 if the printer's default paper is used. + /// + public double PaperWidth { get; set; } + /// + /// The height of the paper in portrait orientation in mm; 0 if the printer's default paper is used. + /// + public double PaperHeight { get; set; } + /// + /// True if the page is printed in landscape orientation. + /// + public bool Landscape { get; set; } + /// + /// The margins in mm. + /// + public double MarginLeft { get; set; } + /// The right margin in mm. + public double MarginRight { get; set; } + /// The top margin in mm. + public double MarginTop { get; set; } + /// The bottom margin in mm. + public double MarginBottom { get; set; } + /// + /// The name of the printer; null for the default printer. + /// + public string PrinterName { get; set; } + + /// + /// Creates page settings for the default paper of the default printer, in portrait orientation, without margins. + /// + public PageSetup() + { + } + /// + /// Returns a copy of these page settings. + /// + public PageSetup Clone() + { + return (PageSetup)MemberwiseClone(); + } + + // GDI+ measures paper and margins in hundredths of an inch + private const double HundredthInch = 0.254; + + #region ISerializable + private PageSetup(SerializationInfo info, StreamingContext context) + { + foreach (SerializationEntry entry in info) + { + switch (entry.Name) + { + case "PaperName": PaperName = entry.Value as string; break; + case "PaperKind": PaperKind = Convert.ToInt32(entry.Value); break; + case "PaperWidth": PaperWidth = Convert.ToDouble(entry.Value); break; + case "PaperHeight": PaperHeight = Convert.ToDouble(entry.Value); break; + case "Landscape": Landscape = Convert.ToBoolean(entry.Value); break; + case "MarginLeft": MarginLeft = Convert.ToDouble(entry.Value); break; + case "MarginRight": MarginRight = Convert.ToDouble(entry.Value); break; + case "MarginTop": MarginTop = Convert.ToDouble(entry.Value); break; + case "MarginBottom": MarginBottom = Convert.ToDouble(entry.Value); break; + case "PrinterName": PrinterName = entry.Value as string; break; + } + } + } + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("PaperName", PaperName); + info.AddValue("PaperKind", PaperKind); + info.AddValue("PaperWidth", PaperWidth); + info.AddValue("PaperHeight", PaperHeight); + info.AddValue("Landscape", Landscape); + info.AddValue("MarginLeft", MarginLeft); + info.AddValue("MarginRight", MarginRight); + info.AddValue("MarginTop", MarginTop); + info.AddValue("MarginBottom", MarginBottom); + info.AddValue("PrinterName", PrinterName); + } + #endregion + + /// + /// The page settings of a project file of CADability 1.x, which contains a serialized GDI+ PageSettings: either + /// the it is read as, or a PageSettings object itself. Null if there are none + /// or they cannot be understood. + /// + internal static PageSetup FromSerialized(object value) + { + try + { + if (value is LegacyPrintingData legacy) return FromLegacy(legacy); + if (value is System.Drawing.Printing.PageSettings pageSettings) return FromPageSettings(pageSettings); + } + catch (Exception e) when (!(e is OutOfMemoryException) && !(e is System.Threading.ThreadAbortException)) + { + System.Diagnostics.Trace.TraceWarning("The page settings of the project file cannot be read: " + e.Message); + } + return null; + } + /// + /// Reads the fields of a serialized PageSettings. The JSON format of 1.x has the public properties (e.g. + /// "Landscape", "PaperSize" with "Width", "PaperName" and "RawKind"), the binary format of .NET Framework the private + /// fields (e.g. "landscape" as a TriState, "paperSize" with "width", "name" and "kind"). + /// + private static PageSetup FromLegacy(LegacyPrintingData data) + { + PageSetup res = new PageSetup(); + res.Landscape = AsBool(data.Get("Landscape", "landscape")); + if (data.Get("PaperSize", "paperSize") is LegacyPrintingData paper) + { + res.PaperName = paper.Get("PaperName", "name") as string; + res.PaperKind = AsInt(paper.Get("RawKind", "kind")); + res.PaperWidth = AsInt(paper.Get("Width", "width")) * HundredthInch; + res.PaperHeight = AsInt(paper.Get("Height", "height")) * HundredthInch; + } + if (data.Get("Margins", "margins") is LegacyPrintingData margins) + { + res.MarginLeft = AsInt(margins.Get("Left", "left")) * HundredthInch; + res.MarginRight = AsInt(margins.Get("Right", "right")) * HundredthInch; + res.MarginTop = AsInt(margins.Get("Top", "top")) * HundredthInch; + res.MarginBottom = AsInt(margins.Get("Bottom", "bottom")) * HundredthInch; + } + if (data.Get("PrinterSettings", "printerSettings") is LegacyPrintingData printer) + { + string name = printer.Get("PrinterName", "printerName") as string; + res.PrinterName = string.IsNullOrEmpty(name) ? null : name; // empty: the default printer + } + return res; + } + private static bool AsBool(object value) + { + if (value is bool b) return b; + // the internal TriState of System.Drawing.Printing: 0 is the printer's default, 1 false, 2 true + if (value is LegacyPrintingData triState) return AsInt(triState.Get("value")) == 2; + return false; + } + private static int AsInt(object value) + { + if (value is LegacyPrintingData enumValue) value = enumValue.Get("value__"); // an enum, e.g. PaperKind + if (value == null) return 0; + try + { + return Convert.ToInt32(value); + } + catch (Exception e) when (e is FormatException || e is InvalidCastException || e is OverflowException) + { + return 0; + } + } + + /// + /// The page settings of a GDI+ PageSettings (Windows only). + /// + internal static PageSetup FromPageSettings(System.Drawing.Printing.PageSettings pageSettings) + { + if (pageSettings == null) return null; + PageSetup res = new PageSetup(); + res.Landscape = pageSettings.Landscape; + System.Drawing.Printing.PaperSize paper = pageSettings.PaperSize; + if (paper != null) + { + res.PaperName = paper.PaperName; + res.PaperKind = paper.RawKind; + res.PaperWidth = paper.Width * HundredthInch; + res.PaperHeight = paper.Height * HundredthInch; + } + System.Drawing.Printing.Margins margins = pageSettings.Margins; + if (margins != null) + { + res.MarginLeft = margins.Left * HundredthInch; + res.MarginRight = margins.Right * HundredthInch; + res.MarginTop = margins.Top * HundredthInch; + res.MarginBottom = margins.Bottom * HundredthInch; + } + string printer = pageSettings.PrinterSettings?.PrinterName; + res.PrinterName = string.IsNullOrEmpty(printer) ? null : printer; + return res; + } + + /// + /// Applies these page settings to a GDI+ print document (Windows only): the printer, if it is installed, the paper + /// of that printer with the same kind, name or size (or a custom paper of this size), the orientation and the margins. + /// + internal void ApplyTo(System.Drawing.Printing.PrintDocument printDocument) + { + System.Drawing.Printing.PrinterSettings printer = printDocument.PrinterSettings; + if (!string.IsNullOrEmpty(PrinterName)) + { + foreach (string installed in System.Drawing.Printing.PrinterSettings.InstalledPrinters) + { + if (installed == PrinterName) + { + printer.PrinterName = PrinterName; + break; + } + } + } + System.Drawing.Printing.PageSettings pageSettings = printDocument.DefaultPageSettings; + if (PaperWidth > 0 && PaperHeight > 0) + { + int width = (int)Math.Round(PaperWidth / HundredthInch), height = (int)Math.Round(PaperHeight / HundredthInch); + System.Drawing.Printing.PaperSize found = null; + foreach (System.Drawing.Printing.PaperSize size in printer.PaperSizes) + { + bool sameSize = Math.Abs(size.Width - width) <= 1 && Math.Abs(size.Height - height) <= 1; + if (PaperKind != 0 && size.RawKind == PaperKind && sameSize) { found = size; break; } + if (found == null && sameSize && (PaperName == null || size.PaperName == PaperName)) found = size; + } + pageSettings.PaperSize = found ?? new System.Drawing.Printing.PaperSize(PaperName ?? "Custom", width, height); + } + pageSettings.Landscape = Landscape; + pageSettings.Margins = new System.Drawing.Printing.Margins((int)Math.Round(MarginLeft / HundredthInch), (int)Math.Round(MarginRight / HundredthInch), + (int)Math.Round(MarginTop / HundredthInch), (int)Math.Round(MarginBottom / HundredthInch)); + } + } + + /// + /// Stands for an object of System.Drawing.Printing (PageSettings, PaperSize, Margins, + /// PrinterSettings and their enums) in a project file written by CADability 1.x. It only collects the serialized + /// values, so that the file opens without GDI+ printing, and takes the page settings from it + /// (issue #412). + /// + [Serializable] + internal sealed class LegacyPrintingData : ISerializable + { + private readonly Dictionary values = new Dictionary(); + + private LegacyPrintingData(SerializationInfo info, StreamingContext context) + { + foreach (SerializationEntry entry in info) values[entry.Name] = entry.Value; + } + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + foreach (KeyValuePair value in values) info.AddValue(value.Key, value.Value); + } + /// + /// The first of the values with these names, null if there is none. + /// + public object Get(params string[] names) + { + foreach (string name in names) if (values.TryGetValue(name, out object value)) return value; + return null; + } + /// + /// True for the types of System.Drawing.Printing, which CADability 1.x wrote into project files with the page + /// settings. + /// + public static bool IsPrintingType(string typeName) + { + return typeName != null && typeName.StartsWith("System.Drawing.Printing."); + } + } +} diff --git a/CADability/PrintToGDI.cs b/CADability/PrintToGDI.cs index 9563707c3..6a63ca9e3 100644 --- a/CADability/PrintToGDI.cs +++ b/CADability/PrintToGDI.cs @@ -1515,11 +1515,7 @@ public static void PrintSinglePage(Project pr, string nameOfView, bool print2D, found = pr.GdiViews[i]; } } - if (pd == null) - { - pd = pr.printDocument; - if (pd == null) pd = new PrintDocument(); - } + if (pd == null) pd = pr.PrintDocument; if (found != null) { PrintToGDI ptg = new PrintToGDI(found); diff --git a/CADability/Project.cs b/CADability/Project.cs index d2a97c0e7..3b720e38f 100644 --- a/CADability/Project.cs +++ b/CADability/Project.cs @@ -336,7 +336,53 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) private AnimatedView[] ser_animatedViews; private Layout[] ser_layouts; private GDI2DView[] ser_gdiViews; - internal PrintDocument printDocument; // das PrintDocument zum Drucken, enthält die Druckereinstellungen + private PrintDocument printDocument; // das PrintDocument zum Drucken, enthält die Druckereinstellungen; created on demand (Windows) + private PageSetup defaultPageSetup; // the page settings as long as there is no printDocument + /// + /// The page settings of the project for printing, which are saved with the project. Null if none have been set: then + /// the printer's defaults are used. On Windows they are also changed by the page setup and printer dialogs. + /// + public PageSetup DefaultPageSetup + { + get + { + if (printDocument != null) + { + try + { + return PageSetup.FromPageSettings(printDocument.DefaultPageSettings); + } + catch (InvalidPrinterException) { } // no printer installed: the settings that were read or set + } + return defaultPageSetup?.Clone(); + } + set + { + defaultPageSetup = value?.Clone(); + printDocument = null; // made again from the new settings when needed + } + } + /// + /// The print document with the page settings of the project, created from when it is + /// first needed (Windows only). + /// + internal PrintDocument PrintDocument + { + get + { + if (printDocument == null) + { + PrintDocument created = new PrintDocument(); + try + { + defaultPageSetup?.ApplyTo(created); + } + catch (InvalidPrinterException) { } // no printer installed: its defaults + printDocument = created; + } + return printDocument; + } + } /// /// Creates an empty Project. The empty project contains clones of the globally defined /// attributes and attribute lists (like colors, layers etc.) @@ -1476,11 +1522,33 @@ public void ExportToWebGl(string fileName) wgl.WriteToFile(fileName); } + /// + /// Reads the GDI+ objects in binary project files of CADability 1.x without GDI+ (issue #412): the bitmaps of pictures + /// and icons as , i.e. as their image file bytes, and the page settings with all their + /// parts as , from which a is made. All other types are bound + /// as usual. + /// + private class LegacySystemDrawingBinder : SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + return BindLegacySystemDrawing(assemblyName, typeName); // null: the formatter's own binding + } + } + private static Type BindLegacySystemDrawing(string assemblyName, string typeName) + { + if (assemblyName == null || !assemblyName.StartsWith("System.Drawing")) return null; + if (LegacyImageData.IsGdiImageType(typeName)) return typeof(LegacyImageData); + if (LegacyPrintingData.IsPrintingType(typeName)) return typeof(LegacyPrintingData); + return null; + } private class CondorSerializationBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { System.Diagnostics.Trace.WriteLine("BindToType: " + assemblyName + ", " + typeName); + Type legacy = BindLegacySystemDrawing(assemblyName, typeName); + if (legacy != null) return legacy; // Diese Zeilen dienen dazu alte CONDOR Dateien lesbar zu machen. // evtl. machen sie Schwierigkeiten, wenn Objekte von anderen Modulen // deserialisiert werden sollen. Dann müsste man weiter unten es wieder @@ -1649,6 +1717,8 @@ public static Project ReadFromStream(Stream stream) #if FW2 formatter.Binder = new CondorSerializationBinder(); // das soll hier gleich laufen, damit nicht versucht wird die CADability.dll zu laden +#else + formatter.Binder = new LegacySystemDrawingBinder(); #endif long startPosition = stream.Position; try @@ -2583,17 +2653,10 @@ protected Project(SerializationInfo info, StreamingContext context) } if (info.MemberCount > 12) // to avoid exceptions { - try - { - PageSettings ps = (PageSettings)info.GetValue("DefaultPageSettings", typeof(PageSettings)); - if (printDocument == null) printDocument = new PrintDocument(); - printDocument.DefaultPageSettings = ps; - } - catch (SerializationException) - { - } - catch (Exception e) when (IsSystemDrawingUnavailable(e)) + foreach (SerializationEntry entry in info) { + if (entry.Name == "DefaultPageSetup") defaultPageSetup = entry.Value as PageSetup; + else if (entry.Name == "DefaultPageSettings" && defaultPageSetup == null) defaultPageSetup = PageSetup.FromSerialized(entry.Value); // 1.x } } base.resourceIdInternal = "ProjectSettings"; @@ -2653,10 +2716,9 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte info.AddValue("FilterList", filterList); info.AddValue("AnimatedViews", animatedViews.ToArray()); info.AddValue("ActiveViewName", activeViewName); - if (!Settings.GlobalSettings.GetBoolValue("DontSave.System.Drawing", false)) - { - if (printDocument != null) info.AddValue("DefaultPageSettings", printDocument.DefaultPageSettings); - } + // the neutral page settings, not the GDI+ PageSettings, which cannot be serialized on .NET Core (issue #412) + PageSetup pageSetup = DefaultPageSetup; + if (pageSetup != null) info.AddValue("DefaultPageSetup", pageSetup); } /// /// Implements : writes the models, the active model, the @@ -2682,10 +2744,9 @@ public void GetObjectData(IJsonWriteData data) data.AddProperty("FilterList", filterList); data.AddProperty("AnimatedViews", animatedViews); data.AddProperty("ActiveViewName", activeViewName); - if (!Settings.GlobalSettings.GetBoolValue("DontSave.System.Drawing", false)) - { - if (printDocument != null) data.AddProperty("DefaultPageSettings", printDocument.DefaultPageSettings); - } + // the neutral page settings, not the GDI+ PageSettings, which cannot be serialized on .NET Core (issue #412) + PageSetup pageSetup = DefaultPageSetup; + if (pageSetup != null) data.AddProperty("DefaultPageSetup", pageSetup); } /// @@ -2717,15 +2778,9 @@ public void SetObjectData(IJsonReadData data) filterList = data.GetPropertyOrDefault("FilterList"); animatedViews = data.GetPropertyOrDefault>("AnimatedViews"); activeViewName = data.GetPropertyOrDefault("ActiveViewName"); - try - { - if (!Settings.GlobalSettings.GetBoolValue("DontSave.System.Drawing", false)) - { - printDocument = new PrintDocument(); - printDocument.DefaultPageSettings = data.GetPropertyOrDefault("DefaultPageSettings"); - } - } - catch (Exception e) when (IsSystemDrawingUnavailable(e)) { } + defaultPageSetup = data.GetPropertyOrDefault("DefaultPageSetup"); + if (defaultPageSetup == null && data.HasProperty("DefaultPageSettings")) defaultPageSetup = PageSetup.FromSerialized(data.GetProperty("DefaultPageSettings")); // 1.x + printDocument = null; data.RegisterForSerializationDoneCallback(this); } /// diff --git a/CADability/Properties/AssemblyInfo.cs b/CADability/Properties/AssemblyInfo.cs index 6fac604ac..daaf7f73e 100644 --- a/CADability/Properties/AssemblyInfo.cs +++ b/CADability/Properties/AssemblyInfo.cs @@ -3,3 +3,5 @@ [assembly: CLSCompliant(true)] // the unit tests (tests/CADability.Tests, signed with CADabilityKey.snk) may use internal classes like FontCache [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CADability.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1ad8b0ed092aad57de8d0423919856eab910629e254ad40d6de3709a87cc161162d71827d65177e862b4822364c691d32f20beb81f0f7c17690662c2b397fe7bf556ac85e9dc66c7de56f435df1c5899a22b6fde65c423c1ec4fe3e4bb316838dbc7332ff1d31995a1657f754f942b36b82787d4c9c8e4325b4fb2871bdedbb")] +// CADability.Forms makes its GDI font provider the default (FontProvider.SetDefault) +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CADability.Forms, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1ad8b0ed092aad57de8d0423919856eab910629e254ad40d6de3709a87cc161162d71827d65177e862b4822364c691d32f20beb81f0f7c17690662c2b397fe7bf556ac85e9dc66c7de56f435df1c5899a22b6fde65c423c1ec4fe3e4bb316838dbc7332ff1d31995a1657f754f942b36b82787d4c9c8e4325b4fb2871bdedbb")] diff --git a/CADability/SyntheticFontFace.cs b/CADability/SyntheticFontFace.cs new file mode 100644 index 000000000..add2556b8 --- /dev/null +++ b/CADability/SyntheticFontFace.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace CADability +{ + /// + /// A bold or italic face made from another face of the family, for a family that does not have that style, the way GDI+ + /// made it for the texts of CADability before the font service (issue #412): GDI+ reports every style of every family + /// as available and synthesizes the missing ones. + /// + /// Italic is a shear of the glyphs by 0x5700/0x10000 (18.77°) around the baseline. The advance widths stay the same. + /// Bold makes every stroke wider: the outline is moved outwards by half the strength on every side + /// (FT_Outline_EmboldenXY of FreeType) and then by half the strength to the right and up, so that the left side and the + /// baseline of a glyph stay where they are and its right side and top grow by the strength. The strength is + /// ceiling(em / 50) font units horizontally and one unit less vertically, e.g. 41 and 40 for 2048 units per em. A face + /// with a weight of 600 (semibold) or more is not made wider. The advance widths stay the same, as with the widths of + /// GDI that CADability used. + /// + /// Both were measured against GDI+ on Windows. + /// + internal sealed class SyntheticFontFace : FontFace + { + /// + /// The shear of the synthetic italic of GDI and GDI+: 0x5700/0x10000, about tan(18.77°). + /// + internal const double ItalicShear = 0x5700 / 65536.0; + + private readonly FontFace face; + private readonly bool bold, italic; + + public SyntheticFontFace(FontFace face, bool bold, bool italic) + { + this.face = face; + this.bold = bold; + this.italic = italic; + } + /// + /// The face the synthetic style is made from. + /// + internal FontFace BaseFace => face; + public override string FamilyName => face.FamilyName; + public override bool IsBold => bold || face.IsBold; + public override bool IsItalic => italic || face.IsItalic; + public override int EmHeight => face.EmHeight; + public override int CellAscent => face.CellAscent; + public override int CellDescent => face.CellDescent; + public override int LineSpacing => face.LineSpacing; + public override double GetAdvanceWidth(int codePoint) => face.GetAdvanceWidth(codePoint); + public override double GetKerning(int left, int right) => face.GetKerning(left, right); + + /// + /// The strength of the synthetic bold of in units of the em height, horizontally and + /// vertically: how much wider and higher a stroke gets. Zero for a face with a weight of 600 or more. + /// + internal static (double x, double y) BoldStrengthOf(FontFace face) + { + int weight = face is ManagedFontFace managed ? managed.Font.WeightClass : face.IsBold ? 700 : 400; + if (weight >= 600) return (0.0, 0.0); // GDI+ does not make a semibold or heavier face wider + int em = face.EmHeight; + int x = (int)Math.Ceiling(em / 50.0); + return (x / (double)em, (x - 1) / (double)em); + } + + public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types) + { + (double x, double y) = bold ? BoldStrengthOf(face) : (0.0, 0.0); + if (x > 0 && face is ManagedFontFace managed) + { // made wider on the outline of the font, before the quadratic segments of TrueType become cubic ones + managed.GetGlyphPath(codePoint, underline, strikeout, x, y, out points, out types); + } + else + { + face.GetGlyphPath(codePoint, underline, strikeout, out points, out types); + if (x > 0 && points.Length > 0) points = Embolden(points, types, x, y); + } + if (points.Length == 0) return; + if (italic) + { + // the path is in units of the em height with y downwards and the baseline at CellAscent/EmHeight + double baseline = CellAscent / (double)EmHeight; + for (int i = 0; i < points.Length; ++i) points[i].X = (float)(points[i].X + ItalicShear * (baseline - points[i].Y)); + } + } + + /// + /// Makes the strokes of a path (in the layout of , y downwards) wider by + /// and higher by , see . The + /// control points of the Bezier segments are moved like the other points. For the glyphs of a TrueType font this is + /// done on the quadratic outline instead, as GDI+ does it (see ). + /// + internal static PointF[] Embolden(PointF[] points, byte[] types, double strengthX, double strengthY) + { + List<(double x, double y)[]> contours = new List<(double x, double y)[]>(); + List starts = new List(); + int first = 0; + while (first < types.Length) + { + int last = first; + while (last + 1 < types.Length && (types[last + 1] & 0x07) != 0) ++last; // a figure ends before the next start point + (double x, double y)[] contour = new (double x, double y)[last - first + 1]; + for (int i = first; i <= last; ++i) contour[i - first] = (points[i].X, -points[i].Y); // y upwards + contours.Add(contour); + starts.Add(first); + first = last + 1; + } + List<(double x, double y)[]> bold = EmboldenContours(contours, strengthX, strengthY); + PointF[] res = new PointF[points.Length]; + for (int c = 0; c < bold.Count; ++c) + for (int i = 0; i < bold[c].Length; ++i) res[starts[c] + i] = new PointF((float)bold[c][i].x, (float)-bold[c][i].y); + return res; + } + + /// + /// Makes the strokes of an outline (closed contours, y upwards) wider by and higher by + /// : FT_Outline_EmboldenXY of FreeType moves every point of a contour outwards by half + /// the strength along the bisector of its two edges, then the outline is moved by half the strength to the right + /// and up. Unlike FreeType, which limits the shift at an inner corner by the length of the shorter edge, the shift + /// is kept within half the strength on each axis, as GDI+ does it: a short edge at a rounded corner still moves by + /// the whole strength, and a sharp corner does not shoot out further than its edges. For a polygon this moves every + /// edge that faces right by the strength to the right and every edge that faces up by the strength up, and leaves + /// the others where they are, which is what GDI+ does. The orientation is that of the whole outline, as FreeType + /// determines it: the holes run the other way than the outer contours, so that "outwards" means away from the ink + /// for both, and the holes get smaller. + /// + internal static List<(double x, double y)[]> EmboldenContours(IList<(double x, double y)[]> contours, double strengthX, double strengthY) + { + List<(double x, double y)[]> res = new List<(double x, double y)[]>(contours.Count); + foreach ((double x, double y)[] contour in contours) res.Add(((double x, double y)[])contour.Clone()); + double area = 0.0; + foreach ((double x, double y)[] contour in contours) + for (int i = 0; i < contour.Length; ++i) + { + var p = contour[i]; var q = contour[(i + 1) % contour.Length]; + area += p.x * q.y - q.x * p.y; + } + if (area == 0.0) return res; + bool clockwise = area < 0; // the TrueType orientation (outer contours clockwise with y upwards) + double halfX = strengthX / 2.0, halfY = strengthY / 2.0; + for (int c = 0; c < contours.Count; ++c) EmboldenContour(contours[c], res[c], halfX, halfY, clockwise); + foreach ((double x, double y)[] contour in res) + for (int i = 0; i < contour.Length; ++i) contour[i] = (contour[i].x + halfX, contour[i].y + halfY); + return res; + } + + private static void EmboldenContour((double x, double y)[] points, (double x, double y)[] res, double halfX, double halfY, bool clockwise) + { + int n = points.Length; + if (n < 3) return; + (double x, double y) P(int i) => points[(i % n + n) % n]; + // the incoming edge of the first point: the last edge with a length + double inX = 0, inY = 0, lIn = 0; + for (int k = 1; k <= n; ++k) + { + inX = P(0).x - P(-k).x; inY = P(0).y - P(-k).y; + lIn = Math.Sqrt(inX * inX + inY * inY); + if (lIn > 0) break; + } + if (lIn == 0) return; + inX /= lIn; inY /= lIn; + for (int i = 0; i < n; ++i) + { + if (i > 0 && P(i) == P(i - 1)) + { // a point on top of the previous one moves with it + res[i] = res[i - 1]; + continue; + } + double outX = 0, outY = 0, lOut = 0; + for (int k = 1; k <= n; ++k) + { + outX = P(i + k).x - P(i).x; outY = P(i + k).y - P(i).y; + lOut = Math.Sqrt(outX * outX + outY * outY); + if (lOut > 0) break; + } + if (lOut == 0) return; + outX /= lOut; outY /= lOut; + double shiftX = 0, shiftY = 0; + double d = inX * outX + inY * outY; // cosine of the turn + if (d > -0.9375) // not a sharp turn back, where the shift would be huge + { + d += 1; + shiftX = inY + outY; + shiftY = inX + outX; + if (clockwise) shiftX = -shiftX; else shiftY = -shiftY; + // the shift along the bisector; GDI+ does not limit it at an inner corner by the length of the shorter + // edge as FreeType does, but keeps it within half the strength on each axis, so that a sharp corner moves + // no further than an edge + shiftX = Math.Max(-halfX, Math.Min(halfX, shiftX * halfX / d)); + shiftY = Math.Max(-halfY, Math.Min(halfY, shiftY * halfY / d)); + } + res[i] = (points[i].x + shiftX, points[i].y + shiftY); + inX = outX; inY = outY; + } + } + } +} diff --git a/CADability/Text.cs b/CADability/Text.cs index 1488360b8..0e171b094 100644 --- a/CADability/Text.cs +++ b/CADability/Text.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Drawing.Drawing2D; using System.Linq; using System.Runtime.Serialization; using System.Threading; @@ -36,15 +35,7 @@ public ShowPropertyFont(Text t) private MultipleChoiceProperty InitFontList() { - FontFamily[] families = FontFamily.Families; - string[] choices = new string[families.Length]; - for (int i = 0; i < families.Length; i++) - { - choices[i] = families[i].Name; - int emS = families[i].GetEmHeight(FontStyle.Regular); - int asc = families[i].GetCellAscent(FontStyle.Regular); - int desc = families[i].GetCellDescent(FontStyle.Regular); - } + string[] choices = FontProvider.Current.GetFamilyNames().OrderBy(n => n, StringComparer.CurrentCultureIgnoreCase).ToArray(); MultipleChoiceProperty res = new MultipleChoiceProperty("Text.Font", choices, text.Font); res.ValueChangedEvent += FontChanged; return res; @@ -404,9 +395,8 @@ string IGeoObjectShowProperty.GetContextMenuId() internal class FontCache { - // Holds CompoundShapes for all characters requested so far - // and a kerning table for all fonts used so far - // Maintains a DeviceContext throughout its lifetime, where only one font is switched. + // Holds CompoundShapes for all characters requested so far + // and the font faces used so far, which come from FontProvider.Current (issue #412: no GDI here). // The unit of the font is size 1.0, allowing easy work without scaling. // However, the faces are only generated when a string is created. // It might also be possible to keep a list of faces for each character in different sizes, @@ -418,8 +408,8 @@ struct DictKey { public readonly string fontName; public readonly int fontStyle; - public readonly char c; - public DictKey(string fontName, int fontStyle, char c) + public readonly int c; // a Unicode code point + public DictKey(string fontName, int fontStyle, int c) { this.fontName = fontName; this.fontStyle = fontStyle; @@ -427,7 +417,7 @@ public DictKey(string fontName, int fontStyle, char c) } public override int GetHashCode() { - return fontName.GetHashCode() ^ fontStyle ^ (int)c; + return fontName.GetHashCode() ^ fontStyle ^ c; } public override bool Equals(object obj) { @@ -462,28 +452,6 @@ public override bool Equals(object obj) return false; } } - struct KerningKey - { - public readonly string fontName; - public readonly int fontStyle; - public KerningKey(string fontName, int fontStyle) - { - this.fontName = fontName; - this.fontStyle = fontStyle; - } - public override int GetHashCode() - { - return fontName.GetHashCode() ^ fontStyle; - } - public override bool Equals(object obj) - { - if (obj is KerningKey other) - { - return other.fontName == fontName && other.fontStyle == fontStyle; - } - return false; - } - } struct DictVal { public CompoundShape shape; // hat die Splines statt der Polygone @@ -508,8 +476,7 @@ struct FontCharacteristics readonly Dictionary cache; // Cache von CompundShapes und Breiten für einzelne zeichen readonly Dictionary centerLineCache; // Cache für die CenterLines, Größe 1 readonly Dictionary fontCharacteristicsCache; // Cache für die FontCharacteristics - readonly Dictionary> kerning; // Kerningtabellen - readonly IntPtr hDC; // fester DeviceContext (für die ganze Lebensdauer) + readonly Dictionary faces; // the faces of FontProvider.Current, per font name and bold/italic public FontCache() { FontPrecision = Settings.GlobalSettings.GetIntValue("Font.Precision", 1); // |grob|mittel|fein @@ -517,76 +484,58 @@ public FontCache() cache = new Dictionary(); centerLineCache = new Dictionary(); fontCharacteristicsCache = new Dictionary(); - kerning = new Dictionary>(); - hDC = Gdi.CreateCompatibleDC(IntPtr.Zero); + faces = new Dictionary(); } - ~FontCache() + /// + /// Returns the face of for a font name and a font style (the flags of + /// System.Drawing.FontStyle: 1 bold, 2 italic, 4 underline, 8 strikeout). An unknown font name gives the default face of + /// the provider. + /// + /// when the provider has no font at all + internal FontFace GetFace(string fontName, int fontStyle) { - // the constructor throws where there is no GDI (e.g. on Linux), and an exception in a finalizer ends the process - if (hDC != IntPtr.Zero) Gdi.DeleteDC(hDC); + bool bold = (fontStyle & 1) != 0, italic = (fontStyle & 2) != 0; + FontKey key = new FontKey(fontName ?? "", fontStyle & 3); + lock (faces) + { + if (faces.TryGetValue(key, out FontFace face)) return face; + FontProvider provider = FontProvider.Current; + face = provider.GetFace(fontName, bold, italic) ?? provider.GetDefaultFace(bold, italic); + if (face == null) throw new InvalidOperationException("No font is available for texts. Install TrueType fonts, add font files to a ManagedFontProvider or set FontProvider.Current."); + faces[key] = face; + return face; + } } - // The caches and the device context are shared by all threads that measure or paint text, and Clear may empty the - // caches at any time: every access goes through lock (this). The lock is reentrant, Get and GetCenterLines call GetOutline2D. - public Path2D[] GetOutline2D(string fontName, int fontStyle, char c, out double width) + /// + /// Like , but returns null instead of throwing when there is no font at all: painting and the + /// extent of a text leave the text out then, instead of failing. + /// + internal FontFace TryGetFace(string fontName, int fontStyle) + { + try + { + return GetFace(fontName, fontStyle); + } + catch (InvalidOperationException) + { + return null; + } + } + // The caches are shared by all threads that measure or paint text, and Clear may empty them at any time: every access + // goes through lock (this). The lock is reentrant, Get and GetCenterLines call GetOutline2D. The faces are locked + // separately and always after this, so the two locks are taken in the same order everywhere. + public Path2D[] GetOutline2D(string fontName, int fontStyle, int c, out double width) { lock (this) { - return ReadOutline2D(fontName, fontStyle, c, out width); - } - } - private Path2D[] ReadOutline2D(string fontName, int fontStyle, char c, out double width) - { - using GraphicsPath path = new GraphicsPath(); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = Text.FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - StringFormat sf = StringFormat.GenericTypographic.Clone() as StringFormat; - sf.LineAlignment = StringAlignment.Near; - sf.Alignment = StringAlignment.Near; - if (!ff.IsStyleAvailable((FontStyle)fontStyle)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fontStyle = (int)FontStyle.Regular; - if (ff.IsStyleAvailable(FontStyle.Bold)) fontStyle = (int)FontStyle.Bold; - if (ff.IsStyleAvailable(FontStyle.Italic)) fontStyle = (int)FontStyle.Italic; - if (ff.IsStyleAvailable(FontStyle.Strikeout)) fontStyle = (int)FontStyle.Strikeout; - if (ff.IsStyleAvailable(FontStyle.Underline)) fontStyle = (int)FontStyle.Underline; - } - int fs = fontStyle; - int em = ff.GetEmHeight((FontStyle)fs); - if (em == 0) em = 1000; - using Font font = new Font(ff, em, (FontStyle)fs, GraphicsUnit.Pixel); - IntPtr hfont = font.ToHfont(); - IntPtr oldfont = Gdi.SelectObject(hDC, hfont); - Gdi.ABC[] abc = new Gdi.ABC[1]; - Gdi.GetCharABCWidths(hDC, (uint)c, (uint)c, abc); - //int[] widths = new int[1]; // liefert das selbe wie GetCharABCWidths, nur die Summe halt. - //Gdi.GetCharWidth32(hDC, (uint)c, (uint)c, widths); - if (!kerning.ContainsKey(new KerningKey(fontName, fontStyle))) - { - KerningKey kk = new KerningKey(fontName, fontStyle); - Dictionary<(char Left, char Right), double> pairs = new Dictionary<(char Left, char Right), double>(); - kerning[kk] = pairs; - int num = Gdi.GetKerningPairs(hDC, 0, null); - if (num > 0) - { - Gdi.KERNINGPAIR[] kp = new Gdi.KERNINGPAIR[num]; - int ok = Gdi.GetKerningPairs(hDC, num, kp); - for (int i = 0; i < kp.Length; ++i) - { - pairs[((char)kp[i].wFirst, (char)kp[i].wSecond)] = kp[i].iKernAmount / (double)em; - } - } + FontFace face = GetFace(fontName, fontStyle); + width = face.GetAdvanceWidth(c); + face.GetGlyphPath(c, (fontStyle & 4) != 0, (fontStyle & 8) != 0, out PointF[] points, out byte[] types); + if (points.Length == 0) return new Path2D[0]; + return GlyphShapeBuilder.PathDataToPath2D(points, types, FontPrecision); } - Gdi.SelectObject(hDC, oldfont); - Gdi.DeleteObject(hfont); - - width = (abc[0].abcA + abc[0].abcB + abc[0].abcC) / (double)em; - - path.AddString(c.ToString(), ff, fs, 1.0f, new PointF(0.0f, 0.0f), sf); - if (path.PointCount == 0) return new Path2D[0]; - return GlyphShapeBuilder.PathDataToPath2D(path.PathPoints, path.PathTypes, FontPrecision); } - public CompoundShape Get(string font, int fontStyle, char c, out double width) + public CompoundShape Get(string font, int fontStyle, int c, out double width) { // soll verschwinden!! DictVal found; lock (this) @@ -605,20 +554,14 @@ public CompoundShape Get(string font, int fontStyle, char c, out double width) width = found.width; return found.shape; } - public double GetKerning(string font, int fontStyle, char a, char b) + public double GetKerning(string font, int fontStyle, int a, int b) { - Dictionary<(char Left, char Right), double> pairs; - double res = 0.0; lock (this) { - if (kerning.TryGetValue(new KerningKey(font, fontStyle), out pairs)) - { - pairs.TryGetValue((a, b), out res); - } + return GetFace(font, fontStyle).GetKerning(a, b); } - return res; } - public IPaintTo3DList Get(string font, int fontStyle, char c, out double width, IPaintTo3D paintTo3D) + public IPaintTo3DList Get(string font, int fontStyle, int c, out double width, IPaintTo3D paintTo3D) { bool useLists = true; if (paintTo3D != null) useLists = !paintTo3D.IsBitmap; @@ -811,17 +754,17 @@ public void Clear() cache.Clear(); centerLineCache.Clear(); fontCharacteristicsCache.Clear(); - kerning.Clear(); + lock (faces) faces.Clear(); } } - public GeoObjectList GetCenterLines(string font, int fontStyle, char c, out double width) + public GeoObjectList GetCenterLines(string font, int fontStyle, int c, out double width) { lock (this) { return ReadCenterLines(font, fontStyle, c, out width); } } - private GeoObjectList ReadCenterLines(string font, int fontStyle, char c, out double width) + private GeoObjectList ReadCenterLines(string font, int fontStyle, int c, out double width) { CenterLineVal found; if (!centerLineCache.TryGetValue(new DictKey(font, fontStyle, c), out found)) @@ -996,9 +939,16 @@ public enum LineAlignMode private LineAlignMode lineAlignment; // private IGeoObject[] cachedDisplayItems; private static HashSet fontFamilyNames; + /// + /// The upper case names of the font families of . + /// internal static HashSet FontFamilyNames => LazyInitializer.EnsureInitialized(ref fontFamilyNames, () => - new HashSet(FontFamily.Families.Select(f => f.Name.ToUpper()))); + new HashSet(FontProvider.Current.GetFamilyNames().Select(f => f.ToUpper()))); + internal static void ResetFontFamilyNames() + { + fontFamilyNames = null; + } #region polymorph construction /// @@ -1057,37 +1007,29 @@ private void calcExtent() { try { - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - FontStyle fs = fontStyle; - if (!ff.IsStyleAvailable(fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - int em = ff.GetEmHeight(fs); - int asc = ff.GetCellAscent(fs); - int desc = ff.GetCellDescent(fs); - int lsp = ff.GetLineSpacing(fs); - double dbg = (double)(asc + desc) / (double)lsp; + int fs = (int)fontStyle; + FontFace ff = FontCache.GlobalFontCache.GetFace(fontName, fs); // FontProvider.Current, see issue #412 + int em = ff.EmHeight; + int asc = ff.CellAscent; + int desc = ff.CellDescent; + int lsp = ff.LineSpacing; // asc+desc kann mehr als em sein. ich nehme jedoch an, dass em für die Größe ausschlaggebend ist // wenn man em als 1 annimmt, dann muss man um desc/em nach unten verschieben um auf die Grundlinie zu kommen emSizeFactor = (double)(asc + desc) / (double)em; emAscDescFactor = (double)(desc) / (double)asc; emDescDiff = (double)(em - asc) / (double)em; this.desc = (double)(desc) / (double)em; - // the bitmap only serves as a device for measuring; it must be disposed as well, otherwise - // 400 KB of unmanaged GDI+ memory stay alive per measured text until the finalizer runs - using (Bitmap measureBitmap = new Bitmap(1000, 100)) - using (Graphics graphics = Graphics.FromImage(measureBitmap)) - using (Font font = new Font(fontName, (float)(glyphDirection.Length), GraphicsUnit.Pixel)) + // the size of the text string with the font size glyphDirection.Length: the advance widths and the kerning + // of the characters and the line spacing (this used to be Graphics.MeasureString of GDI+) + double width = 0.0; + int[] chars = CodePoints(textString); + for (int i = 0; i < chars.Length; ++i) { - sizeExtent = graphics.MeasureString(textString, font); + width += ff.GetAdvanceWidth(chars[i]); + if (i > 0) width += ff.GetKerning(chars[i - 1], chars[i]); } + double size = glyphDirection.Length; + sizeExtent = new SizeF((float)(width * size), (float)(size * lsp / em)); isValidExtent = true; } catch (Exception e) @@ -1223,6 +1165,24 @@ private void AddToPath2D(List addto, List points, bool spl } points.RemoveRange(0, points.Count - 1); // den letzten als ersten drinlassen } + /// + /// The characters of as Unicode code points: a surrogate pair (e.g. an emoji) is one character. + /// A lone surrogate is kept as the char it is. + /// + internal static int[] CodePoints(string s) + { + List res = new List(s.Length); + for (int i = 0; i < s.Length; ++i) + { + if (char.IsHighSurrogate(s[i]) && i + 1 < s.Length && char.IsLowSurrogate(s[i + 1])) + { + res.Add(char.ConvertToUtf32(s[i], s[i + 1])); + ++i; + } + else res.Add(s[i]); + } + return res.ToArray(); + } // Returns the string to display: textString, with additional line breaks inserted at word // boundaries when ColumnWidth demands wrapping. Character widths are measured with the same // FontCache the layout loops use, so the wrapped lines fit the column exactly. @@ -1255,9 +1215,10 @@ private string GetDisplayString(int fs) } private void AppendWrappedParagraph(System.Text.StringBuilder result, string paragraph, double normalizedWidth, int fs) { - double MeasureWord(string w) + double MeasureWord(string word) { double total = 0.0; + int[] w = CodePoints(word); for (int i = 0; i < w.Length; i++) { FontCache.GlobalFontCache.Get(fontName, fs, w[i], out double cw, null); @@ -1306,22 +1267,12 @@ public virtual GeoObjectList GetOutline() if (Bold) fs |= (int)FontStyle.Bold; if (Strikeout) fs |= (int)FontStyle.Strikeout; PlaneSurface pls = new PlaneSurface(location, lineDirection, glyphDirection, lineDirection ^ glyphDirection); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - string txt = GetDisplayString(fs); - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); + FontFace ff = FontCache.GlobalFontCache.GetFace(fontName, fs); // FontProvider.Current, see issue #412 + int[] txt = CodePoints(GetDisplayString(fs)); + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; double lsp = ls / em; if (lineSpacing > 0.0) lsp = lineSpacing; // explicit baseline distance as factor of the text size double dx = 0.0; @@ -1463,22 +1414,12 @@ public CompoundShape[] GetShapes() if (Bold) fs |= (int)FontStyle.Bold; if (Strikeout) fs |= (int)FontStyle.Strikeout; PlaneSurface pls = new PlaneSurface(location, lineDirection, glyphDirection, lineDirection ^ glyphDirection); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - string txt = GetDisplayString(fs); - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); + FontFace ff = FontCache.GlobalFontCache.GetFace(fontName, fs); // FontProvider.Current, see issue #412 + int[] txt = CodePoints(GetDisplayString(fs)); + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; double lsp = ls / em; if (lineSpacing > 0.0) lsp = lineSpacing; // explicit baseline distance as factor of the text size double dx = 0.0; @@ -1606,22 +1547,12 @@ public virtual GeoObjectList GetCenterline() if (Bold) fs |= (int)FontStyle.Bold; if (Strikeout) fs |= (int)FontStyle.Strikeout; PlaneSurface pls = new PlaneSurface(location, lineDirection, glyphDirection, lineDirection ^ glyphDirection); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - string txt = GetDisplayString(fs); - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); + FontFace ff = FontCache.GlobalFontCache.GetFace(fontName, fs); // FontProvider.Current, see issue #412 + int[] txt = CodePoints(GetDisplayString(fs)); + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; double lsp = ls / em; if (lineSpacing > 0.0) lsp = lineSpacing; // explicit baseline distance as factor of the text size double dx = 0.0; @@ -2010,22 +1941,13 @@ private void CalcExtent() { // bei sehr kleinen lineDirection u.s.w. gibts hier ein Problem pls = new PlaneSurface(location, GeoVector.XAxis, GeoVector.YAxis, GeoVector.ZAxis); } - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - string txt = GetDisplayString(fs); - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); + FontFace ff = FontCache.GlobalFontCache.TryGetFace(fontName, fs); // FontProvider.Current, see issue #412 + if (ff == null) return; // no font at all: nothing to paint or measure + int[] txt = CodePoints(GetDisplayString(fs)); + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; double lsp; if (em > 0) lsp = (double)ls / em; else lsp = 1.0; @@ -2650,25 +2572,15 @@ public override void PrePaintTo3D(IPaintTo3D paintTo3D) if (Strikeout) fs |= (int)FontStyle.Strikeout; List res = new List(); PlaneSurface pls = new PlaneSurface(location, lineDirection, glyphDirection, lineDirection ^ glyphDirection); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // there must be one - } - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); - for (int i = 0; i < textString.Length; ++i) + FontFace ff = FontCache.GlobalFontCache.TryGetFace(fontName, fs); // FontProvider.Current, see issue #412 + if (ff == null) return; // no font at all: nothing to paint or measure + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; + foreach (int c in CodePoints(textString)) { - double width; - FontCache.GlobalFontCache.Get(fontName, fs, textString[i], out width, paintTo3D); + FontCache.GlobalFontCache.Get(fontName, fs, c, out double width, paintTo3D); } } else @@ -2719,22 +2631,13 @@ public override void PaintTo3D(IPaintTo3D paintTo3D) if (Strikeout) fs |= (int)FontStyle.Strikeout; List res = new List(); PlaneSurface pls = new PlaneSurface(location, lineDirection, glyphDirection, lineDirection ^ glyphDirection); - // FontFamily wraps an unmanaged GDI+ object: dispose it at the end of the block instead of - // leaving it to the finalizer (this code runs for every text on every repaint) - using FontFamily ff = FontFamilyNames.Contains(fontName.ToUpper()) ? new FontFamily(fontName) : new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); - if (!ff.IsStyleAvailable((FontStyle)fs)) - { - if (ff.IsStyleAvailable(FontStyle.Regular)) fs = (int)FontStyle.Regular; - else if (ff.IsStyleAvailable(FontStyle.Bold)) fs = (int)FontStyle.Bold; - else if (ff.IsStyleAvailable(FontStyle.Italic)) fs = (int)FontStyle.Italic; - else if (ff.IsStyleAvailable(FontStyle.Strikeout)) fs = (int)FontStyle.Strikeout; - else if (ff.IsStyleAvailable(FontStyle.Underline)) fs = (int)FontStyle.Underline; // irgend einen muss es ja wohl geben, oder? - } - string txt = GetDisplayString(fs); - int em = ff.GetEmHeight((FontStyle)fs); - int ls = ff.GetLineSpacing((FontStyle)fs); - int dc = ff.GetCellDescent((FontStyle)fs); - int ac = ff.GetCellAscent((FontStyle)fs); + FontFace ff = FontCache.GlobalFontCache.TryGetFace(fontName, fs); // FontProvider.Current, see issue #412 + if (ff == null) return; // no font at all: nothing to paint or measure + int[] txt = CodePoints(GetDisplayString(fs)); + int em = ff.EmHeight; + int ls = ff.LineSpacing; + int dc = ff.CellDescent; + int ac = ff.CellAscent; double lsp = ls / em; if (lineSpacing > 0.0) lsp = lineSpacing; // explicit baseline distance as factor of the text size if (lineSpacing > 0.0) lsp = lineSpacing; // explicit baseline distance as factor of the text size diff --git a/CADability/TrueTypeFont.cs b/CADability/TrueTypeFont.cs new file mode 100644 index 000000000..9b470310b --- /dev/null +++ b/CADability/TrueTypeFont.cs @@ -0,0 +1,1121 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; + +namespace CADability +{ + /// + /// A point of a TrueType glyph contour in font units. Off-curve points are the control points of quadratic Bezier + /// segments; between two consecutive off-curve points there is an implied on-curve point in the middle. + /// + internal struct GlyphPoint + { + public double X, Y; + public bool OnCurve; + public GlyphPoint(double x, double y, bool onCurve) + { + X = x; + Y = y; + OnCurve = 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 (most .otf files) are read with : their cubic curves go into + /// the path data unchanged. + /// + internal sealed class TrueTypeFont + { + private readonly byte[] data; + private readonly Dictionary tables = new Dictionary(); + private readonly int numGlyphs; + private readonly int numberOfHMetrics; + private readonly bool longLoca; + private int cmapOffset = -1, cmapFormat, symbolOffset; + private Dictionary kerningPairs; // (left glyph << 16 | right glyph) -> value in font units, created on demand + private readonly int fontOffset; // of the table directory, a font of a collection does not start at 0 + private Dictionary names; // name ID -> name, the preferred record per ID + private readonly FontVariations variations; // null for a font that is not variable + private double[] coords; // normalized variation coordinates, null for the default instance + + /// + /// The family name, e.g. "Liberation Sans", as GDI knows it (name ID 1, Windows platform preferred). + /// + public string FamilyName { get; } + /// + /// The typographic family name (name ID 16) when the font has one, otherwise . + /// + public string TypographicFamilyName { get; } + /// + /// The subfamily (style) name, e.g. "Regular" or "Bold Italic" (name ID 2). + /// + public string SubfamilyName { get; } + /// + /// The full name, e.g. "Liberation Sans Bold" (name ID 4). + /// + public string FullName { get; } + public bool IsBold { get; } + public bool IsItalic { get; } + /// + /// The weight of the face, usWeightClass of the "OS/2" table (400 regular, 700 bold); for an instance of a variable font + /// the value of its "wght" axis. Without an "OS/2" table 700 for a bold face and 400 otherwise. + /// + public int WeightClass { get; private set; } + public int UnitsPerEm { get; } + public int Ascender { get; } + public int Descender { get; } + public int LineGap { get; } + public int WinAscent { get; private set; } + public int WinDescent { get; private set; } + /// + /// The typographic ascender, descender (negative) and line gap of the "OS/2" table. + /// + public int TypoAscender { get; private set; } + public int TypoDescender { get; private set; } + public int TypoLineGap { get; private set; } + /// + /// True if the font asks for its typographic metrics to be used for the line layout (USE_TYPO_METRICS, bit 7 of + /// fsSelection in "OS/2"), as GDI+ does: then they are the cell and the line spacing instead of usWinAscent and + /// usWinDescent. Bahnschrift, Cascadia, Sitka and Cambria Math are such fonts. + /// + public bool UseTypoMetrics { get; } + /// + /// The middle of the underline above the baseline in font units (negative: below), from the "post" table. GDI+ centers + /// the underline on it. + /// + public int UnderlinePosition { get; private set; } + public int UnderlineThickness { get; private set; } + /// + /// The middle of the strikeout line above the baseline in font units, from the "OS/2" table. GDI+ centers the + /// strikeout line on it. + /// + public int StrikeoutPosition { get; private set; } + public int StrikeoutSize { get; private set; } + /// + /// True for a font read by : it knows its names, style and metrics, but has no glyphs. + /// + public bool IsHeaderOnly { get; private set; } + /// + /// 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 " or "CFF2" table (Compact Font Format, cubic curves). + /// + public bool HasCffOutlines { get; } + /// + /// True if the CFF outlines are in the "CFF2" table, the format of variable OpenType fonts. + /// + public bool IsCff2 { get; } + /// + /// True if the glyph outlines can be read: TrueType, CFF or CFF2 outlines. False for bitmap fonts. + /// + public bool HasOutlines => HasTrueTypeOutlines || HasCffOutlines; + private CffOutlines cff; // created on demand + public int GlyphCount => numGlyphs; + + /// + /// The ascent of the character cell in font units, as GDI+ uses it: usWinAscent, or the typographic ascender when + /// is set. + /// + public int CellAscent => UseTypoMetrics ? TypoAscender : WinAscent; + /// + /// The descent of the character cell in font units, positive downwards: usWinDescent, or the typographic descender + /// when is set. + /// + public int CellDescent => UseTypoMetrics ? -TypoDescender : WinDescent; + /// + /// The distance between two lines in font units, computed like GDI+ does. With it is the + /// typographic ascender, descender and line gap. Otherwise it is the cell height plus the external leading, which is + /// the part of the line gap of the "hhea" table that is not already part of the cell. + /// + public int LineSpacing + { + get + { + if (UseTypoMetrics) return TypoAscender - TypoDescender + TypoLineGap; + int cell = WinAscent + WinDescent; + int external = Math.Max(0, LineGap - (cell - (Ascender - Descender))); + return cell + external; + } + } + + private TrueTypeFont(byte[] data, int offset) + { + this.data = data; + fontOffset = offset; + uint version = U32(offset); + bool trueType = version == 0x00010000 || version == 0x74727565; // 'true' (old Apple fonts) + if (!trueType && version != 0x4F54544F) throw new InvalidDataException("not a TrueType or OpenType font"); // 'OTTO' + int numTables = U16(offset + 4); + for (int i = 0; i < numTables; ++i) + { + int rec = offset + 12 + 16 * i; + string tag = Encoding.ASCII.GetString(data, rec, 4); + int tableOffset = (int)U32(rec + 8); + int length = (int)U32(rec + 12); + if (tableOffset < 0 || length < 0 || tableOffset + (long)length > data.Length) throw new InvalidDataException("table " + tag + " exceeds the file"); + tables[tag] = (tableOffset, length); + } + // 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 ") || tables.ContainsKey("CFF2")); + IsCff2 = !trueType && !tables.ContainsKey("CFF ") && tables.ContainsKey("CFF2"); + int head = Table("head"); + UnitsPerEm = U16(head + 18); + if (UnitsPerEm == 0) UnitsPerEm = 1000; + int macStyle = U16(head + 44); + longLoca = S16(head + 50) != 0; + numGlyphs = U16(Table("maxp") + 4); + int hhea = Table("hhea"); + Ascender = S16(hhea + 4); + Descender = S16(hhea + 6); + LineGap = S16(hhea + 8); + numberOfHMetrics = U16(hhea + 34); + bool bold = (macStyle & 1) != 0, italic = (macStyle & 2) != 0; + WinAscent = Ascender; + WinDescent = -Descender; + // defaults like the ones GDI uses when the tables are missing + UnderlineThickness = Math.Max(1, UnitsPerEm / 14); + UnderlinePosition = -UnitsPerEm / 10; + StrikeoutSize = UnderlineThickness; + StrikeoutPosition = UnitsPerEm / 4; + if (tables.TryGetValue("OS/2", out var os2) && os2.length >= 78) + { + WeightClass = U16(os2.offset + 4); + StrikeoutSize = S16(os2.offset + 26); + StrikeoutPosition = S16(os2.offset + 28); + int fsSelection = U16(os2.offset + 62); + italic = (fsSelection & 1) != 0; + bold = (fsSelection & 0x20) != 0; + TypoAscender = S16(os2.offset + 68); + TypoDescender = S16(os2.offset + 70); + TypoLineGap = S16(os2.offset + 72); + UseTypoMetrics = (fsSelection & 0x80) != 0 && TypoAscender - TypoDescender > 0; + WinAscent = U16(os2.offset + 74); + WinDescent = U16(os2.offset + 76); + } + if (tables.TryGetValue("post", out var post) && post.length >= 12) + { + UnderlinePosition = S16(post.offset + 8); + UnderlineThickness = S16(post.offset + 10); + } + IsBold = bold; + IsItalic = italic; + if (WeightClass == 0) WeightClass = bold ? 700 : 400; + ReadNames(); + FamilyName = NameOf(1) ?? ""; + SubfamilyName = NameOf(2) ?? ""; + FullName = NameOf(4) ?? FamilyName; + TypographicFamilyName = NameOf(16) ?? FamilyName; + SelectCmap(); + if (tables.ContainsKey("fvar") && (HasTrueTypeOutlines || HasCffOutlines)) variations = new FontVariations(data, tables, NameOf); + } + + /// + /// The name with the name ID (Windows US English preferred), null if the font has none. + /// + public string NameOf(int nameId) + { + return names != null && names.TryGetValue(nameId, out string res) ? res : null; + } + /// + /// True for a variable font ("fvar"). are its predefined instances, + /// makes an instance at any coordinates. + /// + public bool IsVariable => variations != null; + /// + /// The axes of a variable font, empty for other fonts. + /// + public VariationAxis[] Axes => variations?.Axes ?? new VariationAxis[0]; + /// + /// The named instances of a variable font, e.g. "Light Condensed", empty for other fonts. + /// + public NamedInstance[] NamedInstances => variations?.Instances ?? new NamedInstance[0]; + /// + /// The name of a named instance as Windows makes it from the style attributes ("STAT"), e.g. "Display Light" for an + /// instance of Segoe UI Variable whose subfamily name in "fvar" is "Light Display". The subfamily name of "fvar" when + /// the font has no usable "STAT" table. + /// + public string InstanceName(NamedInstance instance) => variations?.StatInstanceName(instance.Coordinates) ?? instance.SubfamilyName; + /// + /// The normalized coordinates of this instance, null for the default instance. + /// + public double[] NormalizedCoordinates => coords == null ? null : (double[])coords.Clone(); + /// + /// Returns this variable font at the coordinates (in the units of the axes, e.g. wght + /// 300, one value per axis of ): its glyph outlines and advance widths are varied. The metrics of the + /// whole font ("MVAR") stay those of the default instance. + /// + public TrueTypeFont WithVariation(double[] userCoordinates) + { + if (variations == null) throw new InvalidOperationException("The font \"" + FullName + "\" is not a variable font"); + TrueTypeFont res = new TrueTypeFont(data, fontOffset); + res.IsHeaderOnly = IsHeaderOnly; + double[] normalized = res.variations.Normalize(userCoordinates); + res.coords = normalized.Any(c => c != 0.0) ? normalized : null; + VariationAxis[] axes = res.variations.Axes; + for (int i = 0; i < axes.Length && i < userCoordinates.Length; ++i) + if (axes[i].Tag == "wght") res.WeightClass = (int)Math.Round(userCoordinates[i]); + if (res.coords != null) + { // the font wide metrics of the instance ("MVAR"), rounded like the values of the tables + int Varied(int value, string tag) => (int)Math.Floor(value + res.variations.GetMetricDelta(tag, res.coords) + 0.5); + res.TypoAscender = Varied(res.TypoAscender, "hasc"); + res.TypoDescender = Varied(res.TypoDescender, "hdsc"); + res.TypoLineGap = Varied(res.TypoLineGap, "hlgp"); + res.WinAscent = Varied(res.WinAscent, "hcla"); + res.WinDescent = Varied(res.WinDescent, "hcld"); + res.UnderlinePosition = Varied(res.UnderlinePosition, "undo"); + res.UnderlineThickness = Varied(res.UnderlineThickness, "unds"); + res.StrikeoutPosition = Varied(res.StrikeoutPosition, "stro"); + res.StrikeoutSize = Varied(res.StrikeoutSize, "strs"); + } + return res; + } + + /// + /// Reads all fonts of a font file: one for a .ttf or .otf file, all fonts of a .ttc or .otc collection. + /// + /// The content of the file + /// The fonts + public static TrueTypeFont[] Load(byte[] data) + { + if (data == null || data.Length < 12) throw new InvalidDataException("not a font file"); + if (data[0] == 't' && data[1] == 't' && data[2] == 'c' && data[3] == 'f') + { + int count = (int)ReadU32(data, 8); + if (count <= 0 || 12 + 4L * count > data.Length) throw new InvalidDataException("invalid font collection"); + TrueTypeFont[] res = new TrueTypeFont[count]; + for (int i = 0; i < count; ++i) res[i] = new TrueTypeFont(data, (int)ReadU32(data, 12 + 4 * i)); + return res; + } + return new TrueTypeFont[] { new TrueTypeFont(data, 0) }; + } + /// + /// Reads the names, styles and metrics of all fonts of a font file without reading the whole file: only the tables + /// "head", "hhea", "maxp", "OS/2", "post" and "name" are read. This is how a directory with hundreds of fonts is indexed + /// quickly. The fonts returned have set; load the file with to + /// get the glyphs. + /// + public static TrueTypeFont[] ReadHeaders(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + byte[] head = ReadBytes(stream, 0, 12); + if (head[0] == 't' && head[1] == 't' && head[2] == 'c' && head[3] == 'f') + { + int count = (int)ReadU32(head, 8); + if (count <= 0 || count > 10000) throw new InvalidDataException("invalid font collection"); + byte[] offsets = ReadBytes(stream, 12, 4 * count); + TrueTypeFont[] res = new TrueTypeFont[count]; + for (int i = 0; i < count; ++i) res[i] = ReadHeader(stream, ReadU32(offsets, 4 * i)); + return res; + } + return new TrueTypeFont[] { ReadHeader(stream, 0) }; + } + } + private static TrueTypeFont ReadHeader(FileStream stream, uint offset) + { + // a small font made of the needed tables only, with the same table directory layout, so that the constructor reads it + byte[] directory = ReadBytes(stream, offset, 12); + int numTables = (directory[4] << 8) | directory[5]; + if (numTables > 1000) throw new InvalidDataException("invalid table directory"); + byte[] records = ReadBytes(stream, offset + 12, 16 * numTables); + string[] needed = { "head", "hhea", "maxp", "OS/2", "post", "name", "fvar", "STAT" }; + List<(byte[] record, byte[] content)> copied = new List<(byte[], byte[])>(); + for (int i = 0; i < numTables; ++i) + { + string tag = Encoding.ASCII.GetString(records, 16 * i, 4); + byte[] record = new byte[16]; + Array.Copy(records, 16 * i, record, 0, 16); + if (Array.IndexOf(needed, tag) >= 0) + { + uint length = ReadU32(records, 16 * i + 12); + 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" || tag == "CFF " || tag == "CFF2") + { // only their presence matters (HasTrueTypeOutlines, HasCffOutlines) + for (int k = 8; k < 16; ++k) record[k] = 0; + copied.Add((record, new byte[0])); + } + } + int size = 12 + 16 * copied.Count; + foreach (var (_, content) in copied) size += content.Length; + byte[] data = new byte[size]; + Array.Copy(directory, data, 12); + data[4] = (byte)(copied.Count >> 8); + data[5] = (byte)copied.Count; + int pos = 12 + 16 * copied.Count; + for (int i = 0; i < copied.Count; ++i) + { + byte[] record = copied[i].record, content = copied[i].content; + if (content.Length > 0) + { + record[8] = (byte)(pos >> 24); record[9] = (byte)(pos >> 16); record[10] = (byte)(pos >> 8); record[11] = (byte)pos; + } + Array.Copy(record, 0, data, 12 + 16 * i, 16); + Array.Copy(content, 0, data, pos, content.Length); + pos += content.Length; + } + TrueTypeFont res = new TrueTypeFont(data, 0); + res.IsHeaderOnly = true; + return res; + } + private static byte[] ReadBytes(FileStream stream, long offset, int count) + { + if (count < 0 || offset + count > stream.Length) throw new InvalidDataException("unexpected end of the font file"); + byte[] res = new byte[count]; + stream.Seek(offset, SeekOrigin.Begin); + int read = 0; + while (read < count) + { + int n = stream.Read(res, read, count - read); + if (n <= 0) throw new InvalidDataException("unexpected end of the font file"); + read += n; + } + return res; + } + /// + /// Reads all fonts of the font file , see . + /// + public static TrueTypeFont[] Load(string fileName) + { + return Load(File.ReadAllBytes(fileName)); + } + + /// + /// Returns the glyph index of the character (a Unicode code point), 0 (the "missing + /// glyph") if the font has no glyph for it. + /// + public int GetGlyphIndex(int codePoint) + { + if (cmapOffset < 0) return 0; + int glyph = Lookup(codePoint); + if (glyph == 0 && symbolOffset != 0 && codePoint < 0x100) glyph = Lookup(codePoint + symbolOffset); + return glyph < numGlyphs ? glyph : 0; + } + /// + /// Returns the advance width of a glyph in font units. + /// + public double GetAdvanceWidth(int glyph) + { + int hmtx = Table("hmtx"); + if (numberOfHMetrics == 0) return 0; + int i = Math.Min(glyph, numberOfHMetrics - 1); + double advance = U16(hmtx + 4 * i); + if (coords != null) + { + double delta = variations.GetAdvanceDelta(glyph, coords); + if (double.IsNaN(delta)) delta = PhantomAdvanceDelta(glyph); // no HVAR: the phantom points of gvar + advance += delta; // fractional: the exact value of the instance + } + return advance; + } + private int LeftSideBearing(int glyph) + { + int hmtx = Table("hmtx"); + if (glyph < numberOfHMetrics) return S16(hmtx + 4 * glyph + 2); + return S16(hmtx + 4 * numberOfHMetrics + 2 * (glyph - numberOfHMetrics)); + } + /// + /// The change of the advance width from the deltas of the two horizontal phantom points in "gvar". + /// + private double PhantomAdvanceDelta(int glyph) + { + PhantomDeltas(glyph, out double left, out double right); + return right - left; + } + /// + /// The deltas of "gvar" for the two horizontal phantom points (the origin and the advance) of a glyph. + /// + private void PhantomDeltas(int glyph, out double left, out double right) + { + left = right = 0.0; + if (!HasTrueTypeOutlines || coords == null) return; + int g = GlyphOffset(glyph, out int length); + int pointCount = 0; + int xMin = 0; + if (length > 0) + { + int numberOfContours = S16(g); + xMin = S16(g + 2); + if (numberOfContours >= 0) pointCount = numberOfContours == 0 ? 0 : U16(g + 10 + 2 * (numberOfContours - 1)) + 1; + else pointCount = CompositeComponents(g).Count; + } + double[] x = new double[pointCount + 4], y = new double[pointCount + 4], dx = new double[pointCount + 4], dy = new double[pointCount + 4]; + x[pointCount] = xMin - LeftSideBearing(glyph); + variations.AddGlyphDeltas(glyph, x, y, null, coords, dx, dy); + left = dx[pointCount]; + right = dx[pointCount + 1]; + } + /// + /// The x coordinate of the origin of a glyph in the coordinates of "glyf": the first phantom point, xMin of the glyph + /// minus its left side bearing from "hmtx". It is 0 in almost every font. When it is not, the rasterizers of Windows + /// and FreeType (and fontTools) move the outline so that the origin is at 0, which puts the glyph at its left side + /// bearing. Times New Roman Italic and Symbol have such glyphs. A composite glyph with USE_MY_METRICS has the origin + /// of that component. + /// + private double HorizontalOrigin(int glyph, int depth = 0) + { + int g = GlyphOffset(glyph, out int length); + if (length == 0) return 0.0; // no outline to move + if (S16(g) < 0 && depth < 8) + { // a composite glyph with USE_MY_METRICS takes the metrics, and so the origin, of that component (Times New + // Roman Bold 'Ü' has another left side bearing than its 'U', and Windows draws it like the 'U') + foreach (Component component in CompositeComponents(g)) + if ((component.Flags & 0x200) != 0) return HorizontalOrigin(component.Glyph, depth + 1); + } + PhantomDeltas(glyph, out double left, out _); + return S16(g + 2) - LeftSideBearing(glyph) + left; + } + /// + /// Returns the kerning between two glyphs in font units from the "kern" table, 0 if there is none. Kerning that only + /// exists in the "GPOS" table is not used, which is what GDI does as well. + /// + public int GetKerning(int leftGlyph, int rightGlyph) + { + if (kerningPairs == null) kerningPairs = ReadKerning(); + kerningPairs.TryGetValue((leftGlyph << 16) | rightGlyph, out int res); + return res; + } + /// + /// All kerning pairs of the "kern" table: (left glyph, right glyph) to the value in font units. + /// + public IEnumerable> GetKerningPairs() + { + if (kerningPairs == null) kerningPairs = ReadKerning(); + foreach (KeyValuePair kv in kerningPairs) yield return new KeyValuePair<(int, int), int>((kv.Key >> 16, kv.Key & 0xFFFF), kv.Value); + } + + /// + /// Returns the contours of a glyph in font units (y upwards), with the components of a composite glyph resolved and the + /// origin of the glyph at x = 0 (see ). + /// + public List GetContours(int glyph) + { + if (!HasTrueTypeOutlines) throw new NotSupportedException("The outlines of the font \"" + FullName + "\" cannot be read: they are in CFF format or the font has bitmaps only"); + if (IsHeaderOnly) throw new InvalidOperationException("The font \"" + FullName + "\" was read by ReadHeaders and has no glyphs, load it with Load"); + List res = new List(); + AddContours(glyph, res, 0); + double origin = glyph >= 0 && glyph < numGlyphs ? HorizontalOrigin(glyph) : 0.0; + if (origin != 0.0) + { + foreach (GlyphPoint[] contour in res) + for (int i = 0; i < contour.Length; ++i) contour[i] = new GlyphPoint(contour[i].X - origin, contour[i].Y, contour[i].OnCurve); + } + return res; + } + + /// + /// Returns the outline of a glyph as path data in the form a GDI+ GraphicsPath has after AddString with an em size + /// of 1 and StringFormat.GenericTypographic: in units of the em, y downwards, the top of the character cell at 0 + /// (the baseline at /). The quadratic segments are converted to cubic Bezier + /// segments without any loss (degree elevation), as GDI+ does. The point types are those of GDI+ PathPointType: + /// 0 start, 1 line, 3 Bezier, 0x80 closes the figure. This is the input of GlyphShapeBuilder.PathDataToPath2D. + /// + public void GetPathData(int glyph, out PointF[] points, out byte[] types) + { + GetPathData(glyph, false, false, out points, out types); + } + /// + /// Like , and adds the underline and the strikeout line as + /// rectangles over the advance width of the glyph, as GDI+ does for an underlined or struck out font. + /// + public void GetPathData(int glyph, bool underline, bool strikeout, out PointF[] points, out byte[] types) + { + GetPathData(glyph, underline, strikeout, 0.0, 0.0, out points, out types); + } + /// + /// Like , with the strokes made wider by + /// and higher by font units for a synthetic bold face (see + /// ). The quadratic outline of TrueType is made wider, before its + /// segments become cubic ones, as GDI+ does it; a CFF outline is made wider after it was drawn. The underline and the + /// strikeout line are not made wider. + /// + internal void GetPathData(int glyph, bool underline, bool strikeout, double boldX, double boldY, out PointF[] points, out byte[] types) + { + List pts = new List(); + List tps = new List(); + double scale = 1.0 / UnitsPerEm; + PointF Map(double x, double y) => new PointF((float)(x * scale), (float)((CellAscent - y) * scale)); + bool bold = boldX > 0 || boldY > 0; + if (HasCffOutlines) + { + DrawCff(glyph, new PathDataSink(pts, tps, Map)); + if (bold && pts.Count > 0) + { + PointF[] wider = SyntheticFontFace.Embolden(pts.ToArray(), tps.ToArray(), boldX * scale, boldY * scale); + pts.Clear(); + pts.AddRange(wider); + } + } + else foreach (GlyphPoint[] contour in Emboldened(GetContours(glyph), boldX, boldY)) + { + if (contour.Length < 2) continue; + int n = contour.Length; + // the start point: the first on-curve point, or the middle between the last and the first point + int first = -1; + for (int i = 0; i < n; ++i) if (contour[i].OnCurve) { first = i; break; } + double sx, sy; + if (first >= 0) + { + sx = contour[first].X; + sy = contour[first].Y; + } + else + { + first = n - 1; // all points are off-curve: start in the middle of the last and the first point + sx = (contour[n - 1].X + contour[0].X) / 2; + sy = (contour[n - 1].Y + contour[0].Y) / 2; + } + int startIndex = pts.Count; + pts.Add(Map(sx, sy)); + tps.Add(0); + double cx = sx, cy = sy; // current point + for (int k = 1; k <= n; ++k) + { + GlyphPoint p = contour[(first + k) % n]; + if (p.OnCurve) + { + if (k == n) break; // the closing line is implied by the closing flag + if (p.X == cx && p.Y == cy) continue; + pts.Add(Map(p.X, p.Y)); + tps.Add(1); + cx = p.X; + cy = p.Y; + } + else + { + GlyphPoint next = contour[(first + k + 1) % n]; + double ex, ey; + if (k == n) + { // the start point itself was implied (all points off-curve), the last curve ends there + ex = sx; + ey = sy; + } + else if (next.OnCurve) + { + ex = next.X; + ey = next.Y; + ++k; + } + else + { + ex = (p.X + next.X) / 2; + ey = (p.Y + next.Y) / 2; + } + if (!(p.X == cx && p.Y == cy && ex == cx && ey == cy)) + { + // degree elevation of the quadratic segment (c, p, e) to a cubic one + pts.Add(Map(cx + 2.0 / 3.0 * (p.X - cx), cy + 2.0 / 3.0 * (p.Y - cy))); + pts.Add(Map(ex + 2.0 / 3.0 * (p.X - ex), ey + 2.0 / 3.0 * (p.Y - ey))); + pts.Add(Map(ex, ey)); + tps.Add(3); + tps.Add(3); + tps.Add(3); + } + cx = ex; + cy = ey; + } + } + if (pts.Count - startIndex < 2) + { // nothing but the start point + pts.RemoveAt(startIndex); + tps.RemoveAt(startIndex); + continue; + } + tps[tps.Count - 1] |= 0x80; + } + void AddBar(int middle, int thickness) + { // centered on the position, as GDI+ does + if (thickness <= 0) return; + double width = GetAdvanceWidth(glyph); + double top = middle + thickness / 2.0; + pts.Add(Map(0, top)); tps.Add(0); + pts.Add(Map(width, top)); tps.Add(1); + pts.Add(Map(width, top - thickness)); tps.Add(1); + pts.Add(Map(0, top - thickness)); tps.Add(0x81); + } + if (underline) AddBar(UnderlinePosition, UnderlineThickness); + if (strikeout) AddBar(StrikeoutPosition, StrikeoutSize); + points = pts.ToArray(); + types = tps.ToArray(); + } + + // the contours made wider for a synthetic bold face, keeping which points are on the curve + private static List Emboldened(List contours, double boldX, double boldY) + { + if (boldX <= 0 && boldY <= 0) return contours; + List<(double x, double y)[]> xy = contours.Select(c => c.Select(p => (p.X, p.Y)).ToArray()).ToList(); + List<(double x, double y)[]> wider = SyntheticFontFace.EmboldenContours(xy, boldX, boldY); + List res = new List(contours.Count); + for (int c = 0; c < contours.Count; ++c) + res.Add(contours[c].Select((p, i) => new GlyphPoint(wider[c][i].x, wider[c][i].y, p.OnCurve)).ToArray()); + return res; + } + + /// + /// 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) + { + if (IsCff2) + { + var t = tables["CFF2"]; + cff = new CffOutlines(data, t.offset, t.length, UnitsPerEm, variations); + } + else + { + var t = tables["CFF "]; + cff = new CffOutlines(data, t.offset, t.length, UnitsPerEm); + } + } + cff.Draw(glyph, sink, coords); + } + /// + /// 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; + } + } + /// + /// The position of a glyph in the "glyf" table and its length, 0 for an empty glyph. + /// + private int GlyphOffset(int glyph, out int length) + { + int loca = Table("loca"); + int start, end; + if (longLoca) + { + start = (int)U32(loca + 4 * glyph); + end = (int)U32(loca + 4 * glyph + 4); + } + else + { + start = 2 * U16(loca + 2 * glyph); + end = 2 * U16(loca + 2 * glyph + 2); + } + var glyf = tables["glyf"]; + if (end > glyf.length) throw new InvalidDataException("glyph " + glyph + " exceeds the glyf table"); + length = Math.Max(0, end - start); + return glyf.offset + start; + } + 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 + int g = GlyphOffset(glyph, out int length); + if (length == 0) return; // an empty glyph, e.g. the space + int numberOfContours = S16(g); + if (numberOfContours >= 0) ReadSimpleGlyph(glyph, g, numberOfContours, res); + else ReadCompositeGlyph(glyph, g, res, depth); + } + private void ReadSimpleGlyph(int glyph, int g, int numberOfContours, List res) + { + int pos = g + 10; + int[] endPts = new int[numberOfContours]; + for (int i = 0; i < numberOfContours; ++i) endPts[i] = U16(pos + 2 * i); + pos += 2 * numberOfContours; + int numPoints = numberOfContours == 0 ? 0 : endPts[numberOfContours - 1] + 1; + int instructionLength = U16(pos); + pos += 2 + instructionLength; + byte[] flags = new byte[numPoints]; + for (int i = 0; i < numPoints;) + { + byte f = data[pos++]; + flags[i++] = f; + if ((f & 8) != 0) + { + int repeat = data[pos++]; + for (int r = 0; r < repeat && i < numPoints; ++r) flags[i++] = f; + } + } + double[] xs = new double[numPoints], ys = new double[numPoints]; + int v = 0; + for (int i = 0; i < numPoints; ++i) + { + byte f = flags[i]; + if ((f & 2) != 0) v += (f & 0x10) != 0 ? data[pos++] : -data[pos++]; + else if ((f & 0x10) == 0) { v += S16(pos); pos += 2; } + xs[i] = v; + } + v = 0; + for (int i = 0; i < numPoints; ++i) + { + byte f = flags[i]; + if ((f & 4) != 0) v += (f & 0x20) != 0 ? data[pos++] : -data[pos++]; + else if ((f & 0x20) == 0) { v += S16(pos); pos += 2; } + ys[i] = v; + } + if (coords != null) + { // a variable font: add the deltas of "gvar", with four phantom points after the points of the glyph + double[] x = new double[numPoints + 4], y = new double[numPoints + 4], dx = new double[numPoints + 4], dy = new double[numPoints + 4]; + Array.Copy(xs, x, numPoints); + Array.Copy(ys, y, numPoints); + x[numPoints] = S16(g + 2) - LeftSideBearing(glyph); + x[numPoints + 1] = x[numPoints] + U16(Table("hmtx") + 4 * Math.Min(glyph, numberOfHMetrics - 1)); + variations.AddGlyphDeltas(glyph, x, y, endPts, coords, dx, dy); + for (int i = 0; i < numPoints; ++i) + { + xs[i] += dx[i]; + ys[i] += dy[i]; + } + } + int startPoint = 0; + for (int c = 0; c < numberOfContours; ++c) + { + int count = endPts[c] - startPoint + 1; + if (count <= 0 || endPts[c] >= numPoints) throw new InvalidDataException("invalid contour"); + GlyphPoint[] contour = new GlyphPoint[count]; + for (int i = 0; i < count; ++i) contour[i] = new GlyphPoint(xs[startPoint + i], ys[startPoint + i], (flags[startPoint + i] & 1) != 0); + res.Add(contour); + startPoint = endPts[c] + 1; + } + } + private struct Component + { + public int Flags, Glyph, Arg1, Arg2; + public double A, B, C, D; + } + private List CompositeComponents(int g) + { + List res = new List(); + int pos = g + 10; + int flags; + do + { + Component comp = new Component { A = 1, D = 1 }; + flags = comp.Flags = U16(pos); + comp.Glyph = U16(pos + 2); + pos += 4; + if ((flags & 1) != 0) // ARG_1_AND_2_ARE_WORDS + { + comp.Arg1 = S16(pos); + comp.Arg2 = S16(pos + 2); + pos += 4; + } + else if ((flags & 2) != 0) + { // offsets are signed + comp.Arg1 = (sbyte)data[pos]; + comp.Arg2 = (sbyte)data[pos + 1]; + pos += 2; + } + else + { // point numbers are unsigned + comp.Arg1 = data[pos]; + comp.Arg2 = data[pos + 1]; + pos += 2; + } + if ((flags & 0x8) != 0) // WE_HAVE_A_SCALE + { + comp.A = comp.D = F2Dot14(pos); + pos += 2; + } + else if ((flags & 0x40) != 0) // WE_HAVE_AN_X_AND_Y_SCALE + { + comp.A = F2Dot14(pos); + comp.D = F2Dot14(pos + 2); + pos += 4; + } + else if ((flags & 0x80) != 0) // WE_HAVE_A_TWO_BY_TWO + { + comp.A = F2Dot14(pos); + comp.B = F2Dot14(pos + 2); + comp.C = F2Dot14(pos + 4); + comp.D = F2Dot14(pos + 6); + pos += 8; + } + res.Add(comp); + } while ((flags & 0x20) != 0); // MORE_COMPONENTS + return res; + } + private void ReadCompositeGlyph(int glyph, int g, List res, int depth) + { + List components = CompositeComponents(g); + double[] offsetX = new double[components.Count], offsetY = new double[components.Count]; + for (int i = 0; i < components.Count; ++i) + { + offsetX[i] = components[i].Arg1; + offsetY[i] = components[i].Arg2; + } + if (coords != null) + { // a variable font: "gvar" has one delta per component (for its offset) and four for the phantom points + int n = components.Count; + double[] x = new double[n + 4], y = new double[n + 4], dx = new double[n + 4], dy = new double[n + 4]; + Array.Copy(offsetX, x, n); + Array.Copy(offsetY, y, n); + x[n] = S16(g + 2) - LeftSideBearing(glyph); + variations.AddGlyphDeltas(glyph, x, y, null, coords, dx, dy); + for (int i = 0; i < n; ++i) + { + if ((components[i].Flags & 2) == 0) continue; // point matching has no offset to vary + offsetX[i] += dx[i]; + offsetY[i] += dy[i]; + } + } + for (int ci = 0; ci < components.Count; ++ci) + { + Component comp = components[ci]; + double a = comp.A, b = comp.B, c = comp.C, d = comp.D; + List parts = new List(); + AddContours(comp.Glyph, parts, depth + 1); + double dx = 0, dy = 0; + if ((comp.Flags & 2) != 0) // ARGS_ARE_XY_VALUES + { + dx = offsetX[ci]; + dy = offsetY[ci]; + if ((comp.Flags & 0x800) != 0) // SCALED_COMPONENT_OFFSET: the offset is transformed as well + { + double tx = a * dx + c * dy, ty = b * dx + d * dy; + dx = tx; + dy = ty; + } + } + else + { // the component is placed by matching a point of the glyph so far (arg1) with a point of the component (arg2) + GlyphPoint? p1 = PointByIndex(res, comp.Arg1), p2 = PointByIndex(parts, comp.Arg2); + if (p1.HasValue && p2.HasValue) + { + dx = p1.Value.X - (a * p2.Value.X + c * p2.Value.Y); + dy = p1.Value.Y - (b * p2.Value.X + d * p2.Value.Y); + } + } + foreach (GlyphPoint[] part in parts) + { + GlyphPoint[] moved = new GlyphPoint[part.Length]; + for (int i = 0; i < part.Length; ++i) + { + double x = part[i].X, y = part[i].Y; + moved[i] = new GlyphPoint(a * x + c * y + dx, b * x + d * y + dy, part[i].OnCurve); + } + res.Add(moved); + } + } + } + private static GlyphPoint? PointByIndex(List contours, int index) + { + foreach (GlyphPoint[] contour in contours) + { + if (index < contour.Length) return contour[index]; + index -= contour.Length; + } + return null; + } + + private void SelectCmap() + { + if (!tables.TryGetValue("cmap", out var cmap)) return; + int numTables = U16(cmap.offset + 2); + int best = -1, bestRank = int.MaxValue; + for (int i = 0; i < numTables; ++i) + { + int rec = cmap.offset + 4 + 8 * i; + int platform = U16(rec), encoding = U16(rec + 2); + int sub = cmap.offset + (int)U32(rec + 4); + if (sub + 2 > cmap.offset + cmap.length) continue; + int format = U16(sub); + if (format != 4 && format != 12 && format != 6 && format != 0) continue; + int rank; + if (platform == 3 && encoding == 10 && format == 12) rank = 0; + else if (platform == 0 && format == 12) rank = 1; + else if (platform == 3 && encoding == 1) rank = 2; + else if (platform == 0) rank = 3; + else if (platform == 3 && encoding == 0) rank = 4; // symbol font + else if (platform == 1 && encoding == 0) rank = 5; // Macintosh Roman, only right for ASCII + else continue; + if (rank < bestRank) + { + bestRank = rank; + best = sub; + } + } + if (best < 0) return; + cmapOffset = best; + cmapFormat = U16(best); + if (bestRank == 4) symbolOffset = 0xF000; // symbol fonts map their characters to U+F000 to U+F0FF + } + private int Lookup(int c) + { + int t = cmapOffset; + switch (cmapFormat) + { + case 0: + return c < 256 ? data[t + 6 + c] : 0; + case 6: + { + int firstCode = U16(t + 6), entryCount = U16(t + 8); + int i = c - firstCode; + return i >= 0 && i < entryCount ? U16(t + 10 + 2 * i) : 0; + } + case 4: + { + if (c > 0xFFFF) return 0; + int segCountX2 = U16(t + 6); + int endCodes = t + 14, startCodes = endCodes + segCountX2 + 2, idDeltas = startCodes + segCountX2, idRangeOffsets = idDeltas + segCountX2; + int lo = 0, hi = segCountX2 / 2 - 1; + while (lo <= hi) + { // the segments are sorted by their end code + int mid = (lo + hi) / 2; + if (U16(endCodes + 2 * mid) < c) lo = mid + 1; + else hi = mid - 1; + } + if (lo >= segCountX2 / 2) return 0; + int start = U16(startCodes + 2 * lo); + if (c < start) return 0; + int delta = S16(idDeltas + 2 * lo); + int rangeOffsetPos = idRangeOffsets + 2 * lo; + int rangeOffset = U16(rangeOffsetPos); + if (rangeOffset == 0) return (c + delta) & 0xFFFF; + int glyphPos = rangeOffsetPos + rangeOffset + 2 * (c - start); + int glyph = U16(glyphPos); + return glyph == 0 ? 0 : (glyph + delta) & 0xFFFF; + } + case 12: + { + int numGroups = (int)U32(t + 12); + int lo = 0, hi = numGroups - 1; + while (lo <= hi) + { + int mid = (lo + hi) / 2; + int grp = t + 16 + 12 * mid; + uint startChar = U32(grp), endChar = U32(grp + 4); + if (c < startChar) hi = mid - 1; + else if (c > endChar) lo = mid + 1; + else return (int)(U32(grp + 8) + (c - startChar)); + } + return 0; + } + } + return 0; + } + + private Dictionary ReadKerning() + { + Dictionary res = new Dictionary(); + if (!tables.TryGetValue("kern", out var kern) || kern.length < 4) return res; + if (U16(kern.offset) != 0) return res; // only the version 0 (Windows) table, as GDI reads it + int nTables = U16(kern.offset + 2); + int pos = kern.offset + 4; + for (int i = 0; i < nTables && pos + 6 <= kern.offset + kern.length; ++i) + { + int length = U16(pos + 2); + int coverage = U16(pos + 4); + int format = coverage >> 8; + bool horizontal = (coverage & 1) != 0, minimum = (coverage & 2) != 0, crossStream = (coverage & 4) != 0; + if (format == 0 && horizontal && !minimum && !crossStream) + { + int nPairs = U16(pos + 6); + int p = pos + 14; + for (int k = 0; k < nPairs && p + 6 <= kern.offset + kern.length; ++k, p += 6) + { + int key = (U16(p) << 16) | U16(p + 2); + res.TryGetValue(key, out int value); + res[key] = value + S16(p + 4); + } + } + if (length == 0) break; // some fonts have a wrong length in a single subtable + pos += length; + } + return res; + } + + private void ReadNames() + { + names = new Dictionary(); + if (!tables.TryGetValue("name", out var name)) return; + int count = U16(name.offset + 2); + int strings = name.offset + U16(name.offset + 4); + // per name ID: the rank of the record found so far (Windows US English is best) + Dictionary rankOf = new Dictionary(); + for (int i = 0; i < count; ++i) + { + int rec = name.offset + 6 + 12 * i; + int platform = U16(rec), encoding = U16(rec + 2), language = U16(rec + 4), nameId = U16(rec + 6); + if (nameId == 0 || (nameId > 25 && nameId < 256)) continue; // only the names CADability uses (families, styles, instances) + int length = U16(rec + 8), offset = strings + U16(rec + 10); + if (offset + length > data.Length) continue; + int rank; + if (platform == 3 && (encoding == 1 || encoding == 0 || encoding == 10)) rank = language == 0x409 ? 0 : 1; + else if (platform == 0) rank = 2; + else if (platform == 1 && encoding == 0) rank = 3; + else continue; + if (rankOf.TryGetValue(nameId, out int old) && old <= rank) continue; + rankOf[nameId] = rank; + names[nameId] = rank == 3 ? Encoding.ASCII.GetString(data, offset, length) : Encoding.BigEndianUnicode.GetString(data, offset, length); + } + } + + private int Table(string tag) + { + if (!tables.TryGetValue(tag, out var t)) throw new InvalidDataException("the font has no " + tag + " table"); + return t.offset; + } + private int U16(int pos) => (data[pos] << 8) | data[pos + 1]; + private short S16(int pos) => (short)((data[pos] << 8) | data[pos + 1]); + private uint U32(int pos) => ReadU32(data, pos); + private double F2Dot14(int pos) => S16(pos) / 16384.0; + private static uint ReadU32(byte[] d, int pos) => (uint)((d[pos] << 24) | (d[pos + 1] << 16) | (d[pos + 2] << 8) | d[pos + 3]); + } +} diff --git a/CADability/UnscaledIcon.cs b/CADability/UnscaledIcon.cs index 0b66db6aa..fd6a82f6f 100644 --- a/CADability/UnscaledIcon.cs +++ b/CADability/UnscaledIcon.cs @@ -10,12 +10,15 @@ namespace CADability.GeoObject /// /// A GeoObject that displays an icon at a given position. The icon will always face the viewer /// and will not be scaled. The icon appears with a small offset in direction to the viewer, so it will not be covered - /// by a face with which it coincides. + /// by a face with which it coincides. The image is kept as the bytes of a PNG, JPEG, GIF or BMP file + /// (), which is also what project files contain (issue #412). /// [Serializable()] public class Icon : IGeoObjectImpl, ISerializable { - private Bitmap bitmap; + private byte[] imageData; // the bytes of the image file, null for no image + private Bitmap gdiBitmap; // decoded from imageData with GDI+ for painting (Windows), created on demand + private bool gdiFailed; // GDI+ cannot decode imageData private GeoPoint location; private int offsetx, offsety; #region polymorph construction @@ -51,32 +54,89 @@ public static Icon Construct() #endregion /// /// Protected constructor: raises . Use to create an icon and - /// to define it. + /// to define it. /// protected Icon() { if (Constructed != null) Constructed(this); } /// - /// Sets the Data of the UnscaledIcon. + /// Sets the image and the position of the icon. + /// + /// The bytes of a PNG, JPEG, GIF or BMP file; null for no image. The array is kept, so it + /// must not be changed afterwards. + /// Position where the icon will be displayed + /// X-Position in the icon where location is applied + /// Y-Position in the icon where location is applied. (0,0) is lower left of the icon + /// The data is not a PNG, JPEG, GIF or BMP file + public void Set(byte[] imageData, GeoPoint location, int offsetx, int offsety) + { + EncodedImage.Validate(imageData, nameof(imageData)); + using (new Changing(this)) + { + SetImage(imageData, null); + this.location = location; + this.offsetx = offsetx; + this.offsety = offsety; + } + } + /// + /// Sets the Data of the UnscaledIcon. The bitmap is encoded into : PNG, JPEG, GIF and BMP + /// images keep their format, any other image becomes PNG. Needs GDI+, i.e. Windows. /// /// The icon to be displayed /// Position where the icon will be displayed /// X-Position in the icon where location is applied /// Y-Position in the icon where location is applied. (0,0) is lower left of the icon + [Obsolete("Use Set(byte[], GeoPoint, int, int) with the bytes of the image file. A GDI+ Bitmap only works on Windows (issue #412).")] public void Set(Bitmap bitmap, GeoPoint location, int offsetx, int offsety) { using (new Changing(this)) { - this.bitmap = bitmap; + SetImage(EncodedImage.FromGdiImage(bitmap), bitmap); this.location = location; this.offsetx = offsetx; this.offsety = offsety; } } /// + /// The image of the icon as the bytes of a PNG, JPEG, GIF or BMP file, null if the icon has no image. The array must + /// not be changed; use to replace it. + /// + public byte[] ImageData => imageData; + /// + /// The width of the image in pixels, read from the header of ; 0 without an image. + /// + public int PixelWidth => EncodedImage.TryGetSize(imageData, out int width, out _) ? width : 0; + /// + /// The height of the image in pixels, read from the header of ; 0 without an image. + /// + public int PixelHeight => EncodedImage.TryGetSize(imageData, out _, out int height) ? height : 0; + private void SetImage(byte[] data, Bitmap decoded) + { + imageData = data; + gdiBitmap = decoded; + gdiFailed = false; + } + /// + /// The GDI+ bitmap to paint, null where GDI+ is not available or cannot decode the image. + /// + private Bitmap BitmapForPainting() + { + if (gdiBitmap != null || imageData == null || gdiFailed) return gdiBitmap; + try + { + gdiBitmap = EncodedImage.ToGdiBitmap(imageData); + } + catch (Exception e) when (e is PlatformNotSupportedException || e is TypeInitializationException || e is DllNotFoundException || e is ArgumentException || e is System.Runtime.InteropServices.ExternalException) + { + gdiFailed = true; + } + return gdiBitmap; + } + /// /// The point in world coordinates at which the icon is displayed: the pixel of the bitmap given by the offsets in - /// is placed at this point. Setting it raises the change events and is undoable. + /// is placed at this point. Setting it raises the change events and is undoable. /// public GeoPoint Location { @@ -106,7 +166,7 @@ public override IGeoObject Clone() { Icon res = new Icon(); res.location = location; - res.bitmap = bitmap; + res.SetImage(imageData, gdiBitmap); // the bytes are never changed in place, so they can be shared res.offsetx = offsetx; res.offsety = offsety; return res; @@ -117,7 +177,7 @@ public override void CopyGeometry(IGeoObject ToCopyFrom) Icon from = ToCopyFrom as Icon; if (from == null) return; this.location = from.location; - this.bitmap = from.bitmap; + SetImage(from.imageData, from.gdiBitmap); this.offsetx = from.offsetx; this.offsety = from.offsety; } @@ -135,12 +195,14 @@ public override BoundingCube GetBoundingCube() /// public override void PaintTo3D(IPaintTo3D paintTo3D) { - paintTo3D.DisplayBitmap(location, bitmap); + Bitmap bitmap = BitmapForPainting(); + if (bitmap != null) paintTo3D.DisplayBitmap(location, bitmap); // without GDI+ the icon is not painted } /// public override void PrePaintTo3D(IPaintTo3D paintTo3D) { - paintTo3D.PrepareBitmap(bitmap, offsetx, offsety); + Bitmap bitmap = BitmapForPainting(); + if (bitmap != null) paintTo3D.PrepareBitmap(bitmap, offsetx, offsety); } /// public override void PrepareDisplayList(double precision) @@ -197,14 +259,15 @@ public override double Position(GeoPoint fromHere, GeoVector direction, double p /// StreamingContext protected Icon(SerializationInfo info, StreamingContext context) { - bitmap = info.GetValue("Bitmap", typeof(Bitmap)) as Bitmap; + imageData = Picture.ReadImage(info, null, "Icon"); location = (GeoPoint)info.GetValue("Location", typeof(GeoPoint)); offsetx = info.GetInt32("Offsetx"); offsety = info.GetInt32("Offsety"); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { - info.AddValue("Bitmap", bitmap); + // the image file as Base64, see Picture.GetObjectData + info.AddValue("ImageData", imageData == null ? null : Convert.ToBase64String(imageData)); info.AddValue("Location", location); info.AddValue("Offsetx", offsetx); info.AddValue("Offsety", offsety); diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 000000000..6fe84ee21 --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,43 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/REUSE.toml b/REUSE.toml index 6ffece05f..139dc45db 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -29,3 +29,33 @@ path = [ "CADability/Strings.cs", "CADability/Util.cs", ] + +[[annotations]] +path = [ + "tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.ttf", + "tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.reference.json", +] +SPDX-FileCopyrightText = [ + "2010 Google Corporation with Reserved Font Arimo, Tinos and Cousine", + "2012 Red Hat, Inc. with Reserved Font Name Liberation", +] +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]] +path = [ + "tests/CADability.Tests/Files/Fonts/VarTestSans.ttf", + "tests/CADability.Tests/Files/Fonts/VarTestSans-NoHVAR.ttf", + "tests/CADability.Tests/Files/Fonts/VarTestSans.otf", + "tests/CADability.Tests/Files/Fonts/VarTestSans.reference.json", +] +SPDX-FileCopyrightText = "2023 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'" +SPDX-License-Identifier = "OFL-1.1" diff --git a/tests/CADability.ImportTests/CADability.ImportTests.csproj b/tests/CADability.ImportTests/CADability.ImportTests.csproj index 3ab5a792d..bdea3af5c 100644 --- a/tests/CADability.ImportTests/CADability.ImportTests.csproj +++ b/tests/CADability.ImportTests/CADability.ImportTests.csproj @@ -13,6 +13,13 @@ + + + + PreserveNewest + + + PreserveNewest diff --git a/tests/CADability.ImportTests/DxfDimensionTest.cs b/tests/CADability.ImportTests/DxfDimensionTest.cs index 32b4b7068..4a37aa856 100644 --- a/tests/CADability.ImportTests/DxfDimensionTest.cs +++ b/tests/CADability.ImportTests/DxfDimensionTest.cs @@ -367,7 +367,6 @@ public void import_dxf_dimension_with_missing_block_is_drawn_from_its_definition [TestMethod] public void import_dxf_dimension_without_block_shows_its_measurement() { - RequireGdi(); string dxf = Header + Tables + EntitiesStart + AlignedDimension(null) + Epilogue; Block block = AssertRegeneratedDimension(ImportDxf(dxf).GetActiveModel()); Text text = block.Children.OfType().SingleOrDefault(); @@ -759,7 +758,6 @@ public void export_dimension_writes_its_xdata() [TestMethod] public void export_dimension_round_trips_as_a_dxf_dimension() { - RequireGdi(); Project project = MakeProjectWithDimension(new GeoPoint(0, 0, 0), new GeoPoint(100, 0, 0)); Model reimported = WithChoice("DxfDwg.ExportDimension", 1, () => ExportAndImport(project)); @@ -1613,13 +1611,6 @@ private static Block SingleDimensionBlock(Model model) return block; } - // Adding a Text to a model computes its extent through GDI, which only exists on Windows. - private static void RequireGdi() - { - if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - Assert.Inconclusive("text needs GDI, which is only available on Windows"); - } - private Project ImportDxf(string dxf) { string file = this.TestContext.TestName + ".dxf"; diff --git a/tests/CADability.ImportTests/DxfOcsExportTest.cs b/tests/CADability.ImportTests/DxfOcsExportTest.cs index 5e436801b..3f81fd850 100644 --- a/tests/CADability.ImportTests/DxfOcsExportTest.cs +++ b/tests/CADability.ImportTests/DxfOcsExportTest.cs @@ -90,7 +90,6 @@ public void export_dxf_tilted_circle_and_arc_keep_position() [TestMethod] public void export_dxf_flipped_text_keeps_position() { - RequireGdi(); foreach (bool aligned in new[] { false, true }) { Text text = MakeText("Flipped", new GeoPoint(10, 5, 2)); @@ -119,7 +118,6 @@ public void export_dxf_flipped_text_keeps_position() [TestMethod] public void export_dxf_tilted_text_keeps_position() { - RequireGdi(); Text text = MakeText("Tilted", new GeoPoint(-3, 8, 1)); text.Modify(ModOp.Rotate(new GeoPoint(2, 3, 4), new GeoVector(1, 1, 0.3), new SweepAngle(0.65))); AssertSameText(text, ReimportSingle(Export(text))); @@ -130,7 +128,6 @@ public void export_dxf_tilted_text_keeps_position() [TestMethod] public void export_dxf_flipped_and_tilted_mtext_keep_position() { - RequireGdi(); Text text = MakeText("first line\nsecond line", new GeoPoint(10, 5, 2)); text.Modify(ModOp.Rotate(GeoPoint.Origin, GeoVector.XAxis, new SweepAngle(Math.PI))); string file = Export(text); @@ -151,13 +148,6 @@ public void export_dxf_flipped_and_tilted_mtext_keep_position() // --- helpers ------------------------------------------------------------------------- - // Adding a Text to a model computes its extent through GDI, which only exists on Windows. - private static void RequireGdi() - { - if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - Assert.Inconclusive("text needs GDI, which is only available on Windows"); - } - private static Text MakeText(string value, GeoPoint location) { Text text = Text.Construct(); diff --git a/tests/CADability.ImportTests/TestFonts.cs b/tests/CADability.ImportTests/TestFonts.cs new file mode 100644 index 000000000..1be6eb083 --- /dev/null +++ b/tests/CADability.ImportTests/TestFonts.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CADability.ImportTests +{ + /// + /// Texts get their fonts from FontProvider.Current. The tests add Liberation Sans (Files/Fonts) to the fonts of the system, + /// so that texts have a font on every machine, also on a build server without any fonts installed (issue #412). + /// + [TestClass] + public static class TestFonts + { + public static string Directory => System.IO.Path.Combine(AppContext.BaseDirectory, "Files", "Fonts"); + + [AssemblyInitialize] + public static void UseTestFonts(TestContext context) + { + FontProvider.Current = new ManagedFontProvider(new[] { Directory }, true); + } + } +} diff --git a/tests/CADability.Tests/CffOutlinesTests.cs b/tests/CADability.Tests/CffOutlinesTests.cs new file mode 100644 index 000000000..6937bc68c --- /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/CDB/pagesettings-1x-net48.cdb b/tests/CADability.Tests/Files/CDB/pagesettings-1x-net48.cdb new file mode 100644 index 000000000..5654cdba7 Binary files /dev/null and b/tests/CADability.Tests/Files/CDB/pagesettings-1x-net48.cdb differ diff --git a/tests/CADability.Tests/Files/CDB/pagesettings-1x-net48.cdb.json b/tests/CADability.Tests/Files/CDB/pagesettings-1x-net48.cdb.json new file mode 100644 index 000000000..ec2420be3 --- /dev/null +++ b/tests/CADability.Tests/Files/CDB/pagesettings-1x-net48.cdb.json @@ -0,0 +1 @@ +{"CADability":{"URL":"http://www.cadability.de","Version":"1.6.0.0","Assembly":"CADability"},"Entities":[{"$Type":"CADability.Project","$TypeIndex":1,"$TypeVersion":1,"Models":["#1"],"ActiveModelIndex":0,"AttributeLists":"#2","UserData":"#3","SymbolList":"#4","Layouts":["#5"],"GdiViews":[],"ProjectedModels":["#6","#7","#8","#9"],"NamedValues":"#10","FilterList":"#11","AnimatedViews":[],"ActiveViewName":null,"DefaultPageSettings":"#12"},{"$Type":"CADability.Model","$TypeIndex":2,"$TypeVersion":0,"GeoObjectList":"#13","Name":"Standard model","Unit":"millimeter","DefaultScale":1,"LineStyleScale":1,"MinExtend":[1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308],"AllDrives":null,"AllSchedules":null,"UserData":null},{"$Type":"CADability.JSonDictionary","$TypeIndex":3,"$TypeVersion":0,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.String","$ValueType":"System.Object","$Entries":[["HatchStyleList","#14"],["LineWidthList","#15"],["LayerList","#16"],["StyleList","#17"],["LinePatternList","#18"],["ColorList","#19"],["DimensionStyleList","#20"]]},{"$Type":"CADability.UserData","$TypeIndex":4,"$TypeVersion":0},{"$Type":"CADability.GeoObject.GeoObjectList","$TypeIndex":5,"$TypeVersion":0,"List":[],"UserData":"#21"},{"$Type":"CADability.Layout","$TypeIndex":6,"$TypeVersion":-1,"Name":"A4 portrait","Patches":{"$Type":"CADability.LayoutPatch[]","$Value":[]},"paperWidth":{"$Type":"Double","$Value":"0"},"paperHeight":{"$Type":"Double","$Value":"0"},"PageSettings":"#22"},{"$Type":"CADability.ProjectedModel","$TypeIndex":7,"$TypeVersion":0,"Name":"Top","Model":"#1","Projection":"#23","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":7,"Name":"Right","Model":"#1","Projection":"#24","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":7,"Name":"Front","Model":"#1","Projection":"#25","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":7,"Name":"Perspective","Model":"#1","Projection":"#26","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$Type":"CADability.NamedValuesProperty","$TypeIndex":8,"$TypeVersion":0,"NamedValues":"#27"},{"$Type":"CADability.Attribute.FilterList","$TypeIndex":9,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.Filter[]","$Value":[]}},{"$Type":"System.Drawing.Printing.PageSettings","$Assembly":"System.Drawing","$TypeIndex":10,"Color":true,"Landscape":true,"Margins":"#28","PaperSize":"#29","PaperSource":"#30","PrinterResolution":"#31","PrinterSettings":"#32","$TypeVersion":-1},{"$TypeIndex":5,"List":[],"UserData":"#33"},{"$Type":"CADability.Attribute.HatchStyleList","$TypeIndex":11,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.HatchStyle[]","$Value":["#34","#35","#36"]},"Current":"#35"},{"$Type":"CADability.Attribute.LineWidthList","$TypeIndex":12,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#37","#38","#39"]},"Current":"#37"},{"$Type":"CADability.Attribute.LayerList","$TypeIndex":13,"$TypeVersion":0,"Entries":["#40","#41","#42","#43","#44","#45"],"Current":"#44"},{"$Type":"CADability.Attribute.StyleList","$TypeIndex":14,"$TypeVersion":0,"UnsortedEntries":["#46","#47","#48","#49"],"Current":"#46"},{"$Type":"CADability.Attribute.LinePatternList","$TypeIndex":15,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#50","#51","#52","#53"]},"Current":"#50"},{"$Type":"CADability.Attribute.ColorList","$TypeIndex":16,"$TypeVersion":0,"NamedColors":["#54","#55","#56"],"Current":"#54"},{"$Type":"CADability.Attribute.DimensionStyleList","$TypeIndex":17,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.DimensionStyle[]","$Value":["#57"]},"CurrentDimensionStyle":"#57"},{"$TypeIndex":4},{"$TypeIndex":10,"Color":true,"Landscape":false,"Margins":"#58","PaperSize":"#59","PaperSource":"#60","PrinterResolution":"#61","PrinterSettings":"#62"},{"$Type":"CADability.Projection","$TypeIndex":18,"$TypeVersion":-1,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,0,0,0,0,1,0,0,0,0,1,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#63","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#64","Direction":{"$Type":"CADability.GeoVector","$Value":[0,0,1]}},{"$TypeIndex":18,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[0,1,0,0,0,0,1,0,1,0,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[0,1,0],[0,0,1]]]},"Grid":"#65","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#64","Direction":{"$Type":"CADability.GeoVector","$Value":[1,0,0]}},{"$TypeIndex":18,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,0,0,0,0,0,1,0,0,-1,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,0,1]]]},"Grid":"#66","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#64","Direction":{"$Type":"CADability.GeoVector","$Value":[0,-1,0]}},{"$TypeIndex":18,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[-0.70710678118654757,0.70710678118654757,0,0,-0.40824829046386296,-0.40824829046386296,0.81649658092772592,0,0.57735026918962573,0.57735026918962573,0.57735026918962562,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#67","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#64","Direction":{"$Type":"CADability.GeoVector","$Value":[-1,-1,-1]}},{"$TypeIndex":3,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.Object","$ValueType":"System.Object","$Entries":[]},{"$Type":"System.Drawing.Printing.Margins","$Assembly":"System.Drawing","$TypeIndex":19,"Left":50,"Right":60,"Top":70,"Bottom":80,"DoubleLeft":50,"DoubleRight":60,"DoubleTop":70,"DoubleBottom":80,"$TypeVersion":-1},{"$Type":"System.Drawing.Printing.PaperSize","$Assembly":"System.Drawing","$TypeIndex":20,"Height":1654,"PaperName":"A3","RawKind":8,"Width":1169,"$TypeVersion":-1},{"$Type":"System.Drawing.Printing.PaperSource","$Assembly":"System.Drawing","$TypeIndex":21,"RawKind":15,"SourceName":"unknown","$TypeVersion":-1},{"$Type":"System.Drawing.Printing.PrinterResolution","$Assembly":"System.Drawing","$TypeIndex":22,"Kind":"Custom","X":600,"Y":600,"$TypeVersion":-1},{"$Type":"System.Drawing.Printing.PrinterSettings","$Assembly":"System.Drawing","$TypeIndex":23,"Copies":1,"Collate":true,"Duplex":"Simplex","FromPage":0,"MaximumPage":9999,"MinimumPage":0,"OutputPort":"","PrintFileName":"","PrintDialogDisplayed":false,"PrintRange":"AllPages","PrintToFile":false,"PrinterName":"Microsoft Print to PDF","PrinterNameInternal":"Microsoft Print to PDF","ToPage":0,"$TypeVersion":-1},{"$TypeIndex":4},{"$Type":"CADability.Attribute.HatchStyleLines","$TypeIndex":24,"$TypeVersion":-1,"Name":"Lines","LineDistance":{"$Type":"Double","$Value":"10"},"LineAngle":"#68","MarginOffset":{"$Type":"Double","$Value":"0"},"NumberOffset":{"$Type":"Int32","$Value":"0"},"Offset":{"$Type":"Double","$Value":"0"},"Alternate":false,"ColorDef":"#54","LineWidth":"#37","LinePattern":"#50"},{"$Type":"CADability.Attribute.HatchStyleSolid","$TypeIndex":25,"$TypeVersion":-1,"Name":"Solid","Color":"#54"},{"$TypeIndex":25,"Name":"Standard [DimensionStyleFillSolid]","Color":"#54"},{"$Type":"CADability.Attribute.LineWidth","$TypeIndex":26,"$TypeVersion":0,"Name":"Thin","Width":0,"Scale":"Device"},{"$TypeIndex":26,"Name":"0.7 mm","Width":0.69999999999999996,"Scale":"Layout"},{"$TypeIndex":26,"Name":"0,3 mm","Width":0.29999999999999999,"Scale":"World"},{"$Type":"CADability.Attribute.Layer","$TypeIndex":27,"$TypeVersion":0,"Name":"2D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":27,"Name":"3D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":27,"Name":"Background","DisplayOrder":0,"Transparency":0},{"$TypeIndex":27,"Name":"Dimension","DisplayOrder":0,"Transparency":0},{"$TypeIndex":27,"Name":"Standard","DisplayOrder":0,"Transparency":0},{"$TypeIndex":27,"Name":"Text","DisplayOrder":0,"Transparency":0},{"$Type":"CADability.Attribute.Style","$TypeIndex":28,"$TypeVersion":0,"Name":"2D","Layer":"#40","ColorDef":"#54","LinePattern":"#50","LineWidth":"#37","DimStyle":"#57","HatchStyle":"#34","Parent":"#17","DefaultFor":"Curves"},{"$TypeIndex":28,"Name":"3D","Layer":"#41","Parent":"#17","DefaultFor":"Solids"},{"$TypeIndex":28,"Name":"Dimension","Layer":"#43","Parent":"#17","DefaultFor":"Dimension"},{"$TypeIndex":28,"Name":"Text","Layer":"#45","Parent":"#17","DefaultFor":"Text"},{"$Type":"CADability.Attribute.LinePattern","$TypeIndex":29,"$TypeVersion":0,"Name":"Continous","Pattern":[],"Scale":"Layout"},{"$TypeIndex":29,"Name":"Dashed","Pattern":[2,2],"Scale":"Layout"},{"$TypeIndex":29,"Name":"Dash-dotted","Pattern":[4,2,0,0,0,2],"Scale":"Layout"},{"$TypeIndex":29,"Name":"Dotted","Pattern":[0,2],"Scale":"Layout"},{"$Type":"CADability.Attribute.ColorDef","$TypeIndex":30,"$TypeVersion":0,"Name":"Red","Source":"fromName","Color":-65536},{"$TypeIndex":30,"Name":"Green","Source":"fromName","Color":-16711936},{"$TypeIndex":30,"Name":"Blue","Source":"fromName","Color":-16776961},{"$Type":"CADability.Attribute.DimensionStyle","$TypeIndex":31,"$TypeVersion":-1,"Name":"Standard","TextPrefix":"","TextPostfix":"","TextPostfixAlt":"","PointFlags":{"$Type":"CADability.Attribute.DimensionStyle+EPointFlag","$Value":"0"},"AngleFlags":{"$Type":"CADability.Attribute.DimensionStyle+EAngleFlag","$Value":"0"},"RadiusFlags":{"$Type":"CADability.Attribute.DimensionStyle+ERadiusFlag","$Value":"0"},"SymbolFlags":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolFlag","$Value":"0"},"GeoFlags":{"$Type":"CADability.Attribute.DimensionStyle+EGeoFlag","$Value":"0"},"TextFont":"Arial","FontColor":null,"DimLineColor":"#54","DimLineWidth":null,"ExtLineColor":"#54","ExtLineWidth":null,"TextFlags":{"$Type":"CADability.Attribute.DimensionStyle+ETextFlag","$Value":"0"},"Types":{"$Type":"CADability.Attribute.DimensionStyle+ETypeFlag","$Value":"DimAllTypes"},"DimLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineOffset":{"$Type":"Double","$Value":"0"},"LineIncrement":{"$Type":"Double","$Value":"0"},"Symbol":{"$Type":"CADability.Attribute.DimensionStyle+ESymbol","$Value":"DimFilledArrow"},"SymbolSize":{"$Type":"Double","$Value":"3"},"Scale":{"$Type":"Double","$Value":"1"},"Round":{"$Type":"Double","$Value":"0.001"},"RoundAlt":{"$Type":"Double","$Value":"0.001"},"TextSize":{"$Type":"Double","$Value":"5"},"TextSizeTol":{"$Type":"Double","$Value":"3"},"TextDist":{"$Type":"Double","$Value":"0"},"PlusTolerance":{"$Type":"Double","$Value":"0"},"MinusTolerance":{"$Type":"Double","$Value":"0"},"CenterMarkSize":{"$Type":"Double","$Value":"2"},"ScaleAlt":{"$Type":"Double","$Value":"1"},"DimensionLineGap":{"$Type":"Double","$Value":"0"},"SymbolPlacement":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolPlacement","$Value":"DimAutomatic"},"FillColor":"#54","FixedLayer":null,"AngleText":{"$Type":"CADability.Attribute.DimensionStyle+EAngleText","$Value":"DegreeDecimal"},"UserData":null},{"$TypeIndex":19,"Left":10,"Right":20,"Top":30,"Bottom":40,"DoubleLeft":10,"DoubleRight":20,"DoubleTop":30,"DoubleBottom":40},{"$TypeIndex":20,"Height":1169,"PaperName":"A4","RawKind":9,"Width":827},{"$TypeIndex":21,"RawKind":15,"SourceName":"unknown"},{"$TypeIndex":22,"Kind":"Custom","X":600,"Y":600},{"$TypeIndex":23,"Copies":1,"Collate":true,"Duplex":"Simplex","FromPage":0,"MaximumPage":9999,"MinimumPage":0,"OutputPort":"","PrintFileName":"","PrintDialogDisplayed":false,"PrintRange":"AllPages","PrintToFile":false,"PrinterName":"Microsoft Print to PDF","PrinterNameInternal":"Microsoft Print to PDF","ToPage":0},{"$Type":"CADability.Grid","$TypeIndex":32,"$TypeVersion":-1,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$Type":"CADability.Matrix4","$TypeIndex":33,"$TypeVersion":-1,"Matrix":{"$Type":"System.Double[,]","$Value":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}},{"$TypeIndex":32,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":32,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":32,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$Type":"CADability.Angle","$TypeIndex":34,"$TypeVersion":-1,"A":{"$Type":"Double","$Value":"0.78539816339744828"}}]} \ No newline at end of file diff --git a/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb b/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb new file mode 100644 index 000000000..8c3b475e8 Binary files /dev/null and b/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb differ diff --git a/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb.json b/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb.json new file mode 100644 index 000000000..a5cae528c --- /dev/null +++ b/tests/CADability.Tests/Files/CDB/pictures-1x-net48.cdb.json @@ -0,0 +1 @@ +{"CADability":{"URL":"http://www.cadability.de","Version":"1.6.0.0","Assembly":"CADability"},"Entities":[{"$Type":"CADability.Project","$TypeIndex":1,"$TypeVersion":1,"Models":["#1"],"ActiveModelIndex":0,"AttributeLists":"#2","UserData":"#3","SymbolList":"#4","Layouts":[],"GdiViews":[],"ProjectedModels":["#5","#6","#7","#8"],"NamedValues":"#9","FilterList":"#10","AnimatedViews":[],"ActiveViewName":null},{"$Type":"CADability.Model","$TypeIndex":2,"$TypeVersion":0,"GeoObjectList":"#11","Name":"Standard model","Unit":"millimeter","DefaultScale":1,"LineStyleScale":1,"MinExtend":[1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308],"AllDrives":null,"AllSchedules":null,"UserData":null},{"$Type":"CADability.JSonDictionary","$TypeIndex":3,"$TypeVersion":0,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.String","$ValueType":"System.Object","$Entries":[["HatchStyleList","#12"],["LineWidthList","#13"],["LayerList","#14"],["StyleList","#15"],["LinePatternList","#16"],["ColorList","#17"],["DimensionStyleList","#18"]]},{"$Type":"CADability.UserData","$TypeIndex":4,"$TypeVersion":0},{"$Type":"CADability.GeoObject.GeoObjectList","$TypeIndex":5,"$TypeVersion":0,"List":[],"UserData":"#19"},{"$Type":"CADability.ProjectedModel","$TypeIndex":6,"$TypeVersion":0,"Name":"Top","Model":"#1","Projection":"#20","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Right","Model":"#1","Projection":"#21","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Front","Model":"#1","Projection":"#22","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Perspective","Model":"#1","Projection":"#23","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$Type":"CADability.NamedValuesProperty","$TypeIndex":7,"$TypeVersion":0,"NamedValues":"#24"},{"$Type":"CADability.Attribute.FilterList","$TypeIndex":8,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.Filter[]","$Value":[]}},{"$TypeIndex":5,"List":["#25","#26","#27"],"UserData":"#28"},{"$Type":"CADability.Attribute.HatchStyleList","$TypeIndex":9,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.HatchStyle[]","$Value":["#29","#30","#31"]},"Current":"#30"},{"$Type":"CADability.Attribute.LineWidthList","$TypeIndex":10,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#32","#33","#34"]},"Current":"#32"},{"$Type":"CADability.Attribute.LayerList","$TypeIndex":11,"$TypeVersion":0,"Entries":["#35","#36","#37","#38","#39","#40"],"Current":"#39"},{"$Type":"CADability.Attribute.StyleList","$TypeIndex":12,"$TypeVersion":0,"UnsortedEntries":["#41","#42","#43","#44"],"Current":"#41"},{"$Type":"CADability.Attribute.LinePatternList","$TypeIndex":13,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#45","#46","#47","#48"]},"Current":"#45"},{"$Type":"CADability.Attribute.ColorList","$TypeIndex":14,"$TypeVersion":0,"NamedColors":["#49","#50","#51"],"Current":"#49"},{"$Type":"CADability.Attribute.DimensionStyleList","$TypeIndex":15,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.DimensionStyle[]","$Value":["#52"]},"CurrentDimensionStyle":"#52"},{"$TypeIndex":4},{"$Type":"CADability.Projection","$TypeIndex":16,"$TypeVersion":-1,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,0,0,0,0,1,0,0,0,0,1,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#53","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[0,0,1]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[0,1,0,0,0,0,1,0,1,0,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[0,1,0],[0,0,1]]]},"Grid":"#55","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[1,0,0]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,0,0,0,0,0,1,0,0,-1,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,0,1]]]},"Grid":"#56","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[0,-1,0]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[-0.70710678118654757,0.70710678118654757,0,0,-0.40824829046386296,-0.40824829046386296,0.81649658092772592,0,0.57735026918962573,0.57735026918962573,0.57735026918962562,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#57","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[-1,-1,-1]}},{"$TypeIndex":3,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.Object","$ValueType":"System.Object","$Entries":[]},{"$Type":"CADability.GeoObject.Picture","$TypeIndex":17,"$TypeVersion":-1,"UserData":"#58","Layer":null,"Style":null,"UserAttributes":null,"Actuator":null,"Bitmap":"#59","Location":{"$Type":"CADability.GeoPoint","$Value":[1,2,3]},"DirectionWidth":{"$Type":"CADability.GeoVector","$Value":[70,0,0]},"DirectionHeight":{"$Type":"CADability.GeoVector","$Value":[0,50,0]},"Path":"C:\does\not\exist\pattern.png"},{"$TypeIndex":17,"UserData":"#60","Layer":null,"Style":null,"UserAttributes":null,"Actuator":null,"Bitmap":"#61","Location":{"$Type":"CADability.GeoPoint","$Value":[0,0,0]},"DirectionWidth":{"$Type":"CADability.GeoVector","$Value":[90,0,0]},"DirectionHeight":{"$Type":"CADability.GeoVector","$Value":[0,40,0]},"Path":"pattern.jpg"},{"$Type":"CADability.GeoObject.Icon","$TypeIndex":18,"$TypeVersion":-1,"Bitmap":"#62","Location":{"$Type":"CADability.GeoPoint","$Value":[5,5,0]},"Offsetx":{"$Type":"Int32","$Value":"1"},"Offsety":{"$Type":"Int32","$Value":"2"}},{"$TypeIndex":4},{"$Type":"CADability.Attribute.HatchStyleLines","$TypeIndex":19,"$TypeVersion":-1,"Name":"Lines","LineDistance":{"$Type":"Double","$Value":"10"},"LineAngle":"#63","MarginOffset":{"$Type":"Double","$Value":"0"},"NumberOffset":{"$Type":"Int32","$Value":"0"},"Offset":{"$Type":"Double","$Value":"0"},"Alternate":false,"ColorDef":"#49","LineWidth":"#32","LinePattern":"#45"},{"$Type":"CADability.Attribute.HatchStyleSolid","$TypeIndex":20,"$TypeVersion":-1,"Name":"Solid","Color":"#49"},{"$TypeIndex":20,"Name":"Standard [DimensionStyleFillSolid]","Color":"#49"},{"$Type":"CADability.Attribute.LineWidth","$TypeIndex":21,"$TypeVersion":0,"Name":"Thin","Width":0,"Scale":"Device"},{"$TypeIndex":21,"Name":"0.7 mm","Width":0.69999999999999996,"Scale":"Layout"},{"$TypeIndex":21,"Name":"0,3 mm","Width":0.29999999999999999,"Scale":"World"},{"$Type":"CADability.Attribute.Layer","$TypeIndex":22,"$TypeVersion":0,"Name":"2D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"3D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Background","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Dimension","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Standard","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Text","DisplayOrder":0,"Transparency":0},{"$Type":"CADability.Attribute.Style","$TypeIndex":23,"$TypeVersion":0,"Name":"2D","Layer":"#35","ColorDef":"#49","LinePattern":"#45","LineWidth":"#32","DimStyle":"#52","HatchStyle":"#29","Parent":"#15","DefaultFor":"Curves"},{"$TypeIndex":23,"Name":"3D","Layer":"#36","Parent":"#15","DefaultFor":"Solids"},{"$TypeIndex":23,"Name":"Dimension","Layer":"#38","Parent":"#15","DefaultFor":"Dimension"},{"$TypeIndex":23,"Name":"Text","Layer":"#40","Parent":"#15","DefaultFor":"Text"},{"$Type":"CADability.Attribute.LinePattern","$TypeIndex":24,"$TypeVersion":0,"Name":"Continous","Pattern":[],"Scale":"Layout"},{"$TypeIndex":24,"Name":"Dashed","Pattern":[2,2],"Scale":"Layout"},{"$TypeIndex":24,"Name":"Dash-dotted","Pattern":[4,2,0,0,0,2],"Scale":"Layout"},{"$TypeIndex":24,"Name":"Dotted","Pattern":[0,2],"Scale":"Layout"},{"$Type":"CADability.Attribute.ColorDef","$TypeIndex":25,"$TypeVersion":0,"Name":"Red","Source":"fromName","Color":-65536},{"$TypeIndex":25,"Name":"Green","Source":"fromName","Color":-16711936},{"$TypeIndex":25,"Name":"Blue","Source":"fromName","Color":-16776961},{"$Type":"CADability.Attribute.DimensionStyle","$TypeIndex":26,"$TypeVersion":-1,"Name":"Standard","TextPrefix":"","TextPostfix":"","TextPostfixAlt":"","PointFlags":{"$Type":"CADability.Attribute.DimensionStyle+EPointFlag","$Value":"0"},"AngleFlags":{"$Type":"CADability.Attribute.DimensionStyle+EAngleFlag","$Value":"0"},"RadiusFlags":{"$Type":"CADability.Attribute.DimensionStyle+ERadiusFlag","$Value":"0"},"SymbolFlags":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolFlag","$Value":"0"},"GeoFlags":{"$Type":"CADability.Attribute.DimensionStyle+EGeoFlag","$Value":"0"},"TextFont":"Arial","FontColor":null,"DimLineColor":"#49","DimLineWidth":null,"ExtLineColor":"#49","ExtLineWidth":null,"TextFlags":{"$Type":"CADability.Attribute.DimensionStyle+ETextFlag","$Value":"0"},"Types":{"$Type":"CADability.Attribute.DimensionStyle+ETypeFlag","$Value":"DimAllTypes"},"DimLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineOffset":{"$Type":"Double","$Value":"0"},"LineIncrement":{"$Type":"Double","$Value":"0"},"Symbol":{"$Type":"CADability.Attribute.DimensionStyle+ESymbol","$Value":"DimFilledArrow"},"SymbolSize":{"$Type":"Double","$Value":"3"},"Scale":{"$Type":"Double","$Value":"1"},"Round":{"$Type":"Double","$Value":"0.001"},"RoundAlt":{"$Type":"Double","$Value":"0.001"},"TextSize":{"$Type":"Double","$Value":"5"},"TextSizeTol":{"$Type":"Double","$Value":"3"},"TextDist":{"$Type":"Double","$Value":"0"},"PlusTolerance":{"$Type":"Double","$Value":"0"},"MinusTolerance":{"$Type":"Double","$Value":"0"},"CenterMarkSize":{"$Type":"Double","$Value":"2"},"ScaleAlt":{"$Type":"Double","$Value":"1"},"DimensionLineGap":{"$Type":"Double","$Value":"0"},"SymbolPlacement":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolPlacement","$Value":"DimAutomatic"},"FillColor":"#49","FixedLayer":null,"AngleText":{"$Type":"CADability.Attribute.DimensionStyle+EAngleText","$Value":"DegreeDecimal"},"UserData":null},{"$Type":"CADability.Grid","$TypeIndex":27,"$TypeVersion":-1,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$Type":"CADability.Matrix4","$TypeIndex":28,"$TypeVersion":-1,"Matrix":{"$Type":"System.Double[,]","$Value":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":4},{"$Type":"System.Drawing.Bitmap","$Assembly":"System.Drawing","$TypeIndex":29,"$TypeVersion":-1,"Data":{"$Type":"System.Byte[]","$Value":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,7,0,0,0,5,8,6,0,0,0,137,154,246,216,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,14,195,0,0,14,195,1,199,111,168,100,0,0,0,95,73,68,65,84,24,87,21,201,49,17,0,33,12,0,193,8,65,5,21,66,144,16,1,72,56,1,212,209,128,148,184,186,159,223,118,35,2,71,224,10,220,129,39,240,6,190,192,136,137,99,226,154,184,39,158,137,119,226,155,127,38,142,196,149,184,19,79,226,77,124,249,103,225,40,92,133,187,240,20,222,194,87,127,54,142,198,213,184,27,79,227,109,124,141,31,7,48,77,134,143,115,221,122,0,0,0,0,73,69,78,68,174,66,96,130]}},{"$TypeIndex":4},{"$TypeIndex":29,"Data":{"$Type":"System.Byte[]","$Value":[255,216,255,224,0,16,74,70,73,70,0,1,1,1,0,96,0,96,0,0,255,219,0,67,0,3,2,2,3,2,2,3,3,3,3,4,3,3,4,5,8,5,5,4,4,5,10,7,7,6,8,12,10,12,12,11,10,11,11,13,14,18,16,13,14,17,14,11,11,16,22,16,17,19,20,21,21,21,12,15,23,24,22,20,24,18,20,21,20,255,219,0,67,1,3,4,4,5,4,5,9,5,5,9,20,13,11,13,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,255,192,0,17,8,0,4,0,9,3,1,34,0,2,17,1,3,17,1,255,196,0,31,0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,255,196,0,181,16,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125,1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250,255,196,0,31,1,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,255,196,0,181,17,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119,0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250,255,218,0,12,3,1,0,2,17,3,17,0,63,0,227,254,25,252,20,240,159,238,191,226,95,233,220,127,133,123,47,252,41,63,9,255,0,207,135,234,63,194,138,41,102,248,188,79,214,31,239,37,247,179,239,188,57,199,226,255,0,177,163,251,217,116,251,79,252,207,255,217]}},{"$TypeIndex":29,"Data":{"$Type":"System.Byte[]","$Value":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,4,0,0,0,3,8,6,0,0,0,180,244,174,198,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,14,195,0,0,14,195,1,199,111,168,100,0,0,0,47,73,68,65,84,24,87,21,200,49,17,0,64,8,196,192,19,130,10,42,132,32,1,33,113,158,159,223,114,147,96,5,39,184,193,164,177,26,167,113,251,199,97,29,206,225,30,62,6,41,22,69,103,147,108,174,0,0,0,0,73,69,78,68,174,66,96,130]}},{"$Type":"CADability.Angle","$TypeIndex":30,"$TypeVersion":-1,"A":{"$Type":"Double","$Value":"0.78539816339744828"}}]} \ No newline at end of file diff --git a/tests/CADability.Tests/Files/CDB/pictures-1x.cdb b/tests/CADability.Tests/Files/CDB/pictures-1x.cdb new file mode 100644 index 000000000..d1d4c7622 Binary files /dev/null and b/tests/CADability.Tests/Files/CDB/pictures-1x.cdb differ diff --git a/tests/CADability.Tests/Files/CDB/pictures-1x.cdb.json b/tests/CADability.Tests/Files/CDB/pictures-1x.cdb.json new file mode 100644 index 000000000..906cf0b6f --- /dev/null +++ b/tests/CADability.Tests/Files/CDB/pictures-1x.cdb.json @@ -0,0 +1 @@ +{"CADability":{"URL":"http://www.cadability.de","Version":"1.6.0.0","Assembly":"CADability"},"Entities":[{"$Type":"CADability.Project","$TypeIndex":1,"$TypeVersion":1,"Models":["#1"],"ActiveModelIndex":0,"AttributeLists":"#2","UserData":"#3","SymbolList":"#4","Layouts":[],"GdiViews":[],"ProjectedModels":["#5","#6","#7","#8"],"NamedValues":"#9","FilterList":"#10","AnimatedViews":[],"ActiveViewName":null},{"$Type":"CADability.Model","$TypeIndex":2,"$TypeVersion":0,"GeoObjectList":"#11","Name":"Standard model","Unit":"millimeter","DefaultScale":1,"LineStyleScale":1,"MinExtend":[1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308,1.7976931348623157E+308,-1.7976931348623157E+308],"AllDrives":null,"AllSchedules":null,"UserData":null},{"$Type":"CADability.JSonDictionary","$TypeIndex":3,"$TypeVersion":0,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.String","$ValueType":"System.Object","$Entries":[["ColorList","#12"],["HatchStyleList","#13"],["LayerList","#14"],["LineWidthList","#15"],["StyleList","#16"],["DimensionStyleList","#17"],["LinePatternList","#18"]]},{"$Type":"CADability.UserData","$TypeIndex":4,"$TypeVersion":0},{"$Type":"CADability.GeoObject.GeoObjectList","$TypeIndex":5,"$TypeVersion":0,"List":[],"UserData":"#19"},{"$Type":"CADability.ProjectedModel","$TypeIndex":6,"$TypeVersion":0,"Name":"Top","Model":"#1","Projection":"#20","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Right","Model":"#1","Projection":"#21","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Front","Model":"#1","Projection":"#22","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$TypeIndex":6,"Name":"Perspective","Model":"#1","Projection":"#23","VisibleLayers":[],"FixPoint":[0,0,0],"Distance":0},{"$Type":"CADability.NamedValuesProperty","$TypeIndex":7,"$TypeVersion":0,"NamedValues":"#24"},{"$Type":"CADability.Attribute.FilterList","$TypeIndex":8,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.Filter[]","$Value":[]}},{"$TypeIndex":5,"List":["#25","#26","#27"],"UserData":"#28"},{"$Type":"CADability.Attribute.ColorList","$TypeIndex":9,"$TypeVersion":0,"NamedColors":["#29","#30","#31"],"Current":"#29"},{"$Type":"CADability.Attribute.HatchStyleList","$TypeIndex":10,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.HatchStyle[]","$Value":["#32","#33","#34"]},"Current":"#33"},{"$Type":"CADability.Attribute.LayerList","$TypeIndex":11,"$TypeVersion":0,"Entries":["#35","#36","#37","#38","#39","#40"],"Current":"#39"},{"$Type":"CADability.Attribute.LineWidthList","$TypeIndex":12,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#41","#42","#43"]},"Current":"#41"},{"$Type":"CADability.Attribute.StyleList","$TypeIndex":13,"$TypeVersion":0,"UnsortedEntries":["#44","#45","#46","#47"],"Current":"#44"},{"$Type":"CADability.Attribute.DimensionStyleList","$TypeIndex":14,"$TypeVersion":-1,"UnsortedEntries":{"$Type":"CADability.Attribute.DimensionStyle[]","$Value":["#48"]},"CurrentDimensionStyle":"#48"},{"$Type":"CADability.Attribute.LinePatternList","$TypeIndex":15,"$TypeVersion":-1,"Entries":{"$Type":"System.Collections.ArrayList","$Value":["#49","#50","#51","#52"]},"Current":"#49"},{"$TypeIndex":4},{"$Type":"CADability.Projection","$TypeIndex":16,"$TypeVersion":-1,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,0,0,0,0,1,0,0,0,0,1,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#53","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[0,0,1]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[0,1,0,0,0,0,1,0,1,0,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[0,1,0],[0,0,1]]]},"Grid":"#55","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[1,0,0]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[1,-0,0,0,0,-0,1,0,0,-1,0,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[-0,0,1]]]},"Grid":"#56","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[0,-1,0]}},{"$TypeIndex":16,"UnscaledProjection":{"$Type":"CADability.ModOp","$Value":[-0.70710678118654757,0.70710678118654757,0,0,-0.40824829046386296,-0.40824829046386296,0.81649658092772592,0,0.57735026918962573,0.57735026918962573,0.57735026918962562,0,1,"CompoundTrsf"]},"PlacementFactor":{"$Type":"Double","$Value":"1"},"PlacementX":{"$Type":"Double","$Value":"0"},"PlacementY":{"$Type":"Double","$Value":"0"},"DrawingPlane":{"$Type":"CADability.Plane","$Value":[[[0,0,0],[1,0,0],[0,1,0]]]},"Grid":"#57","LayoutFactor":{"$Type":"Double","$Value":"1"},"ShowFaces":true,"LineWidthFactor":{"$Type":"Double","$Value":"10"},"UseLineWidth":true,"ShowDrawingPlane":false,"IsPerspective":false,"PerspectiveProjection":"#54","Direction":{"$Type":"CADability.GeoVector","$Value":[-1,-1,-1]}},{"$TypeIndex":3,"$OriginalType":"System.Collections.Hashtable","$KeyType":"System.Object","$ValueType":"System.Object","$Entries":[]},{"$Type":"CADability.GeoObject.Picture","$TypeIndex":17,"$TypeVersion":-1,"UserData":"#58","Layer":null,"Style":null,"UserAttributes":null,"Actuator":null,"Bitmap":"#59","Location":{"$Type":"CADability.GeoPoint","$Value":[1,2,3]},"DirectionWidth":{"$Type":"CADability.GeoVector","$Value":[70,0,0]},"DirectionHeight":{"$Type":"CADability.GeoVector","$Value":[0,50,0]},"Path":"C:\does\not\exist\pattern.png"},{"$TypeIndex":17,"UserData":"#60","Layer":null,"Style":null,"UserAttributes":null,"Actuator":null,"Bitmap":"#61","Location":{"$Type":"CADability.GeoPoint","$Value":[0,0,0]},"DirectionWidth":{"$Type":"CADability.GeoVector","$Value":[90,0,0]},"DirectionHeight":{"$Type":"CADability.GeoVector","$Value":[0,40,0]},"Path":"pattern.jpg"},{"$Type":"CADability.GeoObject.Icon","$TypeIndex":18,"$TypeVersion":-1,"Bitmap":"#62","Location":{"$Type":"CADability.GeoPoint","$Value":[5,5,0]},"Offsetx":{"$Type":"Int32","$Value":"1"},"Offsety":{"$Type":"Int32","$Value":"2"}},{"$TypeIndex":4},{"$Type":"CADability.Attribute.ColorDef","$TypeIndex":19,"$TypeVersion":0,"Name":"Red","Source":"fromName","Color":-65536},{"$TypeIndex":19,"Name":"Green","Source":"fromName","Color":-16711936},{"$TypeIndex":19,"Name":"Blue","Source":"fromName","Color":-16776961},{"$Type":"CADability.Attribute.HatchStyleLines","$TypeIndex":20,"$TypeVersion":-1,"Name":"Lines","LineDistance":{"$Type":"Double","$Value":"10"},"LineAngle":"#63","MarginOffset":{"$Type":"Double","$Value":"0"},"NumberOffset":{"$Type":"Int32","$Value":"0"},"Offset":{"$Type":"Double","$Value":"0"},"Alternate":false,"ColorDef":"#29","LineWidth":"#41","LinePattern":"#49"},{"$Type":"CADability.Attribute.HatchStyleSolid","$TypeIndex":21,"$TypeVersion":-1,"Name":"Solid","Color":"#29"},{"$TypeIndex":21,"Name":"Standard [DimensionStyleFillSolid]","Color":"#29"},{"$Type":"CADability.Attribute.Layer","$TypeIndex":22,"$TypeVersion":0,"Name":"2D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"3D","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Background","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Dimension","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Standard","DisplayOrder":0,"Transparency":0},{"$TypeIndex":22,"Name":"Text","DisplayOrder":0,"Transparency":0},{"$Type":"CADability.Attribute.LineWidth","$TypeIndex":23,"$TypeVersion":0,"Name":"Thin","Width":0,"Scale":"Device"},{"$TypeIndex":23,"Name":"0.7 mm","Width":0.69999999999999996,"Scale":"Layout"},{"$TypeIndex":23,"Name":"0,3 mm","Width":0.29999999999999999,"Scale":"World"},{"$Type":"CADability.Attribute.Style","$TypeIndex":24,"$TypeVersion":0,"Name":"2D","Layer":"#35","ColorDef":"#29","LinePattern":"#49","LineWidth":"#41","DimStyle":"#48","HatchStyle":"#32","Parent":"#16","DefaultFor":"Curves"},{"$TypeIndex":24,"Name":"3D","Layer":"#36","Parent":"#16","DefaultFor":"Solids"},{"$TypeIndex":24,"Name":"Dimension","Layer":"#38","Parent":"#16","DefaultFor":"Dimension"},{"$TypeIndex":24,"Name":"Text","Layer":"#40","Parent":"#16","DefaultFor":"Text"},{"$Type":"CADability.Attribute.DimensionStyle","$TypeIndex":25,"$TypeVersion":-1,"Name":"Standard","TextPrefix":"","TextPostfix":"","TextPostfixAlt":"","PointFlags":{"$Type":"CADability.Attribute.DimensionStyle+EPointFlag","$Value":"0"},"AngleFlags":{"$Type":"CADability.Attribute.DimensionStyle+EAngleFlag","$Value":"0"},"RadiusFlags":{"$Type":"CADability.Attribute.DimensionStyle+ERadiusFlag","$Value":"0"},"SymbolFlags":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolFlag","$Value":"0"},"GeoFlags":{"$Type":"CADability.Attribute.DimensionStyle+EGeoFlag","$Value":"0"},"TextFont":"Arial","FontColor":null,"DimLineColor":"#29","DimLineWidth":null,"ExtLineColor":"#29","ExtLineWidth":null,"TextFlags":{"$Type":"CADability.Attribute.DimensionStyle+ETextFlag","$Value":"0"},"Types":{"$Type":"CADability.Attribute.DimensionStyle+ETypeFlag","$Value":"DimAllTypes"},"DimLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineExtension":{"$Type":"Double","$Value":"0"},"ExtLineOffset":{"$Type":"Double","$Value":"0"},"LineIncrement":{"$Type":"Double","$Value":"0"},"Symbol":{"$Type":"CADability.Attribute.DimensionStyle+ESymbol","$Value":"DimFilledArrow"},"SymbolSize":{"$Type":"Double","$Value":"3"},"Scale":{"$Type":"Double","$Value":"1"},"Round":{"$Type":"Double","$Value":"0.001"},"RoundAlt":{"$Type":"Double","$Value":"0.001"},"TextSize":{"$Type":"Double","$Value":"5"},"TextSizeTol":{"$Type":"Double","$Value":"3"},"TextDist":{"$Type":"Double","$Value":"0"},"PlusTolerance":{"$Type":"Double","$Value":"0"},"MinusTolerance":{"$Type":"Double","$Value":"0"},"CenterMarkSize":{"$Type":"Double","$Value":"2"},"ScaleAlt":{"$Type":"Double","$Value":"1"},"DimensionLineGap":{"$Type":"Double","$Value":"0"},"SymbolPlacement":{"$Type":"CADability.Attribute.DimensionStyle+ESymbolPlacement","$Value":"DimAutomatic"},"FillColor":"#29","FixedLayer":null,"AngleText":{"$Type":"CADability.Attribute.DimensionStyle+EAngleText","$Value":"DegreeDecimal"},"UserData":null},{"$Type":"CADability.Attribute.LinePattern","$TypeIndex":26,"$TypeVersion":0,"Name":"Continous","Pattern":[],"Scale":"Layout"},{"$TypeIndex":26,"Name":"Dashed","Pattern":[2,2],"Scale":"Layout"},{"$TypeIndex":26,"Name":"Dash-dotted","Pattern":[4,2,0,0,0,2],"Scale":"Layout"},{"$TypeIndex":26,"Name":"Dotted","Pattern":[0,2],"Scale":"Layout"},{"$Type":"CADability.Grid","$TypeIndex":27,"$TypeVersion":-1,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$Type":"CADability.Matrix4","$TypeIndex":28,"$TypeVersion":-1,"Matrix":{"$Type":"System.Double[,]","$Value":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":27,"XDistance":{"$Type":"Double","$Value":"10"},"YDistance":{"$Type":"Double","$Value":"10"},"DisplayMode":{"$Type":"CADability.Grid+Appearance","$Value":"dots"},"Show":false},{"$TypeIndex":4},{"$Type":"System.Drawing.Bitmap","$Assembly":"System.Drawing.Common","$TypeIndex":29,"$TypeVersion":-1,"Data":{"$Type":"System.Byte[]","$Value":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,7,0,0,0,5,8,6,0,0,0,137,154,246,216,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,14,195,0,0,14,195,1,199,111,168,100,0,0,0,95,73,68,65,84,24,87,21,201,49,17,0,33,12,0,193,8,65,5,21,66,144,16,1,72,56,1,212,209,128,148,184,186,159,223,118,35,2,71,224,10,220,129,39,240,6,190,192,136,137,99,226,154,184,39,158,137,119,226,155,127,38,142,196,149,184,19,79,226,77,124,249,103,225,40,92,133,187,240,20,222,194,87,127,54,142,198,213,184,27,79,227,109,124,141,31,7,48,77,134,143,115,221,122,0,0,0,0,73,69,78,68,174,66,96,130]}},{"$TypeIndex":4},{"$TypeIndex":29,"Data":{"$Type":"System.Byte[]","$Value":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,9,0,0,0,4,8,2,0,0,0,211,109,130,153,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,14,195,0,0,14,195,1,199,111,168,100,0,0,0,123,73,68,65,84,24,87,1,112,0,143,255,0,0,5,106,15,10,112,42,9,113,70,9,112,101,10,113,126,10,111,154,9,110,178,12,114,203,14,117,0,10,46,122,29,51,127,57,50,128,83,49,126,114,50,128,142,50,127,168,50,126,193,52,130,216,53,132,0,9,96,123,27,101,128,56,100,129,84,99,128,114,101,129,140,100,127,166,100,127,192,102,130,216,103,133,0,23,137,138,42,142,144,71,140,145,99,140,144,128,142,145,155,140,143,181,141,142,207,142,146,230,143,149,50,1,44,90,244,191,46,208,0,0,0,0,73,69,78,68,174,66,96,130]}},{"$TypeIndex":29,"Data":{"$Type":"System.Byte[]","$Value":[137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,4,0,0,0,3,8,6,0,0,0,180,244,174,198,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,14,195,0,0,14,195,1,199,111,168,100,0,0,0,47,73,68,65,84,24,87,21,200,49,17,0,64,8,196,192,19,130,10,42,132,32,1,33,113,158,159,223,114,147,96,5,39,184,193,164,177,26,167,113,251,199,97,29,206,225,30,62,6,41,22,69,103,147,108,174,0,0,0,0,73,69,78,68,174,66,96,130]}},{"$Type":"CADability.Angle","$TypeIndex":30,"$TypeVersion":-1,"A":{"$Type":"Double","$Value":"0.78539816339744828"}}]} \ No newline at end of file diff --git a/tests/CADability.Tests/Files/Fonts/InterCffTest.otf b/tests/CADability.Tests/Files/Fonts/InterCffTest.otf new file mode 100644 index 000000000..8afd85e9d 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 000000000..381571f5c --- /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 000000000..0e4fba293 Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/InterCidTest.otf differ diff --git a/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.reference.json b/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.reference.json new file mode 100644 index 000000000..a424a5dfe --- /dev/null +++ b/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.reference.json @@ -0,0 +1 @@ +{"family":"Liberation Sans","subfamily":"Regular","full":"Liberation Sans","unitsPerEm":2048,"ascender":1854,"descender":-434,"lineGap":67,"winAscent":1854,"winDescent":434,"glyphCount":2620,"kerningPairCount":908,"kerning":[{"left":"A","right":"V","value":-152},{"left":"T","right":"o","value":-227},{"left":"L","right":"T","value":-152},{"left":"A","right":"T","value":-152},{"left":"Y","right":"a","value":-152},{"left":"F","right":".","value":-227}],"glyphs":[{"char":" ","glyph":3,"advance":569,"composite":false,"contours":[]},{"char":"!","glyph":4,"advance":569,"composite":false,"contours":[[[359,397,1],[211,397,1],[187,1409,1],[383,1409,1]],[[185,0,1],[185,201,1],[379,201,1],[379,0,1]]]},{"char":"\"","glyph":5,"advance":727,"composite":false,"contours":[[[618,966,1],[476,966,1],[456,1409,1],[640,1409,1]],[[249,966,1],[108,966,1],[87,1409,1],[271,1409,1]]]},{"char":"#","glyph":6,"advance":1139,"composite":false,"contours":[[[896,885,1],[818,516,1],[1078,516,1],[1078,408,1],[795,408,1],[707,0,1],[597,0,1],[683,408,1],[320,408,1],[236,0,1],[126,0,1],[210,408,1],[9,408,1],[9,516,1],[234,516,1],[312,885,1],[60,885,1],[60,993,1],[334,993,1],[423,1401,1],[533,1401,1],[445,993,1],[808,993,1],[896,1401,1],[1006,1401,1],[918,993,1],[1129,993,1],[1129,885,1]],[[425,885,1],[345,516,1],[707,516,1],[785,885,1]]]},{"char":"$","glyph":7,"advance":1139,"composite":false,"contours":[[[518,20,1],[92,38,0],[22,379,1],[192,416,1],[217,297,0],[376,177,0],[518,168,1],[518,664,1],[341,708,0],[206,778,0],[123,869,0],[86,973,0],[86,1046,1],[86,1201,0],[311,1376,0],[518,1385,1],[518,1516,1],[642,1516,1],[642,1385,1],[829,1376,0],[1033,1225,0],[1075,1065,1],[901,1032,1],[881,1126,0],[759,1231,0],[642,1242,1],[642,797,1],[821,755,0],[972,685,0],[1060,597,0],[1106,477,0],[1106,396,1],[1106,231,0],[864,31,0],[642,20,1],[642,-142,1],[518,-142,1]],[[934,394,1],[934,459,0],[882,543,0],[784,598,0],[642,635,1],[642,167,1],[783,176,0],[934,292,0]],[[258,1048,1],[258,989,0],[308,907,0],[407,851,0],[518,823,1],[518,1244,1],[258,1230,0]]]},{"char":"%","glyph":8,"advance":1821,"composite":false,"contours":[[[1748,434,1],[1748,219,0],[1586,-12,0],[1428,-12,1],[1272,-12,0],[1113,213,0],[1113,434,1],[1113,662,0],[1266,885,0],[1432,885,1],[1596,885,0],[1748,656,0]],[[527,0,1],[372,0,1],[1294,1409,1],[1451,1409,1]],[[394,1421,1],[553,1421,0],[707,1197,0],[707,975,1],[707,758,0],[548,524,0],[390,524,1],[232,524,0],[73,756,0],[73,975,1],[73,1198,0],[227,1421,0]],[[1600,434,1],[1600,613,0],[1523,774,0],[1432,774,1],[1341,774,0],[1260,616,0],[1260,434,1],[1260,263,0],[1339,98,0],[1430,98,1],[1518,98,0],[1600,265,0]],[[560,975,1],[560,1151,0],[484,1313,0],[394,1313,1],[300,1313,0],[220,1154,0],[220,975,1],[220,802,0],[300,637,0],[392,637,1],[479,637,0],[560,805,0]]]},{"char":"&","glyph":9,"advance":1366,"composite":false,"contours":[[[1193,-12,1],[1016,-12,0],[895,115,1],[820,50,0],[628,-20,0],[523,-20,1],[308,-20,0],[72,187,0],[72,371,1],[72,649,0],[415,800,1],[382,862,0],[334,1032,0],[334,1102,1],[334,1252,0],[517,1417,0],[685,1417,1],[836,1417,0],[1021,1265,0],[1021,1133,1],[1021,1015,0],[838,831,0],[612,741,1],[723,536,0],[905,329,1],[1018,495,0],[1076,739,1],[1221,696,1],[1158,447,0],[1009,227,1],[1105,129,0],[1217,129,1],[1288,129,0],[1334,145,1],[1334,10,1],[1278,-12,0]],[[869,1133,1],[869,1205,0],[769,1296,0],[683,1296,1],[587,1296,0],[487,1193,0],[487,1102,1],[487,988,0],[552,858,1],[683,911,0],[806,989,0],[869,1079,0]],[[795,217,1],[597,451,0],[476,674,1],[240,574,0],[240,373,1],[240,252,0],[395,111,0],[529,111,1],[600,111,0],[742,166,0]]]},{"char":"'","glyph":10,"advance":391,"composite":false,"contours":[[[266,966,1],[125,966,1],[104,1409,1],[288,1409,1]]]},{"char":"(","glyph":11,"advance":682,"composite":false,"contours":[[[127,532,1],[127,821,0],[308,1281,0],[496,1484,1],[670,1484,1],[483,1276,0],[308,808,0],[308,530,1],[308,253,0],[481,-213,0],[670,-424,1],[496,-424,1],[307,-220,0],[127,241,0],[127,528,1]]]},{"char":")","glyph":12,"advance":682,"composite":false,"contours":[[[555,528,1],[555,239,0],[374,-221,0],[186,-424,1],[12,-424,1],[200,-214,0],[374,251,0],[374,530,1],[374,809,0],[199,1275,0],[12,1484,1],[186,1484,1],[375,1280,0],[555,819,0],[555,532,1]]]},{"char":"*","glyph":13,"advance":797,"composite":false,"contours":[[[456,1114,1],[720,1217,1],[765,1085,1],[483,1012,1],[668,762,1],[549,690,1],[399,948,1],[243,692,1],[124,764,1],[313,1012,1],[33,1085,1],[78,1219,1],[345,1112,1],[333,1409,1],[469,1409,1]]]},{"char":"+","glyph":14,"advance":1196,"composite":false,"contours":[[[671,608,1],[671,180,1],[524,180,1],[524,608,1],[100,608,1],[100,754,1],[524,754,1],[524,1182,1],[671,1182,1],[671,754,1],[1095,754,1],[1095,608,1]]]},{"char":",","glyph":15,"advance":569,"composite":false,"contours":[[[385,219,1],[385,51,1],[385,-55,0],[347,-197,0],[307,-262,1],[184,-262,1],[278,-126,0],[278,0,1],[190,0,1],[190,219,1]]]},{"char":"-","glyph":16,"advance":682,"composite":false,"contours":[[[91,464,1],[91,624,1],[591,624,1],[591,464,1]]]},{"char":".","glyph":17,"advance":569,"composite":false,"contours":[[[187,0,1],[187,219,1],[382,219,1],[382,0,1]]]},{"char":"/","glyph":18,"advance":569,"composite":false,"contours":[[[0,-20,1],[411,1484,1],[569,1484,1],[162,-20,1]]]},{"char":"0","glyph":19,"advance":1139,"composite":false,"contours":[[[1059,705,1],[1059,352,0],[810,-20,0],[567,-20,1],[324,-20,0],[80,350,0],[80,705,1],[80,1068,0],[317,1430,0],[573,1430,1],[822,1430,0],[1059,1064,0]],[[876,705,1],[876,1010,0],[735,1284,0],[573,1284,1],[407,1284,0],[262,1014,0],[262,705,1],[262,405,0],[409,127,0],[569,127,1],[728,127,0],[876,411,0]]]},{"char":"1","glyph":20,"advance":1139,"composite":false,"contours":[[[156,0,1],[156,153,1],[515,153,1],[515,1237,1],[197,1010,1],[197,1180,1],[530,1409,1],[696,1409,1],[696,153,1],[1039,153,1],[1039,0,1]]]},{"char":"2","glyph":21,"advance":1139,"composite":false,"contours":[[[103,0,1],[103,127,1],[154,244,0],[301,423,0],[463,568,0],[622,692,0],[750,816,0],[829,952,0],[829,1038,1],[829,1154,0],[693,1282,0],[572,1282,1],[457,1282,0],[308,1157,0],[295,1044,1],[111,1061,1],[131,1230,0],[378,1430,0],[572,1430,1],[785,1430,0],[1014,1229,0],[1014,1044,1],[1014,962,0],[939,800,0],[791,638,0],[582,468,1],[467,374,0],[331,223,0],[301,153,1],[1036,153,1],[1036,0,1]]]},{"char":"3","glyph":22,"advance":1139,"composite":false,"contours":[[[1049,389,1],[1049,194,0],[801,-20,0],[571,-20,1],[357,-20,0],[102,173,0],[78,362,1],[264,379,1],[300,129,0],[571,129,1],[707,129,0],[862,263,0],[862,395,1],[862,510,0],[685,639,0],[518,639,1],[416,639,1],[416,795,1],[514,795,1],[662,795,0],[825,924,0],[825,1038,1],[825,1151,0],[692,1282,0],[561,1282,1],[442,1282,0],[295,1160,0],[283,1049,1],[102,1063,1],[122,1236,0],[369,1430,0],[563,1430,1],[775,1430,0],[1010,1233,0],[1010,1057,1],[1010,922,0],[859,753,0],[715,723,1],[715,719,1],[873,702,0],[1049,524,0]]]},{"char":"4","glyph":23,"advance":1139,"composite":false,"contours":[[[881,319,1],[881,0,1],[711,0,1],[711,319,1],[47,319,1],[47,459,1],[692,1409,1],[881,1409,1],[881,461,1],[1079,461,1],[1079,319,1]],[[711,1206,1],[709,1200,0],[657,1106,0],[644,1087,1],[283,555,1],[229,481,1],[213,461,1],[711,461,1]]]},{"char":"5","glyph":24,"advance":1139,"composite":false,"contours":[[[1053,459,1],[1053,236,0],[788,-20,0],[553,-20,1],[356,-20,0],[114,152,0],[82,315,1],[264,336,1],[321,127,0],[557,127,1],[702,127,0],[866,302,0],[866,455,1],[866,588,0],[701,752,0],[561,752,1],[488,752,0],[362,706,0],[299,651,1],[123,651,1],[170,1409,1],[971,1409,1],[971,1256,1],[334,1256,1],[307,809,1],[424,899,0],[598,899,1],[806,899,0],[1053,655,0]]]},{"char":"6","glyph":25,"advance":1139,"composite":false,"contours":[[[1049,461,1],[1049,238,0],[807,-20,0],[594,-20,1],[356,-20,0],[104,334,0],[104,672,1],[104,1038,0],[366,1430,0],[608,1430,1],[927,1430,0],[1010,1143,1],[838,1112,1],[785,1284,0],[606,1284,1],[452,1284,0],[283,997,0],[283,725,1],[332,816,0],[510,911,0],[625,911,1],[820,911,0],[1049,667,0]],[[866,453,1],[866,606,0],[716,772,0],[582,772,1],[456,772,0],[301,625,0],[301,496,1],[301,333,0],[462,125,0],[588,125,1],[718,125,0],[866,300,0]]]},{"char":"7","glyph":26,"advance":1139,"composite":false,"contours":[[[1036,1263,1],[820,933,0],[642,559,0],[553,195,0],[553,0,1],[365,0,1],[365,270,0],[594,867,0],[862,1256,1],[105,1256,1],[105,1409,1],[1036,1409,1]]]},{"char":"8","glyph":27,"advance":1139,"composite":false,"contours":[[[1050,393,1],[1050,198,0],[802,-20,0],[570,-20,1],[344,-20,0],[89,194,0],[89,391,1],[89,529,0],[247,717,0],[370,737,1],[370,741,1],[255,768,0],[122,948,0],[122,1069,1],[122,1230,0],[363,1430,0],[566,1430,1],[774,1430,0],[1015,1234,0],[1015,1067,1],[1015,946,0],[881,766,0],[765,743,1],[765,739,1],[900,717,0],[1050,532,0]],[[828,1057,1],[828,1296,0],[566,1296,1],[439,1296,0],[306,1176,0],[306,1057,1],[306,936,0],[443,809,0],[568,809,1],[695,809,0],[828,926,0]],[[863,410,1],[863,541,0],[707,674,0],[566,674,1],[429,674,0],[275,531,0],[275,406,1],[275,115,0],[572,115,1],[719,115,0],[863,256,0]]]},{"char":"9","glyph":28,"advance":1139,"composite":false,"contours":[[[1042,733,1],[1042,370,0],[777,-20,0],[532,-20,1],[367,-20,0],[168,119,0],[125,274,1],[297,301,1],[351,125,0],[535,125,1],[690,125,0],[860,413,0],[864,680,1],[824,590,0],[630,481,0],[514,481,1],[324,481,0],[96,741,0],[96,956,1],[96,1177,0],[344,1430,0],[565,1430,1],[800,1430,0],[1042,1082,0]],[[846,907,1],[846,1077,0],[690,1284,0],[559,1284,1],[429,1284,0],[279,1107,0],[279,956,1],[279,802,0],[429,623,0],[557,623,1],[635,623,0],[769,694,0],[846,824,0]]]},{"char":":","glyph":29,"advance":569,"composite":false,"contours":[[[187,875,1],[187,1082,1],[382,1082,1],[382,875,1]],[[187,0,1],[187,207,1],[382,207,1],[382,0,1]]]},{"char":";","glyph":30,"advance":569,"composite":false,"contours":[[[385,207,1],[385,51,1],[385,-55,0],[347,-197,0],[307,-262,1],[184,-262,1],[278,-126,0],[278,0,1],[190,0,1],[190,207,1]],[[190,875,1],[190,1082,1],[385,1082,1],[385,875,1]]]},{"char":"<","glyph":31,"advance":1196,"composite":false,"contours":[[[101,571,1],[101,776,1],[1096,1194,1],[1096,1040,1],[238,674,1],[1096,307,1],[1096,154,1]]]},{"char":"=","glyph":32,"advance":1196,"composite":false,"contours":[[[100,856,1],[100,1004,1],[1095,1004,1],[1095,856,1]],[[100,344,1],[100,492,1],[1095,492,1],[1095,344,1]]]},{"char":">","glyph":33,"advance":1196,"composite":false,"contours":[[[101,154,1],[101,307,1],[959,674,1],[101,1040,1],[101,1194,1],[1096,776,1],[1096,571,1]]]},{"char":"?","glyph":34,"advance":1139,"composite":false,"contours":[[[1063,1032,1],[1063,957,0],[1019,839,0],[937,739,0],[844,671,1],[764,612,1],[692,560,0],[622,445,0],[621,377,1],[446,377,1],[448,446,0],[487,550,0],[549,630,0],[627,693,0],[707,750,0],[784,807,0],[844,877,0],[881,965,0],[881,1024,1],[881,1138,0],[726,1270,0],[586,1270,1],[446,1270,0],[282,1130,0],[268,1008,1],[84,1020,1],[110,1218,0],[370,1430,0],[584,1430,1],[807,1430,0],[1063,1219,0]],[[438,0,1],[438,201,1],[633,201,1],[633,0,1]]]},{"char":"@","glyph":35,"advance":2079,"composite":false,"contours":[[[1902,755,1],[1902,569,0],[1787,268,0],[1582,104,0],[1455,104,1],[1356,104,0],[1248,192,0],[1248,280,1],[1251,350,1],[1245,350,1],[1179,227,0],[984,104,0],[871,104,1],[714,104,0],[541,308,0],[541,489,1],[541,653,0],[670,935,0],[902,1101,0],[1043,1101,1],[1262,1101,0],[1344,919,1],[1350,919,1],[1389,1079,1],[1545,1079,1],[1429,573,1],[1392,409,0],[1392,320,1],[1392,226,0],[1473,226,1],[1553,226,0],[1688,364,0],[1766,606,0],[1766,753,1],[1766,932,0],[1612,1209,0],[1322,1358,0],[1128,1358,1],[886,1358,0],[514,1144,0],[302,741,0],[302,491,1],[302,298,0],[459,3,0],[756,-155,0],[954,-155,1],[1099,-155,0],[1397,-80,0],[1557,7,1],[1612,-105,1],[1467,-192,0],[1128,-283,0],[954,-283,1],[713,-283,0],[352,-92,0],[161,261,0],[161,491,1],[161,771,0],[410,1229,0],[852,1484,0],[1126,1484,1],[1367,1484,0],[1717,1303,0],[1902,973,0]],[[1296,747,1],[1296,849,0],[1164,974,0],[1054,974,1],[953,974,0],[796,847,0],[706,622,0],[706,491,1],[706,371,0],[801,235,0],[900,235,1],[1025,235,0],[1233,445,0],[1273,602,1],[1296,694,0]]]},{"char":"A","glyph":36,"advance":1366,"composite":false,"contours":[[[1167,0,1],[1006,412,1],[364,412,1],[202,0,1],[4,0,1],[579,1409,1],[796,1409,1],[1362,0,1]],[[685,1265,1],[676,1237,1],[651,1154,0],[602,1024,1],[422,561,1],[949,561,1],[768,1026,1],[740,1095,0],[712,1182,1]]]},{"char":"B","glyph":37,"advance":1366,"composite":false,"contours":[[[1258,397,1],[1258,209,0],[984,0,0],[740,0,1],[168,0,1],[168,1409,1],[680,1409,1],[1176,1409,0],[1176,1067,1],[1176,942,0],[1036,772,0],[908,743,1],[1076,723,0],[1258,538,0]],[[984,1044,1],[984,1158,0],[828,1256,0],[680,1256,1],[359,1256,1],[359,810,1],[680,810,1],[833,810,0],[984,925,0]],[[1065,412,1],[1065,661,0],[715,661,1],[359,661,1],[359,153,1],[730,153,1],[905,153,0],[1065,283,0]]]},{"char":"C","glyph":38,"advance":1479,"composite":false,"contours":[[[792,1274,1],[558,1274,0],[298,973,0],[298,711,1],[298,452,0],[569,137,0],[800,137,1],[1096,137,0],[1245,430,1],[1401,352,1],[1314,170,0],[999,-20,0],[791,-20,1],[578,-20,0],[267,157,0],[104,486,0],[104,711,1],[104,1048,0],[468,1430,0],[790,1430,1],[1015,1430,0],[1317,1254,0],[1388,1081,1],[1207,1021,1],[1158,1144,0],[941,1274,0]]]},{"char":"D","glyph":39,"advance":1479,"composite":false,"contours":[[[1381,719,1],[1381,501,0],[1211,174,0],[899,0,0],[695,0,1],[168,0,1],[168,1409,1],[634,1409,1],[992,1409,0],[1381,1050,0]],[[1189,719,1],[1189,981,0],[902,1256,0],[630,1256,1],[359,1256,1],[359,153,1],[673,153,1],[828,153,0],[1063,289,0],[1189,545,0]]]},{"char":"E","glyph":40,"advance":1366,"composite":false,"contours":[[[168,0,1],[168,1409,1],[1237,1409,1],[1237,1253,1],[359,1253,1],[359,801,1],[1177,801,1],[1177,647,1],[359,647,1],[359,156,1],[1278,156,1],[1278,0,1]]]},{"char":"F","glyph":41,"advance":1251,"composite":false,"contours":[[[359,1253,1],[359,729,1],[1145,729,1],[1145,571,1],[359,571,1],[359,0,1],[168,0,1],[168,1409,1],[1169,1409,1],[1169,1253,1]]]},{"char":"G","glyph":42,"advance":1593,"composite":false,"contours":[[[103,711,1],[103,1054,0],[471,1430,0],[804,1430,1],[1038,1430,0],[1330,1272,0],[1409,1098,1],[1227,1044,1],[1167,1164,0],[956,1274,0],[799,1274,1],[555,1274,0],[297,979,0],[297,711,1],[297,444,0],[571,135,0],[813,135,1],[951,135,0],[1190,219,0],[1264,291,1],[1264,545,1],[843,545,1],[843,705,1],[1440,705,1],[1440,219,1],[1328,105,0],[1003,-20,0],[813,-20,1],[592,-20,0],[272,156,0],[103,487,0]]]},{"char":"H","glyph":43,"advance":1479,"composite":false,"contours":[[[1121,0,1],[1121,653,1],[359,653,1],[359,0,1],[168,0,1],[168,1409,1],[359,1409,1],[359,813,1],[1121,813,1],[1121,1409,1],[1312,1409,1],[1312,0,1]]]},{"char":"I","glyph":44,"advance":569,"composite":false,"contours":[[[189,0,1],[189,1409,1],[380,1409,1],[380,0,1]]]},{"char":"J","glyph":45,"advance":1024,"composite":false,"contours":[[[457,-20,1],[99,-20,0],[32,350,1],[219,381,1],[237,265,0],[363,135,0],[458,135,1],[562,135,0],[682,278,0],[682,416,1],[682,1253,1],[411,1253,1],[411,1409,1],[872,1409,1],[872,420,1],[872,215,0],[650,-20,0]]]},{"char":"K","glyph":46,"advance":1366,"composite":false,"contours":[[[1106,0,1],[543,680,1],[359,540,1],[359,0,1],[168,0,1],[168,1409,1],[359,1409,1],[359,703,1],[1038,1409,1],[1263,1409,1],[663,797,1],[1343,0,1]]]},{"char":"L","glyph":47,"advance":1139,"composite":false,"contours":[[[168,0,1],[168,1409,1],[359,1409,1],[359,156,1],[1071,156,1],[1071,0,1]]]},{"char":"M","glyph":48,"advance":1706,"composite":false,"contours":[[[1366,0,1],[1366,940,1],[1366,1096,0],[1375,1240,1],[1326,1061,0],[1287,960,1],[923,0,1],[789,0,1],[420,960,1],[364,1130,1],[331,1240,1],[334,1129,1],[338,940,1],[338,0,1],[168,0,1],[168,1409,1],[419,1409,1],[794,432,1],[814,373,0],[851,238,0],[857,208,1],[865,248,0],[916,411,0],[925,432,1],[1293,1409,1],[1538,1409,1],[1538,0,1]]]},{"char":"N","glyph":49,"advance":1479,"composite":false,"contours":[[[1082,0,1],[328,1200,1],[333,1103,1],[338,936,1],[338,0,1],[168,0,1],[168,1409,1],[390,1409,1],[1152,201,1],[1140,397,0],[1140,485,1],[1140,1409,1],[1312,1409,1],[1312,0,1]]]},{"char":"O","glyph":50,"advance":1593,"composite":false,"contours":[[[1495,711,1],[1495,490,0],[1326,158,0],[1010,-20,0],[795,-20,1],[578,-20,0],[263,156,0],[97,489,0],[97,711,1],[97,1049,0],[467,1430,0],[797,1430,1],[1012,1430,0],[1328,1259,0],[1495,933,0]],[[1300,711,1],[1300,974,0],[1037,1274,0],[797,1274,1],[555,1274,0],[291,978,0],[291,711,1],[291,446,0],[558,135,0],[795,135,1],[1039,135,0],[1300,436,0]]]},{"char":"P","glyph":51,"advance":1366,"composite":false,"contours":[[[1258,985,1],[1258,785,0],[997,549,0],[773,549,1],[359,549,1],[359,0,1],[168,0,1],[168,1409,1],[761,1409,1],[998,1409,0],[1258,1187,0]],[[1066,983,1],[1066,1256,0],[738,1256,1],[359,1256,1],[359,700,1],[746,700,1],[1066,700,0]]]},{"char":"Q","glyph":52,"advance":1593,"composite":false,"contours":[[[1495,711,1],[1495,413,0],[1195,29,0],[928,-6,1],[969,-132,0],[1102,-244,0],[1204,-244,1],[1259,-244,0],[1319,-231,1],[1319,-365,1],[1226,-387,0],[1141,-387,1],[990,-387,0],[795,-216,0],[733,-16,1],[535,-6,0],[248,175,0],[97,498,0],[97,711,1],[97,1049,0],[467,1430,0],[797,1430,1],[1012,1430,0],[1328,1259,0],[1495,933,0]],[[1300,711,1],[1300,974,0],[1037,1274,0],[797,1274,1],[555,1274,0],[291,978,0],[291,711,1],[291,446,0],[558,135,0],[795,135,1],[1039,135,0],[1300,436,0]]]},{"char":"R","glyph":53,"advance":1479,"composite":false,"contours":[[[1164,0,1],[798,585,1],[359,585,1],[359,0,1],[168,0,1],[168,1409,1],[831,1409,1],[1069,1409,0],[1328,1196,0],[1328,1006,1],[1328,849,0],[1145,635,0],[984,607,1],[1384,0,1]],[[1136,1004,1],[1136,1127,0],[969,1256,0],[812,1256,1],[359,1256,1],[359,736,1],[820,736,1],[971,736,0],[1136,877,0]]]},{"char":"S","glyph":54,"advance":1366,"composite":false,"contours":[[[1272,389,1],[1272,194,0],[967,-20,0],[690,-20,1],[175,-20,0],[93,338,1],[278,375,1],[310,248,0],[518,129,0],[697,129,1],[882,129,0],[1083,256,0],[1083,379,1],[1083,448,0],[1020,534,0],[906,590,0],[748,628,0],[652,650,1],[485,687,0],[312,761,0],[212,852,0],[159,974,0],[159,1053,1],[159,1234,0],[436,1430,0],[694,1430,1],[934,1430,0],[1188,1283,0],[1239,1106,1],[1051,1073,1],[1020,1185,0],[846,1286,0],[692,1286,1],[523,1286,0],[345,1174,0],[345,1063,1],[345,998,0],[414,913,0],[544,854,0],[738,811,1],[803,796,0],[932,765,0],[1050,722,0],[1153,664,0],[1229,580,0],[1272,466,0]]]},{"char":"T","glyph":55,"advance":1251,"composite":false,"contours":[[[720,1253,1],[720,0,1],[530,0,1],[530,1253,1],[46,1253,1],[46,1409,1],[1204,1409,1],[1204,1253,1]]]},{"char":"U","glyph":56,"advance":1479,"composite":false,"contours":[[[731,-20,1],[558,-20,0],[300,106,0],[158,346,0],[158,512,1],[158,1409,1],[349,1409,1],[349,528,1],[349,335,0],[545,135,0],[730,135,1],[920,135,0],[1131,342,0],[1131,541,1],[1131,1409,1],[1321,1409,1],[1321,530,1],[1321,359,0],[1176,111,0],[911,-20,0]]]},{"char":"V","glyph":57,"advance":1366,"composite":false,"contours":[[[782,0,1],[584,0,1],[9,1409,1],[210,1409,1],[600,417,1],[684,168,1],[768,417,1],[1156,1409,1],[1357,1409,1]]]},{"char":"W","glyph":58,"advance":1933,"composite":false,"contours":[[[1511,0,1],[1283,0,1],[1039,895,1],[1015,979,0],[969,1196,1],[943,1080,0],[907,924,0],[652,0,1],[424,0,1],[9,1409,1],[208,1409,1],[461,514,1],[506,346,0],[544,168,1],[568,278,0],[631,538,0],[877,1409,1],[1060,1409,1],[1305,532,1],[1361,317,0],[1393,168,1],[1402,203,1],[1429,318,0],[1463,463,0],[1727,1409,1],[1926,1409,1]]]},{"char":"X","glyph":59,"advance":1366,"composite":false,"contours":[[[1112,0,1],[689,616,1],[257,0,1],[46,0,1],[582,732,1],[87,1409,1],[298,1409,1],[690,856,1],[1071,1409,1],[1282,1409,1],[800,739,1],[1323,0,1]]]},{"char":"Y","glyph":60,"advance":1366,"composite":false,"contours":[[[777,584,1],[777,0,1],[587,0,1],[587,584,1],[45,1409,1],[255,1409,1],[684,738,1],[1111,1409,1],[1321,1409,1]]]},{"char":"Z","glyph":61,"advance":1251,"composite":false,"contours":[[[1187,0,1],[65,0,1],[65,143,1],[923,1253,1],[138,1253,1],[138,1409,1],[1140,1409,1],[1140,1270,1],[282,156,1],[1187,156,1]]]},{"char":"[","glyph":62,"advance":569,"composite":false,"contours":[[[146,-425,1],[146,1484,1],[553,1484,1],[553,1355,1],[320,1355,1],[320,-296,1],[553,-296,1],[553,-425,1]]]},{"char":"\\","glyph":63,"advance":569,"composite":false,"contours":[[[407,-20,1],[0,1484,1],[158,1484,1],[569,-20,1]]]},{"char":"]","glyph":64,"advance":569,"composite":false,"contours":[[[16,-425,1],[16,-296,1],[249,-296,1],[249,1355,1],[16,1355,1],[16,1484,1],[423,1484,1],[423,-425,1]]]},{"char":"^","glyph":65,"advance":961,"composite":false,"contours":[[[787,673,1],[478,1306,1],[172,673,1],[10,673,1],[378,1409,1],[581,1409,1],[951,673,1]]]},{"char":"_","glyph":66,"advance":1139,"composite":false,"contours":[[[-31,-407,1],[-31,-277,1],[1162,-277,1],[1162,-407,1]]]},{"char":"`","glyph":67,"advance":682,"composite":false,"contours":[[[436,1201,1],[106,1479,1],[106,1508,1],[313,1508,1],[530,1221,1],[530,1201,1]]]},{"char":"a","glyph":68,"advance":1139,"composite":false,"contours":[[[414,-20,1],[251,-20,0],[87,152,0],[87,302,1],[87,470,0],[308,650,0],[554,656,1],[797,660,1],[797,719,1],[797,851,0],[685,965,0],[565,965,1],[444,965,0],[334,883,0],[323,793,1],[135,810,1],[181,1102,0],[569,1102,1],[773,1102,0],[979,915,0],[979,738,1],[979,272,1],[979,192,0],[1021,111,0],[1080,111,1],[1106,111,0],[1139,118,1],[1139,6,1],[1071,-10,0],[1000,-10,1],[900,-10,0],[809,95,0],[803,207,1],[797,207,1],[728,83,0],[545,-20,0]],[[455,115,1],[554,115,0],[708,205,0],[797,362,0],[797,445,1],[797,534,1],[600,530,1],[473,528,0],[342,480,0],[272,380,0],[272,299,1],[272,211,0],[367,115,0]]]},{"char":"b","glyph":69,"advance":1139,"composite":false,"contours":[[[1053,546,1],[1053,-20,0],[655,-20,1],[532,-20,0],[369,69,0],[318,168,1],[316,168,1],[316,137,0],[308,10,0],[306,0,1],[132,0,1],[138,54,0],[138,223,1],[138,1484,1],[318,1484,1],[318,1061,1],[318,996,0],[314,908,1],[318,908,1],[368,1012,0],[533,1102,0],[655,1102,1],[860,1102,0],[1053,826,0]],[[864,540,1],[864,767,0],[744,963,0],[609,963,1],[457,963,0],[318,755,0],[318,529,1],[318,316,0],[454,113,0],[607,113,1],[743,113,0],[864,314,0]]]},{"char":"c","glyph":70,"advance":1024,"composite":false,"contours":[[[275,546,1],[275,330,0],[411,122,0],[548,122,1],[644,122,0],[773,226,0],[788,334,1],[970,322,1],[949,166,0],[725,-20,0],[553,-20,1],[326,-20,0],[87,267,0],[87,542,1],[87,815,0],[327,1102,0],[551,1102,1],[717,1102,0],[936,930,0],[964,779,1],[779,765,1],[765,855,0],[651,961,0],[546,961,1],[403,961,0],[275,771,0]]]},{"char":"d","glyph":71,"advance":1139,"composite":false,"contours":[[[821,174,1],[771,70,0],[606,-20,0],[484,-20,1],[279,-20,0],[86,256,0],[86,536,1],[86,1102,0],[484,1102,1],[607,1102,0],[771,1012,0],[821,914,1],[823,914,1],[821,1035,1],[821,1484,1],[1001,1484,1],[1001,223,1],[1001,54,0],[1007,0,1],[835,0,1],[832,16,0],[825,132,0],[825,174,1]],[[275,542,1],[275,315,0],[395,119,0],[530,119,1],[683,119,0],[821,331,0],[821,554,1],[821,769,0],[683,969,0],[532,969,1],[396,969,0],[275,768,0]]]},{"char":"e","glyph":72,"advance":1139,"composite":false,"contours":[[[276,503,1],[276,317,0],[430,115,0],[578,115,1],[695,115,0],[836,209,0],[861,281,1],[1019,236,1],[922,-20,0],[578,-20,1],[338,-20,0],[87,266,0],[87,548,1],[87,816,0],[338,1102,0],[571,1102,1],[1048,1102,0],[1048,527,1],[1048,503,1]],[[862,641,1],[847,812,0],[703,969,0],[568,969,1],[437,969,0],[284,794,0],[278,641,1]]]},{"char":"f","glyph":73,"advance":569,"composite":false,"contours":[[[361,951,1],[361,0,1],[181,0,1],[181,951,1],[29,951,1],[29,1082,1],[181,1082,1],[181,1204,1],[181,1352,0],[311,1482,0],[445,1482,1],[520,1482,0],[572,1470,1],[572,1333,1],[527,1341,0],[492,1341,1],[423,1341,0],[361,1271,0],[361,1179,1],[361,1082,1],[572,1082,1],[572,951,1]]]},{"char":"g","glyph":74,"advance":1139,"composite":false,"contours":[[[548,-425,1],[371,-425,0],[161,-286,0],[131,-158,1],[312,-132,1],[330,-207,0],[453,-288,0],[553,-288,1],[822,-288,0],[822,27,1],[822,201,1],[820,201,1],[769,97,0],[591,-8,0],[472,-8,1],[273,-8,0],[86,256,0],[86,539,1],[86,826,0],[287,1099,0],[492,1099,1],[607,1099,0],[776,994,0],[822,897,1],[824,897,1],[824,927,0],[832,1075,0],[836,1082,1],[1007,1082,1],[1001,1028,0],[1001,858,1],[1001,31,1],[1001,-425,0]],[[822,541,1],[822,673,0],[750,864,0],[619,965,0],[536,965,1],[398,965,0],[272,765,0],[272,541,1],[272,319,0],[390,125,0],[533,125,1],[618,125,0],[750,225,0],[822,412,0]]]},{"char":"h","glyph":75,"advance":1139,"composite":false,"contours":[[[317,897,1],[375,1003,0],[538,1102,0],[663,1102,1],[839,1102,0],[1006,927,0],[1006,721,1],[1006,0,1],[825,0,1],[825,686,1],[825,800,0],[783,911,0],[687,963,0],[602,963,1],[475,963,0],[322,787,0],[322,638,1],[322,0,1],[142,0,1],[142,1484,1],[322,1484,1],[322,1098,1],[322,1037,0],[315,907,0],[314,897,1]]]},{"char":"i","glyph":76,"advance":455,"composite":false,"contours":[[[137,1312,1],[137,1484,1],[317,1484,1],[317,1312,1]],[[137,0,1],[137,1082,1],[317,1082,1],[317,0,1]]]},{"char":"j","glyph":77,"advance":455,"composite":false,"contours":[[[137,1312,1],[137,1484,1],[317,1484,1],[317,1312,1]],[[317,-134,1],[317,-287,0],[197,-425,0],[77,-425,1],[0,-425,0],[-50,-416,1],[-50,-277,1],[12,-283,1],[81,-283,0],[137,-211,0],[137,-107,1],[137,1082,1],[317,1082,1]]]},{"char":"k","glyph":78,"advance":1024,"composite":false,"contours":[[[816,0,1],[450,494,1],[318,385,1],[318,0,1],[138,0,1],[138,1484,1],[318,1484,1],[318,557,1],[793,1082,1],[1004,1082,1],[565,617,1],[1027,0,1]]]},{"char":"l","glyph":79,"advance":455,"composite":false,"contours":[[[138,0,1],[138,1484,1],[318,1484,1],[318,0,1]]]},{"char":"m","glyph":80,"advance":1706,"composite":false,"contours":[[[768,0,1],[768,686,1],[768,843,0],[682,963,0],[570,963,1],[455,963,0],[321,787,0],[321,627,1],[321,0,1],[142,0,1],[142,851,1],[142,1040,0],[136,1082,1],[306,1082,1],[307,1077,0],[309,1033,0],[312,976,0],[314,897,1],[317,897,1],[375,1012,0],[525,1102,0],[633,1102,1],[756,1102,0],[899,1004,0],[927,897,1],[930,897,1],[986,1006,0],[1145,1102,0],[1258,1102,1],[1422,1102,0],[1571,924,0],[1571,721,1],[1571,0,1],[1393,0,1],[1393,686,1],[1393,843,0],[1307,963,0],[1195,963,1],[1077,963,0],[946,788,0],[946,627,1],[946,0,1]]]},{"char":"n","glyph":81,"advance":1139,"composite":false,"contours":[[[825,0,1],[825,686,1],[825,793,0],[783,911,0],[691,963,0],[602,963,1],[472,963,0],[322,785,0],[322,627,1],[322,0,1],[142,0,1],[142,851,1],[142,1040,0],[136,1082,1],[306,1082,1],[307,1077,0],[309,1033,0],[312,976,0],[314,897,1],[317,897,1],[379,1009,0],[542,1102,0],[663,1102,1],[841,1102,0],[1006,925,0],[1006,721,1],[1006,0,1]]]},{"char":"o","glyph":82,"advance":1139,"composite":false,"contours":[[[1053,542,1],[1053,258,0],[803,-20,0],[565,-20,1],[328,-20,0],[86,269,0],[86,542,1],[86,1102,0],[571,1102,1],[819,1102,0],[1053,829,0]],[[864,542,1],[864,766,0],[731,969,0],[574,969,1],[416,969,0],[275,762,0],[275,542,1],[275,328,0],[414,113,0],[563,113,1],[725,113,0],[864,321,0]]]},{"char":"p","glyph":83,"advance":1139,"composite":false,"contours":[[[1053,546,1],[1053,-20,0],[655,-20,1],[405,-20,0],[319,168,1],[314,168,1],[318,160,0],[318,-2,1],[318,-425,1],[138,-425,1],[138,861,1],[138,1028,0],[132,1082,1],[306,1082,1],[307,1078,0],[311,1029,0],[316,927,0],[316,908,1],[320,908,1],[368,1008,0],[526,1101,0],[655,1101,1],[855,1101,0],[1053,833,0]],[[864,542,1],[864,768,0],[742,962,0],[609,962,1],[502,962,0],[381,872,0],[318,681,0],[318,528,1],[318,315,0],[454,113,0],[607,113,1],[741,113,0],[864,310,0]]]},{"char":"q","glyph":84,"advance":1139,"composite":false,"contours":[[[484,-20,1],[278,-20,0],[86,258,0],[86,536,1],[86,1102,0],[484,1102,1],[607,1102,0],[767,1015,0],[821,914,1],[823,914,1],[823,944,0],[831,1091,0],[835,1096,1],[1008,1096,1],[1001,1037,0],[1001,801,1],[1001,-425,1],[821,-425,1],[821,14,1],[825,178,1],[823,178,1],[769,71,0],[611,-20,0]],[[821,554,1],[821,765,0],[683,969,0],[532,969,1],[395,969,0],[275,765,0],[275,542,1],[275,315,0],[396,119,0],[530,119,1],[683,119,0],[821,337,0]]]},{"char":"r","glyph":85,"advance":682,"composite":false,"contours":[[[142,0,1],[142,830,1],[142,944,0],[136,1082,1],[306,1082,1],[314,898,0],[314,861,1],[318,861,1],[361,1000,0],[473,1102,0],[575,1102,1],[611,1102,0],[648,1092,1],[648,927,1],[612,937,0],[552,937,1],[440,937,0],[322,744,0],[322,564,1],[322,0,1]]]},{"char":"s","glyph":86,"advance":1024,"composite":false,"contours":[[[950,299,1],[950,146,0],[719,-20,0],[511,-20,1],[309,-20,0],[90,113,0],[57,254,1],[216,285,1],[239,198,0],[383,117,0],[511,117,1],[648,117,0],[775,201,0],[775,285,1],[775,349,0],[687,429,0],[589,455,1],[460,489,1],[305,529,0],[174,606,0],[100,716,0],[100,796,1],[100,944,0],[311,1099,0],[513,1099,1],[692,1099,0],[903,973,0],[931,834,1],[769,814,1],[754,886,0],[623,963,0],[513,963,1],[391,963,0],[275,889,0],[275,814,1],[275,768,0],[323,708,0],[417,666,0],[568,629,1],[711,593,0],[837,532,0],[910,458,0],[950,361,0]]]},{"char":"t","glyph":87,"advance":569,"composite":false,"contours":[[[554,8,1],[465,-16,0],[372,-16,1],[156,-16,0],[156,229,1],[156,951,1],[31,951,1],[31,1082,1],[163,1082,1],[216,1324,1],[336,1324,1],[336,1082,1],[536,1082,1],[536,951,1],[336,951,1],[336,268,1],[336,190,0],[387,127,0],[450,127,1],[486,127,0],[554,141,1]]]},{"char":"u","glyph":88,"advance":1139,"composite":false,"contours":[[[314,1082,1],[314,396,1],[314,289,0],[356,171,0],[448,119,0],[537,119,1],[667,119,0],[817,297,0],[817,455,1],[817,1082,1],[997,1082,1],[997,231,1],[997,42,0],[1003,0,1],[833,0,1],[832,5,0],[830,49,0],[827,106,0],[825,185,1],[822,185,1],[760,73,0],[597,-20,0],[476,-20,1],[298,-20,0],[133,157,0],[133,361,1],[133,1082,1]]]},{"char":"v","glyph":89,"advance":1024,"composite":false,"contours":[[[613,0,1],[400,0,1],[7,1082,1],[199,1082,1],[437,378,1],[450,338,0],[506,141,1],[541,258,1],[580,376,1],[826,1082,1],[1017,1082,1]]]},{"char":"w","glyph":90,"advance":1479,"composite":false,"contours":[[[1174,0,1],[965,0,1],[776,765,1],[740,934,1],[731,889,0],[693,720,0],[508,0,1],[300,0,1],[-3,1082,1],[175,1082,1],[358,347,1],[365,323,0],[401,149,1],[418,223,1],[644,1082,1],[837,1082,1],[1026,339,1],[1072,149,1],[1103,288,1],[1308,1082,1],[1484,1082,1]]]},{"char":"x","glyph":91,"advance":1024,"composite":false,"contours":[[[801,0,1],[510,444,1],[217,0,1],[23,0,1],[408,556,1],[41,1082,1],[240,1082,1],[510,661,1],[778,1082,1],[979,1082,1],[612,558,1],[1002,0,1]]]},{"char":"y","glyph":92,"advance":1024,"composite":false,"contours":[[[191,-425,1],[117,-425,0],[67,-414,1],[67,-279,1],[105,-285,0],[151,-285,1],[319,-285,0],[417,-38,1],[434,5,1],[5,1082,1],[197,1082,1],[425,484,1],[430,470,0],[444,431,0],[520,209,0],[523,196,1],[593,393,1],[830,1082,1],[1020,1082,1],[604,0,1],[537,-173,0],[421,-342,0],[280,-425,0]]]},{"char":"z","glyph":93,"advance":1024,"composite":false,"contours":[[[83,0,1],[83,137,1],[688,943,1],[117,943,1],[117,1082,1],[901,1082,1],[901,945,1],[295,139,1],[922,139,1],[922,0,1]]]},{"char":"{","glyph":94,"advance":684,"composite":false,"contours":[[[513,-425,1],[386,-425,0],[249,-271,0],[249,-132,1],[249,229,1],[249,346,0],[144,461,0],[34,466,1],[34,593,1],[143,597,0],[249,712,0],[249,829,1],[249,1191,1],[249,1332,0],[382,1484,0],[513,1484,1],[648,1484,1],[648,1355,1],[585,1355,1],[494,1355,0],[417,1248,0],[417,1140,1],[417,784,1],[417,690,0],[311,553,0],[223,532,1],[223,530,1],[312,509,0],[417,373,0],[417,276,1],[417,-81,1],[417,-187,0],[494,-296,0],[585,-296,1],[648,-296,1],[648,-425,1]]]},{"char":"|","glyph":95,"advance":532,"composite":false,"contours":[[[183,-434,1],[183,1484,1],[349,1484,1],[349,-434,1]]]},{"char":"}","glyph":96,"advance":684,"composite":false,"contours":[[[94,-296,1],[185,-296,0],[264,-187,0],[264,-81,1],[264,276,1],[264,374,0],[368,510,0],[457,530,1],[457,532,1],[371,552,0],[264,687,0],[264,784,1],[264,1140,1],[264,1248,0],[185,1355,0],[94,1355,1],[34,1355,1],[34,1484,1],[166,1484,1],[297,1484,0],[430,1332,0],[430,1191,1],[430,829,1],[430,713,0],[536,597,0],[647,593,1],[647,466,1],[535,462,0],[430,346,0],[430,229,1],[430,-132,1],[430,-270,0],[293,-425,0],[166,-425,1],[34,-425,1],[34,-296,1]]]},{"char":"~","glyph":97,"advance":1196,"composite":false,"contours":[[[844,553,1],[775,553,0],[630,597,0],[557,623,1],[428,668,0],[340,668,1],[273,668,0],[157,627,0],[92,580,1],[92,723,1],[203,807,0],[355,807,1],[407,807,0],[534,781,0],[664,735,1],[695,723,0],[815,690,0],[860,690,1],[990,690,0],[1104,782,1],[1104,633,1],[1046,591,0],[929,553,0]]]},{"char":"Ä","glyph":134,"advance":1366,"composite":true,"contours":[[[1167,0,1],[1006,412,1],[364,412,1],[202,0,1],[4,0,1],[579,1409,1],[796,1409,1],[1362,0,1]],[[685,1265,1],[676,1237,1],[651,1154,0],[602,1024,1],[422,561,1],[949,561,1],[768,1026,1],[740,1095,0],[712,1182,1]],[[803,1530,1],[803,1714,1],[966,1714,1],[966,1530,1]],[[409,1530,1],[409,1714,1],[574,1714,1],[574,1530,1]]]},{"char":"Ö","glyph":152,"advance":1593,"composite":true,"contours":[[[1495,711,1],[1495,490,0],[1326,158,0],[1010,-20,0],[795,-20,1],[578,-20,0],[263,156,0],[97,489,0],[97,711,1],[97,1049,0],[467,1430,0],[797,1430,1],[1012,1430,0],[1328,1259,0],[1495,933,0]],[[1300,711,1],[1300,974,0],[1037,1274,0],[797,1274,1],[555,1274,0],[291,978,0],[291,711,1],[291,446,0],[558,135,0],[795,135,1],[1039,135,0],[1300,436,0]],[[910,1530,1],[910,1714,1],[1073,1714,1],[1073,1530,1]],[[516,1530,1],[516,1714,1],[681,1714,1],[681,1530,1]]]},{"char":"Ü","glyph":158,"advance":1479,"composite":true,"contours":[[[731,-20,1],[558,-20,0],[300,106,0],[158,346,0],[158,512,1],[158,1409,1],[349,1409,1],[349,528,1],[349,335,0],[545,135,0],[730,135,1],[920,135,0],[1131,342,0],[1131,541,1],[1131,1409,1],[1321,1409,1],[1321,530,1],[1321,359,0],[1176,111,0],[911,-20,0]],[[853,1530,1],[853,1714,1],[1016,1714,1],[1016,1530,1]],[[459,1530,1],[459,1714,1],[624,1714,1],[624,1530,1]]]},{"char":"ä","glyph":166,"advance":1139,"composite":true,"contours":[[[414,-20,1],[251,-20,0],[87,152,0],[87,302,1],[87,470,0],[308,650,0],[554,656,1],[797,660,1],[797,719,1],[797,851,0],[685,965,0],[565,965,1],[444,965,0],[334,883,0],[323,793,1],[135,810,1],[181,1102,0],[569,1102,1],[773,1102,0],[979,915,0],[979,738,1],[979,272,1],[979,192,0],[1021,111,0],[1080,111,1],[1106,111,0],[1139,118,1],[1139,6,1],[1071,-10,0],[1000,-10,1],[900,-10,0],[809,95,0],[803,207,1],[797,207,1],[728,83,0],[545,-20,0]],[[455,115,1],[554,115,0],[708,205,0],[797,362,0],[797,445,1],[797,534,1],[600,530,1],[473,528,0],[342,480,0],[272,380,0],[272,299,1],[272,211,0],[367,115,0]],[[674,1219,1],[674,1403,1],[837,1403,1],[837,1219,1]],[[280,1219,1],[280,1403,1],[445,1403,1],[445,1219,1]]]},{"char":"ö","glyph":184,"advance":1139,"composite":true,"contours":[[[1053,542,1],[1053,258,0],[803,-20,0],[565,-20,1],[328,-20,0],[86,269,0],[86,542,1],[86,1102,0],[571,1102,1],[819,1102,0],[1053,829,0]],[[864,542,1],[864,766,0],[731,969,0],[574,969,1],[416,969,0],[275,762,0],[275,542,1],[275,328,0],[414,113,0],[563,113,1],[725,113,0],[864,321,0]],[[689,1219,1],[689,1403,1],[852,1403,1],[852,1219,1]],[[295,1219,1],[295,1403,1],[460,1403,1],[460,1219,1]]]},{"char":"ü","glyph":190,"advance":1139,"composite":true,"contours":[[[320,1082,1],[320,396,1],[320,289,0],[362,171,0],[454,119,0],[543,119,1],[673,119,0],[823,297,0],[823,455,1],[823,1082,1],[1003,1082,1],[1003,231,1],[1003,42,0],[1009,0,1],[839,0,1],[838,5,0],[836,49,0],[833,106,0],[831,185,1],[828,185,1],[766,73,0],[603,-20,0],[482,-20,1],[304,-20,0],[139,157,0],[139,361,1],[139,1082,1]],[[676,1219,1],[676,1403,1],[839,1403,1],[839,1219,1]],[[282,1219,1],[282,1403,1],[447,1403,1],[447,1219,1]]]},{"char":"ß","glyph":161,"advance":1251,"composite":false,"contours":[[[1167,295,1],[1167,145,0],[979,-20,0],[809,-20,1],[638,-20,0],[526,29,1],[524,193,1],[576,164,0],[736,124,0],[805,124,1],[897,124,0],[995,210,0],[995,289,1],[995,353,0],[910,455,0],[813,513,1],[721,567,0],[630,699,0],[630,785,1],[630,846,0],[687,946,0],[741,991,1],[799,1039,0],[852,1123,0],[852,1173,1],[852,1250,0],[718,1343,0],[609,1343,1],[461,1343,0],[322,1183,0],[322,1021,1],[322,0,1],[142,0,1],[142,1027,1],[142,1254,0],[377,1484,0],[609,1484,1],[799,1484,0],[1024,1323,0],[1024,1187,1],[1024,1044,0],[911,941,1],[832,869,0],[798,820,0],[798,791,1],[798,753,0],[853,694,0],[935,637,0],[983,605,1],[1167,481,0]]]},{"char":"é","glyph":171,"advance":1139,"composite":true,"contours":[[[276,503,1],[276,317,0],[430,115,0],[578,115,1],[695,115,0],[836,209,0],[861,281,1],[1019,236,1],[922,-20,0],[578,-20,1],[338,-20,0],[87,266,0],[87,548,1],[87,816,0],[338,1102,0],[571,1102,1],[1048,1102,0],[1048,527,1],[1048,503,1]],[[862,641,1],[847,812,0],[703,969,0],[568,969,1],[437,969,0],[284,794,0],[278,641,1]],[[440,1201,1],[440,1221,1],[657,1508,1],[864,1508,1],[864,1479,1],[534,1201,1]]]},{"char":"Å","glyph":135,"advance":1366,"composite":true,"contours":[[[1167,0,1],[1006,412,1],[364,412,1],[202,0,1],[4,0,1],[579,1409,1],[796,1409,1],[1362,0,1]],[[685,1265,1],[676,1237,1],[651,1154,0],[602,1024,1],[422,561,1],[949,561,1],[768,1026,1],[740,1095,0],[712,1182,1]],[[928,1546,1],[928,1446,0],[786,1304,0],[686,1304,1],[586,1304,0],[444,1446,0],[444,1546,1],[444,1647,0],[586,1787,0],[686,1787,1],[785,1787,0],[928,1646,0]],[[820,1546,1],[820,1602,0],[742,1680,0],[686,1680,1],[629,1680,0],[551,1602,0],[551,1546,1],[551,1491,0],[627,1409,0],[686,1409,1],[744,1409,0],[820,1490,0]]]},{"char":"€","glyph":2088,"advance":1139,"composite":false,"contours":[[[691,126,1],[791,126,0],[911,226,0],[925,317,1],[1110,303,1],[1080,148,0],[861,-20,0],[687,-20,1],[470,-20,0],[217,242,0],[187,502,1],[16,502,1],[56,631,1],[176,631,1],[173,709,1],[176,795,1],[16,795,1],[56,922,1],[185,922,1],[217,1179,0],[465,1430,0],[684,1430,1],[859,1430,0],[1078,1261,0],[1107,1107,1],[922,1093,1],[908,1184,0],[787,1284,0],[681,1284,1],[539,1284,0],[388,1110,0],[367,922,1],[813,922,1],[773,795,1],[360,795,1],[357,771,0],[357,709,1],[357,669,0],[361,631,1],[813,631,1],[773,502,1],[369,502,1],[391,307,0],[553,126,0]]]},{"char":"°","glyph":114,"advance":819,"composite":false,"contours":[[[696,1145,1],[696,1026,0],[527,860,0],[409,860,1],[291,860,0],[122,1028,0],[122,1145,1],[122,1262,0],[289,1430,0],[409,1430,1],[529,1430,0],[696,1264,0]],[[587,1145,1],[587,1221,0],[484,1325,0],[409,1325,1],[334,1325,0],[231,1219,0],[231,1145,1],[231,1071,0],[336,965,0],[409,965,1],[483,965,0],[587,1070,0]]]},{"char":"±","glyph":115,"advance":1124,"composite":false,"contours":[[[636,680,1],[636,285,1],[489,285,1],[489,680,1],[65,680,1],[65,825,1],[489,825,1],[489,1219,1],[636,1219,1],[636,825,1],[1060,825,1],[1060,680,1]],[[65,0,1],[65,145,1],[1060,145,1],[1060,0,1]]]},{"char":"Ì","glyph":142,"advance":569,"composite":true,"contours":[[[189,0,1],[189,1409,1],[380,1409,1],[380,0,1]],[[319,1530,1],[9,1747,1],[9,1776,1],[216,1776,1],[433,1550,1],[433,1530,1]]]},{"char":"Î","glyph":144,"advance":569,"composite":true,"contours":[[[189,0,1],[189,1409,1],[380,1409,1],[380,0,1]],[[616,1550,1],[616,1530,1],[511,1530,1],[292,1669,1],[290,1669,1],[58,1530,1],[-46,1530,1],[-46,1550,1],[188,1790,1],[392,1790,1]]]},{"char":"ï","glyph":177,"advance":569,"composite":true,"contours":[[[194,0,1],[194,1082,1],[374,1082,1],[374,0,1]],[[402,1219,1],[402,1403,1],[565,1403,1],[565,1219,1]],[[8,1219,1],[8,1403,1],[173,1403,1],[173,1219,1]]]},{"char":"Ī","glyph":236,"advance":569,"composite":true,"contours":[[[189,0,1],[189,1409,1],[380,1409,1],[380,0,1]],[[561,1565,1],[12,1565,1],[12,1692,1],[561,1692,1]]]},{"char":"¼","glyph":126,"advance":1708,"composite":true,"contours":[[[56,563,1],[56,670,1],[267,670,1],[267,1290,1],[65,1152,1],[65,1272,1],[275,1409,1],[398,1409,1],[398,670,1],[613,670,1],[613,563,1]],[[493,0,1],[345,0,1],[1226,1409,1],[1371,1409,1]],[[1605,148,1],[1605,-36,1],[1497,-36,1],[1497,148,1],[1076,148,1],[1076,229,1],[1485,776,1],[1605,776,1],[1605,230,1],[1730,230,1],[1730,148,1]],[[1497,659,1],[1488,635,0],[1454,590,1],[1226,284,1],[1191,241,1],[1181,230,1],[1497,230,1]]]}]} \ No newline at end of file diff --git a/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.ttf b/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.ttf new file mode 100644 index 000000000..7769c4119 Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/LiberationSans-Regular.ttf differ diff --git a/tests/CADability.Tests/Files/Fonts/VarTestSans-NoHVAR.ttf b/tests/CADability.Tests/Files/Fonts/VarTestSans-NoHVAR.ttf new file mode 100644 index 000000000..14dc51ee3 Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/VarTestSans-NoHVAR.ttf differ diff --git a/tests/CADability.Tests/Files/Fonts/VarTestSans.otf b/tests/CADability.Tests/Files/Fonts/VarTestSans.otf new file mode 100644 index 000000000..447131972 Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/VarTestSans.otf differ diff --git a/tests/CADability.Tests/Files/Fonts/VarTestSans.reference.json b/tests/CADability.Tests/Files/Fonts/VarTestSans.reference.json new file mode 100644 index 000000000..8b96b565a --- /dev/null +++ b/tests/CADability.Tests/Files/Fonts/VarTestSans.reference.json @@ -0,0 +1 @@ +[{"file":"VarTestSans.ttf","family":"VarTestSans","instances":["ExtraLight","Light","Regular","Medium","Semibold","Bold","Black"],"advances":[{"coordinates":[200.0],"widths":[200.0,248.0,336.0,472.0,472.0,798.0,562.0,208.0,270.0,270.0,386.0,472.0,208.0,294.0,208.0,360.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,208.0,208.0,472.0,472.0,472.0,394.0,802.0,520.0,574.0,562.0,600.0,510.0,470.0,600.0,634.0,232.0,456.0,550.0,460.0,698.0,632.0,648.0,542.0,648.0,534.0,516.0,520.0,630.0,482.0,764.0,470.0,436.0,538.0,270.0,360.0,270.0,472.0,500.0,532.0,486.0,538.0,446.0,540.0,478.0,252.0,480.0,522.0,222.0,222.0,452.0,230.0,806.0,526.0,532.0,540.0,540.0,306.0,400.0,302.0,524.0,422.0,672.0,392.0,424.0,396.0,270.0,220.0,270.0,472.0,520.0,648.0,630.0,486.0,532.0,524.0,478.0,520.0],"strikeout":[286,50]},{"coordinates":[300.0],"widths":[200.0,259.195216,360.389577,478.830692,478.797095,805.196924,574.794532,219.195216,278.996155,278.996155,394.596326,478.797095,219.195216,298.598035,219.195216,357.401111,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,219.195216,219.195216,478.797095,478.797095,478.797095,402.396412,814.161964,526.330886,577.798377,564.332432,604.165056,514.598035,476.597181,604.598035,638.831465,240.330113,462.330886,557.796668,466.997295,705.830306,636.165056,652.331659,548.597181,652.39812,543.595899,520.79795,524.331659,634.165056,490.996155,769.997681,481.794959,446.829146,538.399829,278.996155,357.401111,278.996155,478.797095,500.0,534.798804,490.998068,542.198206,448.665636,543.998291,482.831465,262.795387,486.597181,527.997437,228.597181,228.797095,463.595045,236.830692,812.197352,531.597608,534.798804,543.998291,543.998291,317.195216,405.197779,311.829533,529.331272,434.194789,684.661771,406.793678,435.794959,403.796668,278.996155,225.797522,278.996155,478.797095,526.397266,652.39812,634.198206,490.997864,534.798804,529.397693,482.79795,526.397266],"strikeout":[287,50]},{"coordinates":[400.0],"widths":[200.0,289.214674,425.789111,497.146881,497.023195,824.495148,609.102485,249.214674,303.118934,303.118934,417.646982,497.023195,249.214674,310.927455,249.214674,350.432308,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,249.214674,249.214674,497.023195,497.023195,497.023195,424.911005,846.773715,543.306865,587.98355,570.58674,615.333464,526.927455,494.287219,616.927455,651.786818,262.666928,479.306865,578.703077,485.760219,726.826912,647.333464,663.946802,566.287219,664.191479,569.326863,533.663432,535.946802,645.333464,515.118934,786.080188,513.422603,475.867006,539.471953,303.118934,350.432308,303.118934,497.023195,500.0,542.303668,504.400157,553.455503,455.813417,554.719526,495.786818,291.742721,504.287219,544.07929,246.287219,247.023195,494.686627,255.146881,828.815266,546.607337,542.303668,554.719526,554.719526,347.214674,419.135384,338.186975,543.626834,466.894556,718.61373,446.462248,467.422603,424.703077,303.118934,241.343313,303.118934,497.023195,543.551242,664.191479,645.455503,504.399408,542.303668,543.871361,495.663432,543.551242],"strikeout":[291,50]},{"coordinates":[500.0],"widths":[200.0,302.432957,454.586084,505.211935,505.048581,832.992615,624.209093,262.432957,313.740769,313.740769,427.796735,505.048581,262.432957,316.356393,262.432957,347.363778,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,262.432957,262.432957,505.048581,505.048581,505.048581,434.824718,861.133445,550.781794,592.468325,573.340661,620.25118,532.356393,502.076564,622.356393,657.491369,272.50236,486.781794,587.908666,494.021982,736.072218,652.25118,669.061227,574.076564,669.384376,580.65682,539.32841,541.061227,650.25118,525.740769,793.161699,527.349008,488.653068,539.944034,313.740769,347.363778,313.740769,505.048581,500.0,545.608239,510.301416,558.412359,458.960755,559.440342,501.491369,304.488923,512.076564,551.160513,254.076564,255.048581,508.376991,263.211935,836.13253,553.216478,545.608239,559.440342,559.440342,360.432957,425.272444,349.792785,549.92151,481.293042,733.563587,463.929264,481.349008,433.908666,313.740769,248.188495,313.740769,505.048581,551.104547,669.384376,650.412359,510.300427,545.608239,550.244461,501.32841,551.104547],"strikeout":[292,50]},{"coordinates":[600.0],"widths":[200.0,315.200328,482.400714,513.001845,512.800199,841.200211,638.800374,275.200328,324.000263,324.000263,437.600252,512.800199,275.200328,321.600135,275.200328,344.399924,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,275.200328,275.200328,512.800199,512.800199,512.800199,444.400246,875.003349,558.001777,596.800111,576.000683,625.001162,537.600135,509.600193,627.600135,663.001299,282.002324,494.001777,596.800228,502.001914,745.002119,657.001025,674.00123,581.600193,674.400129,591.600281,544.80014,546.00123,655.001162,536.000263,800.00164,540.800345,501.002939,540.400012,324.000263,344.399924,324.000263,512.800199,500.0,548.800082,516.001367,563.200123,462.000683,564.000117,507.001299,316.800316,519.600193,558.000175,261.600193,262.800199,521.600339,271.001845,843.200181,559.600164,548.800082,564.000117,564.000117,373.200328,431.200152,361.002665,556.001504,495.200357,748.003417,480.800433,494.800345,442.800228,324.000263,254.80017,324.000263,512.800199,558.400187,674.400129,655.200123,516.000146,548.800082,556.400158,506.80014,558.400187],"strikeout":[294,50]},{"coordinates":[700.0],"widths":[200.0,340.283625,537.046469,528.118089,528.029344,857.325188,667.467,300.283625,344.156485,344.156485,456.860641,528.029344,300.283625,331.902203,300.283625,338.577016,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,300.283625,300.283625,528.029344,528.029344,528.029344,463.212719,902.436531,572.558159,605.310516,581.599292,634.518797,547.902203,524.381422,637.902203,673.638655,301.037593,508.558159,614.268953,517.678018,762.357806,665.398938,684.078726,596.381422,684.254281,613.10025,555.550125,556.078726,664.518797,556.156485,813.438301,567.227391,525.076956,541.295844,344.156485,338.577016,344.156485,528.029344,500.0,555.070906,527.198584,572.606359,467.599292,572.958438,517.638655,340.987782,534.381422,571.437656,276.381422,278.029344,547.579469,286.118089,857.085578,572.141813,555.070906,572.958438,572.958438,398.283625,442.845969,382.837239,568.318443,522.523235,775.99646,513.946219,521.227391,460.268953,344.156485,267.789735,344.156485,528.029344,572.7335,684.254281,664.606359,527.198047,555.070906,568.493891,517.550125,572.7335],"strikeout":[297,50]},{"coordinates":[900.0],"widths":[200.0,360.0,580.0,540.0,540.0,870.0,690.0,320.0,360.0,360.0,472.0,540.0,320.0,340.0,320.0,334.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,320.0,320.0,540.0,540.0,540.0,478.0,924.0,584.0,612.0,586.0,642.0,556.0,536.0,646.0,682.0,316.0,520.0,628.0,530.0,776.0,672.0,692.0,608.0,692.0,630.0,564.0,564.0,672.0,572.0,824.0,588.0,544.0,542.0,360.0,334.0,360.0,540.0,500.0,560.0,536.0,580.0,472.0,580.0,526.0,360.0,546.0,582.0,288.0,290.0,568.0,298.0,868.0,582.0,560.0,580.0,580.0,418.0,452.0,400.0,578.0,544.0,798.0,540.0,542.0,474.0,360.0,278.0,360.0,540.0,584.0,692.0,672.0,536.0,560.0,578.0,526.0,584.0],"strikeout":[299,50]}],"locations":[{"coordinates":[300.0],"glyphs":[{"char":"A","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[97.201196,0]],["lineTo",[97.201196,659.000427]],["lineTo",[457.798377,659.000427]],["lineTo",[457.798377,619.405382]],["lineTo",[143.195216,619.405382]],["lineTo",[143.195216,369.798377]],["lineTo",[407.397693,369.798377]],["lineTo",[407.397693,330.203332]],["lineTo",[143.195216,330.203332]],["lineTo",[143.195216,39.595045]],["lineTo",[467.798377,39.595045]],["lineTo",[467.798377,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[326.165829,-12]],["curveTo",[273.388352,-12.0,226.583043,2.111133,185.749903,30.333398]],["curveTo",[144.916763,58.555663,112.889125,98.444595,89.666989,150.000193]],["curveTo",[66.444852,201.555792,54.833784,262.111326,54.833784,331.666795]],["curveTo",[54.833784,401.222265,66.444852,461.416699,89.666989,512.250097]],["curveTo",[112.889125,563.083494,144.916763,602.250225,185.749903,629.75029]],["curveTo",[226.583043,657.250354,273.388352,671.000386,326.165829,671.000386]],["curveTo",[379.054375,671.000386,425.915217,657.250354,466.748357,629.75029]],["curveTo",[507.581497,602.250225,539.609136,563.083494,562.831272,512.250097]],["curveTo",[586.053408,461.416699,597.664477,401.222265,597.664477,331.666795]],["curveTo",[597.664477,262.111326,586.053408,201.555792,562.831272,150.000193]],["curveTo",[539.609136,98.444595,507.581497,58.555663,466.748357,30.333398]],["curveTo",[425.915217,2.111133,379.054375,-12.0,326.165829,-12]],["closePath",[]],["moveTo",[326.165829,29.82876]],["curveTo",[370.833269,29.82876,409.833977,42.301186,443.167955,67.246038]],["curveTo",[476.501932,92.190891,502.530215,127.274772,521.252802,172.497681]],["curveTo",[539.975389,217.72059,549.336683,270.776962,549.336683,331.666795]],["curveTo",[549.336683,392.556629,539.975389,445.224133,521.252802,489.669308]],["curveTo",[502.530215,534.114482,476.501932,568.476162,443.167955,592.754348]],["curveTo",[409.833977,617.032534,370.833269,629.171627,326.165829,629.171627]],["curveTo",[281.609458,629.171627,242.636516,617.032534,209.247005,592.754348]],["curveTo",[175.857493,568.476162,149.801444,534.114482,131.078856,489.669308]],["curveTo",[112.356269,445.224133,102.994976,392.556629,102.994976,331.666795]],["curveTo",[102.994976,270.776962,112.356269,217.72059,131.078856,172.497681]],["curveTo",[149.801444,127.274772,175.857493,92.190891,209.247005,67.246038]],["curveTo",[242.636516,42.301186,281.609458,29.82876,326.165829,29.82876]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.999573,-12]],["curveTo",[220.93265,-12.0,178.399331,-2.699915,141.399616,15.900256]],["curveTo",[104.3999,34.500427,72.73359,58.333761,46.400683,87.400256]],["lineTo",[75.197779,116.596326]],["curveTo",[98.531112,90.129289,127.081233,69.045636,160.848142,53.345365]],["curveTo",[194.615051,37.645095,230.798804,29.794959,269.399402,29.794959]],["curveTo",[320.00057,29.794959,359.984907,42.045351,389.352413,66.546134]],["curveTo",[418.71992,91.046917,433.403674,122.931226,433.403674,162.19906]],["curveTo",[433.403674,189.666524,427.953581,211.667165,417.053396,228.200982]],["curveTo",[406.153211,244.7348,391.803004,258.385163,374.002777,269.152072]],["curveTo",[356.202549,279.91898,336.46903,290.169158,314.802221,299.902606]],["lineTo",[214.901324,344.603161]],["curveTo",[195.168162,353.403104,175.001566,364.469671,154.401538,377.802862]],["curveTo",[133.801509,391.136053,116.451445,408.285918,102.351346,429.252456]],["curveTo",[88.251246,450.218995,81.201196,476.668945,81.201196,508.602307]],["curveTo",[81.201196,540.535384,89.43443,568.668447,105.900897,593.001495]],["curveTo",[122.367364,617.334544,145.017087,636.417628,173.850064,650.250748]],["curveTo",[202.683041,664.083867,235.665955,671.000427,272.798804,671.000427]],["curveTo",[312.732166,671.000427,348.38203,663.267051,379.748398,647.800299]],["curveTo",[411.114766,632.333547,436.664388,613.933647,456.397266,592.600598]],["lineTo",[431.200342,563.404528]],["curveTo",[411.000427,582.938061,387.833689,598.788267,361.700128,610.955147]],["curveTo",[335.566567,623.122028,305.932792,629.205468,272.798804,629.205468]],["curveTo",[229.331055,629.205468,194.230172,618.488466,167.496155,597.054464]],["curveTo",[140.762139,575.620461,127.39513,547.069628,127.39513,511.401965]],["curveTo",[127.39513,485.801225,133.47857,465.000641,145.645451,449.000214]],["curveTo",[157.812331,432.999786,172.879183,420.06614,190.846006,410.199274]],["curveTo",[208.812829,400.332408,225.662822,391.698989,241.395985,384.299018]],["lineTo",[341.096967,339.798377]],["curveTo",[366.630215,328.531682,389.930229,315.781717,410.99701,301.548484]],["curveTo",[432.06379,287.31525,448.797238,269.465328,461.197352,247.998718]],["curveTo",[473.597465,226.532109,479.797522,198.998718,479.797522,165.398548]],["curveTo",[479.797522,131.732023,471.314253,101.465542,454.347715,74.599103]],["curveTo",[437.381176,47.732664,413.048056,26.582871,381.348355,11.149722]],["curveTo",[349.648654,-4.283426,312.19906,-12.0,268.999573,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.000386,-12]],["curveTo",[171.667311,-12.0,148.417472,-7.250097,127.25087,2.24971]],["curveTo",[106.084267,11.749517,89.1954,26.221557,76.584267,45.665829]],["curveTo",[63.973135,65.110102,57.667568,89.72117,57.667568,119.499034]],["curveTo",[57.667568,173.165572,82.63993,213.887708,132.584654,241.665443]],["curveTo",[182.529377,269.443178,260.890864,289.220977,367.669114,300.998841]],["curveTo",[368.113645,325.443886,365.502577,349.388996,359.83591,372.834171]],["curveTo",[354.169243,396.279345,343.030215,415.50219,326.418825,430.502705]],["curveTo",[309.807434,445.503221,285.779152,453.003478,254.333977,453.003478]],["curveTo",[222.0,453.003478,192.527316,446.86445,165.915926,434.586393]],["curveTo",[139.304536,422.308336,117.776833,410.336039,101.332818,398.669501]],["lineTo",[81.001159,429.999227]],["curveTo",[92.333977,438.554568,107.000258,447.665572,125.0,457.332238]],["curveTo",[142.999742,466.998905,163.444015,475.22117,186.332818,481.999034]],["curveTo",[209.221621,488.776897,233.666023,492.165829,259.666023,492.165829]],["curveTo",[297.554697,492.165829,327.63774,483.860263,349.915153,467.24913]],["curveTo",[372.192566,450.637998,388.081111,428.304793,397.580789,400.249517]],["curveTo",[407.080467,372.19424,411.830306,341.000386,411.830306,306.667955]],["lineTo",[411.830306,0]],["lineTo",[374.33475,0]],["lineTo",[369.668341,62.333977]],["lineTo",[367.335137,62.333977]],["curveTo",[343.001675,42.556114,316.418116,25.222652,287.584461,10.333591]],["curveTo",[258.750805,-4.55547,228.556114,-12.0,197.000386,-12]],["closePath",[]],["moveTo",[204.664863,26.995749]],["curveTo",[232.443242,26.995749,259.13832,33.523709,284.750097,46.579629]],["curveTo",[310.361873,59.63555,338.001546,78.663832,367.669114,103.664477]],["lineTo",[367.669114,266.002319]],["curveTo",[301.889705,258.446592,249.749517,248.2519,211.248551,235.418245]],["curveTo",[172.747584,222.584589,145.080209,206.806404,128.246425,188.083688]],["curveTo",[111.412641,169.360972,102.995749,147.221492,102.995749,121.66525]],["curveTo",[102.995749,87.664219,113.134906,63.385711,133.41322,48.829726]],["curveTo",[153.691535,34.273741,177.442083,26.995749,204.664863,26.995749]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[273.999227,-12]],["curveTo",[232.999614,-12.0,195.666602,-1.972233,162.000193,18.083301]],["curveTo",[128.333784,38.138835,101.528411,66.91657,81.584074,104.416506]],["curveTo",[61.639737,141.916441,51.667568,186.888545,51.667568,239.332818]],["curveTo",[51.667568,291.554954,61.695271,336.55489,81.750676,374.332625]],["curveTo",[101.806082,412.11036,127.778121,441.193661,159.666795,461.582528]],["curveTo",[191.55547,481.971396,225.444144,492.165829,261.332818,492.165829]],["curveTo",[299.332303,492.165829,332.081948,483.415926,359.581755,465.916119]],["curveTo",[387.081562,448.416312,408.192566,423.138706,422.914766,390.083301]],["curveTo",[437.636967,357.027896,444.998068,317.000129,444.998068,270]],["curveTo",[444.998068,264.33372,444.914766,258.445174,444.748164,252.334364]],["curveTo",[444.581562,246.223553,443.77606,239.890478,442.331659,233.335137]],["lineTo",[79.329726,233.335137]],["lineTo",[79.329726,271.331272]],["lineTo",[403.002705,271.331272]],["curveTo",[403.002705,332.222136,390.280118,377.834622,364.834944,408.168728]],["curveTo",[339.389769,438.502834,305.000129,453.669887,261.666023,453.669887]],["curveTo",[235.220977,453.669887,209.414831,446.030795,184.247584,430.752609]],["curveTo",[159.080338,415.474423,138.302088,392.08504,121.912834,360.584461]],["curveTo",[105.52358,329.083881,97.328953,289.222265,97.328953,240.999614]],["curveTo",[97.328953,196.776962,105.245716,158.60978,121.079243,126.498068]],["curveTo",[136.91277,94.386355,158.607525,69.719173,186.16351,52.496522]],["curveTo",[213.719495,35.27387,244.331143,26.662544,277.998454,26.662544]],["curveTo",[304.332174,26.662544,328.24913,30.218143,349.749324,37.32934]],["curveTo",[371.249517,44.440537,390.332947,53.885067,406.999614,65.662931]],["lineTo",[425.331272,34.999614]],["curveTo",[406.553795,22.333205,385.443049,11.333333,361.999034,2.0]],["curveTo",[338.555019,-7.333333,309.22175,-12.0,273.999227,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.000427,-231.201196]],["curveTo",[208.600313,-231.201196,173.600313,-225.684465,144.000427,-214.651004]],["curveTo",[114.400541,-203.617542,91.600669,-188.200769,75.600812,-168.400683]],["curveTo",[59.600954,-148.600598,51.601025,-125.200627,51.601025,-98.200769]],["curveTo",[51.601025,-76.934074,58.051047,-56.217286,70.951089,-36.050406]],["curveTo",[83.851132,-15.883526,101.334472,2.0,123.401111,17.600171]],["lineTo",[123.401111,21.600171]],["curveTo",[111.334472,29.133419,101.251175,39.283355,93.151217,52.049979]],["curveTo",[85.05126,64.816603,81.001282,80.333191,81.001282,98.599744]],["curveTo",[81.001282,120.133276,87.067991,138.48334,99.20141,153.649936]],["curveTo",[111.334828,168.816531,122.734729,179.999715,133.401111,187.199487]],["lineTo",[133.401111,191.199487]],["curveTo",[118.401253,204.399544,104.518012,222.466254,91.751388,245.399616]],["curveTo",[78.984764,268.332977,72.601452,294.132992,72.601452,322.799658]],["curveTo",[72.601452,355.732593,80.217998,384.949025,95.451089,410.448953]],["curveTo",[110.684181,435.948882,131.033889,455.948882,156.500214,470.448953]],["curveTo",[181.966539,484.949025,210.332906,492.19906,241.599317,492.19906]],["curveTo",[254.999288,492.19906,267.182543,491.049053,278.149082,488.749039]],["curveTo",[289.11562,486.449025,298.198775,483.599032,305.398548,480.19906]],["lineTo",[467.997437,480.19906]],["lineTo",[467.997437,442.603161]],["lineTo",[355.995728,442.603161]],["lineTo",[355.995728,438.603161]],["curveTo",[370.862879,427.00299,383.479994,411.502634,393.847074,392.102093]],["curveTo",[404.214153,372.701552,409.397693,349.334188,409.397693,322]],["curveTo",[409.397693,289.333618,401.897764,260.250392,386.897907,234.75032]],["curveTo",[371.898049,209.250249,351.764915,189.183469,326.498505,174.549979]],["curveTo",[301.232095,159.916489,272.932365,152.599744,241.599317,152.599744]],["curveTo",[228.19906,152.599744,214.448811,154.449808,200.348569,158.149936]],["curveTo",[186.248327,161.850064,173.531539,166.833547,162.198206,173.100384]],["curveTo",[152.131283,164.833547,143.114339,155.266624,135.147373,144.399616]],["curveTo",[127.180407,133.532607,123.196924,119.932223,123.196924,103.598462]],["curveTo",[123.196924,86.331482,129.596967,71.614481,142.397053,59.447458]],["curveTo",[155.197138,47.280436,179.33077,41.196924,214.79795,41.196924]],["lineTo",[317.699274,41.196924]],["curveTo",[371.965542,41.196924,411.915136,32.380322,437.548056,14.747117]],["curveTo",[463.180977,-2.886089,475.997437,-30.802506,475.997437,-69.002136]],["curveTo",[475.997437,-96.601737,466.714225,-122.71814,448.1478,-147.351346]],["curveTo",[429.581375,-171.984551,403.281717,-192.117827,369.248825,-207.751175]],["curveTo",[335.215933,-223.384522,295.133134,-231.201196,249.000427,-231.201196]],["closePath",[]],["moveTo",[241.599317,187.996583]],["curveTo",[263.666524,187.996583,284.100384,193.646732,302.900897,204.947031]],["curveTo",[321.70141,216.24733,336.885163,232.014381,348.452157,252.248185]],["curveTo",[360.019151,272.481988,365.802648,295.999146,365.802648,322.799658]],["curveTo",[365.802648,364.133846,353.602307,396.601239,329.201623,420.201837]],["curveTo",[304.80094,443.802435,275.600171,455.602734,241.599317,455.602734]],["curveTo",[207.598462,455.602734,178.397693,443.802435,153.99701,420.201837]],["curveTo",[129.596326,396.601239,117.395985,364.133846,117.395985,322.799658]],["curveTo",[117.395985,295.999146,123.179482,272.481988,134.746476,252.248185]],["curveTo",[146.31347,232.014381,161.497223,216.24733,180.297736,204.947031]],["curveTo",[199.098249,193.646732,219.532109,187.996583,241.599317,187.996583]],["closePath",[]],["moveTo",[253.599317,-195.404528]],["curveTo",[289.666809,-195.404528,320.884166,-189.521002,347.251388,-177.753951]],["curveTo",[373.61861,-165.9869,394.135697,-150.753097,408.802648,-132.052542]],["curveTo",[423.4696,-113.351986,430.803076,-93.934786,430.803076,-73.80094]],["curveTo",[430.803076,-47.26698,421.369571,-28.433148,402.502563,-17.299445]],["curveTo",[383.635555,-6.165741,356.035028,-0.598889,319.700982,-0.598889]],["lineTo",[216.79795,-0.598889]],["curveTo",[212.39869,-0.598889,204.582372,0.051118,193.348996,1.351132]],["curveTo",[182.11562,2.651146,170.132137,4.601168,157.398548,7.201196]],["curveTo",[135.197779,-7.799231,119.147231,-24.116403,109.246903,-41.75032]],["curveTo",[99.346576,-59.384238,94.396412,-76.668091,94.396412,-93.60188]],["curveTo",[94.396412,-124.002563,108.363306,-148.569842,136.297095,-167.303716]],["curveTo",[164.230884,-186.037591,203.331625,-195.404528,253.599317,-195.404528]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.399829,-12]],["curveTo",[170.999573,-12.0,137.466254,-5.950021,107.799872,6.149936]],["curveTo",[78.13349,18.249893,53.000427,34.533106,32.400683,54.999573]],["lineTo",[55.798377,82.596326]],["curveTo",[76.131568,65.796383,98.048199,52.129645,121.54827,41.596113]],["curveTo",[145.048341,31.06258,174.732166,25.795814,210.599744,25.795814]],["curveTo",[248.400683,25.795814,276.984764,35.346148,296.351986,54.446818]],["curveTo",[315.719208,73.547487,325.402819,95.598177,325.402819,120.598889]],["curveTo",[325.402819,140.666097,319.452727,157.299872,307.552542,170.500214]],["curveTo",[295.652357,183.700555,280.768831,194.750819,262.901965,203.651004]],["curveTo",[245.035099,212.551189,226.801652,220.334615,208.201623,227.001282]],["curveTo",[183.668233,235.667948,159.934786,245.484551,137.001282,256.451089]],["curveTo",[114.067777,267.417628,95.267621,281.367507,80.600812,298.300726]],["curveTo",[65.934003,315.233946,58.600598,337.267265,58.600598,364.400683]],["curveTo",[58.600598,399.333333,71.983696,429.366225,98.749893,454.499359]],["curveTo",[125.51609,479.632493,163.732308,492.19906,213.398548,492.19906]],["curveTo",[240.598035,492.19906,265.96426,487.532465,289.497223,478.199274]],["curveTo",[313.030187,468.866083,333.396554,456.932935,350.596326,442.399829]],["lineTo",[327.198633,414.803076]],["curveTo",[310.93208,426.336324,293.798804,435.819664,275.798804,443.253097]],["curveTo",[257.798804,450.68653,236.598605,454.403246,212.198206,454.403246]],["curveTo",[174.730457,454.403246,147.229674,445.319593,129.695856,427.152285]],["curveTo",[112.162039,408.984978,103.39513,388.934359,103.39513,367.000427]],["curveTo",[103.39513,348.866581,108.47857,333.982842,118.645451,322.34921]],["curveTo",[128.812331,310.715577,142.195856,300.89869,158.796027,292.898548]],["curveTo",[175.396198,284.898405,193.462908,277.264986,212.996155,269.998291]],["curveTo",[238.329489,260.198206,263.096255,249.964901,287.296455,239.298377]],["curveTo",[311.496654,228.631852,331.36352,214.3653,346.897053,196.498718]],["curveTo",[362.430585,178.632137,370.197352,154.265414,370.197352,123.398548]],["curveTo",[370.197352,99.265556,363.980777,76.949167,351.547629,56.449381]],["curveTo",[339.114481,35.949594,320.814752,19.416417,296.648441,6.84985]],["curveTo",[272.48213,-5.716717,243.065926,-12.0,208.399829,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[227.198633,-12]],["curveTo",[192.265983,-12.0,160.299801,-4.766695,131.300085,9.699915]],["curveTo",[102.30037,24.166524,79.233874,44.81646,62.100598,71.649722]],["curveTo",[44.967322,98.482984,36.400683,130.266268,36.400683,166.999573]],["curveTo",[36.400683,197.665955,43.58401,225.065784,57.950662,249.19906]],["curveTo",[72.317315,273.332337,90.717286,295.265698,113.150577,314.999146]],["curveTo",[135.583867,334.732593,159.500498,353.49943,184.90047,371.299658]],["curveTo",[210.300441,389.099886,234.217072,407.000142,256.650363,425.000427]],["curveTo",[279.083654,443.000712,297.483625,462.034316,311.850278,482.101239]],["curveTo",[326.21693,502.168162,333.400256,524.601737,333.400256,549.401965]],["curveTo",[333.400256,570.602307,327.983483,590.102734,317.149936,607.903246]],["curveTo",[306.316389,625.703759,287.665955,634.604015,261.198633,634.604015]],["curveTo",[232.331197,634.604015,209.580664,624.020433,192.947031,602.853268]],["curveTo",[176.313399,581.686103,167.996583,555.93564,167.996583,525.60188]],["curveTo",[167.996583,491.934786,175.396554,456.484337,190.196497,419.250534]],["curveTo",[204.99644,382.016731,224.546348,344.799587,248.84622,307.599103]],["curveTo",[273.146091,270.398619,300.295956,235.131497,330.295814,201.797736]],["curveTo",[360.295671,168.463976,390.462196,139.096967,420.795387,113.696711]],["curveTo",[446.395273,92.229816,471.278442,74.296241,495.444895,59.895985]],["curveTo",[519.611348,45.495728,541.794675,34.528834,561.994874,26.995301]],["lineTo",[547.19607,-12]],["curveTo",[525.130001,-4.199915,501.16389,7.350135,475.297736,22.65015]],["curveTo",[449.431582,37.950164,422.432009,57.500214,394.299018,81.300299]],["curveTo",[361.232806,109.767051,328.833191,141.90047,297.100171,177.700555]],["curveTo",[265.367151,213.500641,236.63406,251.267407,210.900897,291.000854]],["curveTo",[185.167735,330.734302,164.717856,370.41777,149.55126,410.05126]],["curveTo",[134.384665,449.68475,126.801367,487.601595,126.801367,523.801794]],["curveTo",[126.801367,552.134985,132.517941,577.384807,143.951089,599.55126]],["curveTo",[155.384238,621.717713,171.350634,639.167521,191.850278,651.900683]],["curveTo",[212.349922,664.633846,235.932792,671.000427,262.598889,671.000427]],["curveTo",[298.39812,671.000427,325.814111,659.717215,344.84686,637.15079]],["curveTo",[363.87961,614.584366,373.395985,585.934786,373.395985,551.20205]],["curveTo",[373.395985,523.668803,366.429304,498.735512,352.495942,476.402179]],["curveTo",[338.56258,454.068845,320.51253,433.31881,298.345792,414.152072]],["curveTo",[276.179055,394.985334,252.528976,376.268546,227.395557,358.001709]],["curveTo",[202.262139,339.734871,178.61206,321.217998,156.445323,302.451089]],["curveTo",[134.278585,283.684181,116.228535,263.50057,102.295173,241.900256]],["curveTo",[88.361811,220.299943,81.39513,196.266268,81.39513,169.799231]],["curveTo",[81.39513,140.598605,88.211946,115.298021,101.845579,93.89748]],["curveTo",[115.479211,72.496939,133.579596,55.863164,156.146732,43.996155]],["curveTo",[178.713869,32.129147,203.397693,26.195643,230.198206,26.195643]],["curveTo",[260.531966,26.195643,289.11562,34.112416,315.949167,49.945963]],["curveTo",[342.782714,65.77951,367.132849,85.729745,388.999573,109.796668]],["curveTo",[416.73302,140.99701,440.449737,176.514025,460.149722,216.347715]],["curveTo",[479.849708,256.181404,495.933077,299.13171,508.399829,345.198633]],["lineTo",[550.994447,345.198633]],["curveTo",[537.127723,296.598605,519.461128,250.098605,497.99466,205.698633]],["curveTo",[476.528193,161.298662,450.728464,121.398832,420.595472,85.999146]],["curveTo",[395.329062,57.332764,367.129432,33.832977,335.996583,15.499786]],["curveTo",[304.863733,-2.833405,268.59775,-12.0,227.198633,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[387.664477,-145.66525]],["curveTo",[339.44234,-145.66525,294.859168,-137.859683,253.91496,-122.248551]],["curveTo",[212.970751,-106.637418,177.443242,-84.02635,147.332432,-54.415346]],["curveTo",[117.221621,-24.804342,93.749646,11.612163,76.916506,54.834171]],["curveTo",[60.083366,98.056178,51.666795,147.222523,51.666795,202.333205]],["curveTo",[51.666795,267.66628,62.333398,326.777155,83.666602,379.665829]],["curveTo",[104.999807,432.554503,134.138513,477.887644,171.082721,515.66525]],["curveTo",[208.02693,553.442856,250.443306,582.442727,298.331852,602.664863]],["curveTo",[346.220397,622.886999,396.442211,632.998068,448.997295,632.998068]],["curveTo",[513.44131,632.998068,569.079823,619.748164,615.912834,593.248357]],["curveTo",[662.745845,566.748551,698.912254,529.748808,724.412061,482.24913]],["curveTo",[749.911868,434.749452,762.661771,379.222008,762.661771,315.666795]],["curveTo",[762.661771,273.111455,757.022936,235.639415,745.745265,203.250676]],["curveTo",[734.467595,170.861938,719.884423,143.584267,701.995749,121.417665]],["curveTo",[684.107074,99.251063,664.301766,82.639995,642.579823,71.584461]],["curveTo",[620.85788,60.528927,599.552635,55.001159,578.66409,55.001159]],["curveTo",[552.219817,55.001159,530.081111,61.251127,512.247971,73.751063]],["curveTo",[494.414831,86.250998,484.331659,105.5565,481.998454,131.667568]],["lineTo",[479.998454,131.667568]],["curveTo",[462.664863,112.334493,443.775931,95.779023,423.331659,82.001159]],["curveTo",[402.887386,68.223296,381.109651,61.334364,357.998454,61.334364]],["curveTo",[326.776704,61.334364,299.804793,72.862002,277.082721,95.917279]],["curveTo",[254.360649,118.972555,242.999614,153.000129,242.999614,198]],["curveTo",[242.999614,222.332947,247.166216,247.69379,255.49942,274.082528]],["curveTo",[263.832625,300.471267,276.02693,324.943371,292.082335,347.498841]],["curveTo",[308.13774,370.05431,327.804278,388.332045,351.081948,402.332045]],["curveTo",[374.359619,416.332045,400.665121,423.332045,429.998454,423.332045]],["curveTo",[447.887386,423.332045,463.748551,419.304278,477.581948,411.248744]],["curveTo",[491.415346,403.19321,503.220977,390.998841,512.998841,374.665636]],["lineTo",[514.998841,374.665636]],["lineTo",[523.831852,414.998841]],["lineTo",[557.662158,414.998841]],["lineTo",[522.32934,226.333977]],["curveTo",[511.217885,180.555341,510.773355,146.665765,520.995749,124.66525]],["curveTo",[531.218143,102.664734,551.551862,91.664477,581.996908,91.664477]],["curveTo",[602.997037,91.664477,624.413929,100.331208,646.247584,117.66467]],["curveTo",[668.08124,134.998132,686.33153,160.192823,700.998454,193.248744]],["curveTo",[715.665378,226.304664,722.998841,266.332947,722.998841,313.333591]],["curveTo",[722.998841,356.000773,716.720977,394.695593,704.16525,429.418052]],["curveTo",[691.609522,464.14051,673.248229,493.779538,649.081369,518.335137]],["curveTo",[624.914509,542.890735,595.77548,561.862968,561.664283,575.251836]],["curveTo",[527.553086,588.640703,488.942018,595.335137,445.831079,595.335137]],["curveTo",[400.497488,595.335137,356.747262,586.140639,314.580402,567.751643]],["curveTo",[272.413543,549.362646,234.524482,523.056951,200.91322,488.834557]],["curveTo",[167.301959,454.612163,140.746296,413.500837,121.246232,365.50058]],["curveTo",[101.746167,317.500322,91.996135,263.888932,91.996135,204.666409]],["curveTo",[91.996135,156.332947,99.190569,112.943886,113.579436,74.499227]],["curveTo",[127.968304,36.054568,148.41277,3.276575,174.912834,-23.83475]],["curveTo",[201.412898,-50.946076,232.940794,-71.751836,269.496522,-86.252029]],["curveTo",[306.052249,-100.752222,346.441438,-108.002319,390.66409,-108.002319]],["curveTo",[423.664477,-108.002319,453.886935,-103.557789,481.331465,-94.668728]],["curveTo",[508.775996,-85.779667,534.331659,-74.335008,557.998454,-60.33475]],["lineTo",[574.830692,-92.331659]],["curveTo",[546.38642,-109.998454,516.636516,-123.304085,485.580982,-132.248551]],["curveTo",[454.525448,-141.193017,421.886613,-145.66525,387.664477,-145.66525]],["closePath",[]],["moveTo",[363.830306,98.330886]],["curveTo",[380.275094,98.330886,397.497681,103.692115,415.498068,114.414573]],["curveTo",[433.498454,125.137032,452.332174,141.998454,471.999227,164.998841]],["lineTo",[502.999614,337.667568]],["curveTo",[491.33256,356.223682,479.86007,368.974037,468.582142,375.918631]],["curveTo",[457.304214,382.863226,444.053988,386.335523,428.831465,386.335523]],["curveTo",[399.053086,386.335523,373.385904,376.779861,351.829919,357.668535]],["curveTo",[330.273934,338.557209,313.634713,314.695786,301.912254,286.084267]],["curveTo",[290.189796,257.472748,284.328567,229.055663,284.328567,200.833011]],["curveTo",[284.328567,163.165443,292.134326,136.664863,307.745845,121.331272]],["curveTo",[323.357364,105.997681,342.052184,98.330886,363.830306,98.330886]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[242.798804,-12]],["curveTo",[203.732308,-12.0,168.982486,-4.366652,138.549338,10.900043]],["curveTo",[108.11619,26.166738,84.116332,47.1001,66.549765,73.700128]],["curveTo",[48.983198,100.300157,40.199915,130.333476,40.199915,163.800085]],["curveTo",[40.199915,192.666951,46.650007,218.667165,59.550192,241.800726]],["curveTo",[72.450377,264.934287,88.683896,285.151146,108.250748,302.451303]],["curveTo",[127.817599,319.751459,147.601025,333.734871,167.601025,344.401538]],["lineTo",[167.601025,348.401538]],["curveTo",[144.734444,364.1347,123.817742,384.001282,104.850918,408.001282]],["curveTo",[85.884095,432.001282,76.400683,460.934643,76.400683,494.801367]],["curveTo",[76.400683,525.534387,83.900612,552.750819,98.90047,576.450662]],["curveTo",[113.900327,600.150505,133.950093,618.567065,159.049765,631.700342]],["curveTo",[184.149438,644.833618,212.265698,651.400256,243.398548,651.400256]],["curveTo",[297.931653,651.400256,340.36473,635.566923,370.697779,603.900256]],["curveTo",[401.030827,572.23359,416.197352,532.400399,416.197352,484.400683]],["curveTo",[416.197352,460.867151,411.180621,438.666951,401.147159,417.800085]],["curveTo",[391.113698,396.933219,379.046917,378.199701,364.946818,361.59953]],["curveTo",[350.846718,344.999359,337.630073,331.699274,325.296882,321.699274]],["lineTo",[325.296882,317.699274]],["curveTo",[343.563434,306.432579,361.313399,293.732593,378.546775,279.599317]],["curveTo",[395.780151,265.46604,409.830201,248.349423,420.696924,228.249466]],["curveTo",[431.563648,208.149509,436.99701,183.799516,436.99701,155.199487]],["curveTo",[436.99701,124.19963,428.980421,96.116403,412.947245,70.949808]],["curveTo",[396.914068,45.783212,374.264275,25.666595,344.997864,10.599957]],["curveTo",[315.731454,-4.466681,281.6651,-12.0,242.798804,-12]],["closePath",[]],["moveTo",[289.799231,334.79795]],["curveTo",[317.133419,357.13171,337.934074,380.648868,352.201196,405.349423]],["curveTo",[366.468318,430.049979,373.60188,456.00057,373.60188,483.201196]],["curveTo",[373.60188,506.86829,368.551759,528.668731,358.451516,548.60252]],["curveTo",[348.351274,568.536309,333.517585,584.486687,313.950449,596.453652]],["curveTo",[294.383312,608.420618,270.732735,614.404101,242.998718,614.404101]],["curveTo",[207.264417,614.404101,177.863662,603.120461,154.796455,580.553182]],["curveTo",[131.729247,557.985903,120.195643,529.401965,120.195643,494.801367]],["curveTo",[120.195643,464.600598,128.51253,439.699986,145.146305,420.09953]],["curveTo",[161.78008,400.499074,183.097252,384.132066,209.097821,370.998505]],["curveTo",[235.098391,357.864944,261.998861,345.798092,289.799231,334.79795]],["closePath",[]],["moveTo",[243.198633,24.996155]],["curveTo",[273.266126,24.996155,299.466823,30.762993,321.800726,42.296668]],["curveTo",[344.134629,53.830343,361.46839,69.530756,373.802008,89.397907]],["curveTo",[386.135626,109.265058,392.302435,131.398832,392.302435,155.799231]],["curveTo",[392.302435,188.53339,382.985547,215.167379,364.351773,235.701196]],["curveTo",[345.717998,256.235014,321.417485,273.385377,291.450235,287.152285]],["curveTo",[261.482984,300.919194,229.932507,314.336039,196.798804,327.402819]],["curveTo",[164.664388,308.335754,137.780151,285.435284,116.146091,258.70141]],["curveTo",[94.512032,231.967535,83.695002,201.000142,83.695002,165.799231]],["curveTo",[83.695002,139.532109,90.528478,115.781646,104.195429,94.547843]],["curveTo",[117.862381,73.31404,136.72946,56.413641,160.796668,43.846647]],["curveTo",[184.863876,31.279653,212.331197,24.996155,243.198633,24.996155]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.999573,257.000427]],["curveTo",[151.999715,257.000427,126.933219,265.033746,105.800085,281.100384]],["curveTo",[84.666951,297.167023,68.417129,320.700342,57.050619,351.700342]],["curveTo",[45.684109,382.700342,40.000854,420.467037,40.000854,465.000427]],["curveTo",[40.000854,530.933789,52.700698,581.750463,78.100384,617.450449]],["curveTo",[103.500071,653.150434,137.799801,671.000427,180.999573,671.000427]],["curveTo",[224.799373,671.000427,259.24911,653.150434,284.348783,617.450449]],["curveTo",[309.448455,581.750463,321.998291,530.933789,321.998291,465.000427]],["curveTo",[321.998291,420.467037,316.315036,382.700342,304.948526,351.700342]],["curveTo",[293.582016,320.700342,277.332194,297.167023,256.19906,281.100384]],["curveTo",[235.065926,265.033746,209.99943,257.000427,180.999573,257.000427]],["closePath",[]],["moveTo",[180.999573,289.797522]],["curveTo",[212.333761,289.797522,237.201054,305.147871,255.601452,335.848569]],["curveTo",[274.001851,366.549267,283.20205,409.599886,283.20205,465.000427]],["curveTo",[283.20205,521.000997,274.001851,563.86829,255.601452,593.602307]],["curveTo",[237.201054,623.336324,212.333761,638.203332,180.999573,638.203332]],["curveTo",[150.332052,638.203332,125.631425,623.336324,106.897693,593.602307]],["curveTo",[88.163961,563.86829,78.797095,521.000997,78.797095,465.000427]],["curveTo",[78.797095,409.599886,88.163961,366.549267,106.897693,335.848569]],["curveTo",[125.631425,305.147871,150.332052,289.797522,180.999573,289.797522]],["closePath",[]],["moveTo",[197.799231,-12.0]],["lineTo",[565.600171,671.000427]],["lineTo",[603.997437,671.000427]],["lineTo",[236.196497,-12.0]],["closePath",[]],["moveTo",[624.197352,-12.0]],["curveTo",[595.197494,-12.0,570.130998,-3.966681,548.997864,12.099957]],["curveTo",[527.86473,28.166595,511.614908,51.699915,500.248398,82.699915]],["curveTo",[488.881888,113.699915,483.198633,151.46661,483.198633,196.0]],["curveTo",[483.198633,261.933362,495.898476,312.750036,521.298163,348.450021]],["curveTo",[546.69785,384.150007,580.997579,402.0,624.197352,402.0]],["curveTo",[667.997152,402.0,702.446889,384.150007,727.546561,348.450021]],["curveTo",[752.646234,312.750036,765.19607,261.933362,765.19607,196.0]],["curveTo",[765.19607,151.46661,759.512815,113.699915,748.146305,82.699915]],["curveTo",[736.779795,51.699915,720.529973,28.166595,699.396839,12.099957]],["curveTo",[678.263705,-3.966681,653.197209,-12.0,624.197352,-12.0]],["closePath",[]],["moveTo",[624.197352,20.797095]],["curveTo",[655.531539,20.797095,680.398832,36.147444,698.799231,66.848142]],["curveTo",[717.19963,97.54884,726.399829,140.599459,726.399829,196.0]],["curveTo",[726.399829,252.00057,717.19963,294.867863,698.799231,324.60188]],["curveTo",[680.398832,354.335896,655.531539,369.202905,624.197352,369.202905]],["curveTo",[593.529831,369.202905,568.829204,354.335896,550.095472,324.60188]],["curveTo",[531.36174,294.867863,521.994874,252.00057,521.994874,196.0]],["curveTo",[521.994874,140.599459,531.36174,97.54884,550.095472,66.848142]],["curveTo",[568.829204,36.147444,593.529831,20.797095,624.197352,20.797095]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]],["moveTo",[165.199487,724.601452]],["curveTo",[155.133419,724.601452,146.517229,727.901253,139.350918,734.500854]],["curveTo",[132.184608,741.100456,128.601452,750.0,128.601452,761.199487]],["curveTo",[128.601452,772.398975,132.184608,781.298519,139.350918,787.89812]],["curveTo",[146.517229,794.497722,155.133419,797.797522,165.199487,797.797522]],["curveTo",[175.932223,797.797522,184.715079,794.497722,191.548056,787.89812]],["curveTo",[198.381034,781.298519,201.797522,772.398975,201.797522,761.199487]],["curveTo",[201.797522,750.0,198.381034,741.100456,191.548056,734.500854]],["curveTo",[184.715079,727.901253,175.932223,724.601452,165.199487,724.601452]],["closePath",[]],["moveTo",[360.79795,724.601452]],["curveTo",[350.731881,724.601452,342.115691,727.901253,334.949381,734.500854]],["curveTo",[327.78307,741.100456,324.199915,750.0,324.199915,761.199487]],["curveTo",[324.199915,772.398975,327.78307,781.298519,334.949381,787.89812]],["curveTo",[342.115691,794.497722,350.731881,797.797522,360.79795,797.797522]],["curveTo",[371.530685,797.797522,380.313541,794.497722,387.146519,787.89812]],["curveTo",[393.979496,781.298519,397.395985,772.398975,397.395985,761.199487]],["curveTo",[397.395985,750.0,393.979496,741.100456,387.146519,734.500854]],["curveTo",[380.313541,727.901253,371.530685,724.601452,360.79795,724.601452]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[273.999227,-12]],["curveTo",[232.999614,-12.0,195.666602,-1.972233,162.000193,18.083301]],["curveTo",[128.333784,38.138835,101.528411,66.91657,81.584074,104.416506]],["curveTo",[61.639737,141.916441,51.667568,186.888545,51.667568,239.332818]],["curveTo",[51.667568,291.554954,61.695271,336.55489,81.750676,374.332625]],["curveTo",[101.806082,412.11036,127.778121,441.193661,159.666795,461.582528]],["curveTo",[191.55547,481.971396,225.444144,492.165829,261.332818,492.165829]],["curveTo",[299.332303,492.165829,332.081948,483.415926,359.581755,465.916119]],["curveTo",[387.081562,448.416312,408.192566,423.138706,422.914766,390.083301]],["curveTo",[437.636967,357.027896,444.998068,317.000129,444.998068,270]],["curveTo",[444.998068,264.33372,444.914766,258.445174,444.748164,252.334364]],["curveTo",[444.581562,246.223553,443.77606,239.890478,442.331659,233.335137]],["lineTo",[79.329726,233.335137]],["lineTo",[79.329726,271.331272]],["lineTo",[403.002705,271.331272]],["curveTo",[403.002705,332.222136,390.280118,377.834622,364.834944,408.168728]],["curveTo",[339.389769,438.502834,305.000129,453.669887,261.666023,453.669887]],["curveTo",[235.220977,453.669887,209.414831,446.030795,184.247584,430.752609]],["curveTo",[159.080338,415.474423,138.302088,392.08504,121.912834,360.584461]],["curveTo",[105.52358,329.083881,97.328953,289.222265,97.328953,240.999614]],["curveTo",[97.328953,196.776962,105.245716,158.60978,121.079243,126.498068]],["curveTo",[136.91277,94.386355,158.607525,69.719173,186.16351,52.496522]],["curveTo",[213.719495,35.27387,244.331143,26.662544,277.998454,26.662544]],["curveTo",[304.332174,26.662544,328.24913,30.218143,349.749324,37.32934]],["curveTo",[371.249517,44.440537,390.332947,53.885067,406.999614,65.662931]],["lineTo",[425.331272,34.999614]],["curveTo",[406.553795,22.333205,385.443049,11.333333,361.999034,2.0]],["curveTo",[338.555019,-7.333333,309.22175,-12.0,273.999227,-12]],["closePath",[]],["moveTo",[189.000427,570.0]],["lineTo",[326.001709,723.998291]],["lineTo",[378.195643,723.998291]],["lineTo",[380.995301,718.598889]],["lineTo",[228.996155,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]],["moveTo",[262.965399,699.600171]],["curveTo",[234.521714,699.600171,210.98334,707.283426,192.350278,722.649936]],["curveTo",[173.717215,738.016446,164.400683,758.949594,164.400683,785.449381]],["curveTo",[164.400683,811.949167,173.717215,832.865656,192.350278,848.198847]],["curveTo",[210.98334,863.532038,234.521714,871.198633,262.965399,871.198633]],["curveTo",[292.120177,871.198633,315.84743,863.537987,334.147159,848.216696]],["curveTo",[352.446889,832.895405,361.596754,811.956307,361.596754,785.399402]],["curveTo",[361.596754,758.932935,352.446889,738.016446,334.147159,722.649936]],["curveTo",[315.84743,707.283426,292.120177,699.600171,262.965399,699.600171]],["closePath",[]],["moveTo",[262.967153,725.398548]],["curveTo",[281.58851,725.398548,297.082657,730.448597,309.449594,740.548697]],["curveTo",[321.816531,750.648797,328.0,765.589512,328.0,785.370843]],["curveTo",[328.0,805.790281,321.816531,820.900043,309.449594,830.700128]],["curveTo",[297.082657,840.500214,281.58851,845.400256,262.967153,845.400256]],["curveTo",[244.987912,845.400256,229.664816,840.500214,216.997864,830.700128]],["curveTo",[204.330913,820.900043,197.997437,805.790281,197.997437,785.370843]],["curveTo",[197.997437,765.589512,204.330913,750.648797,216.997864,740.548697]],["curveTo",[229.664816,730.448597,244.987912,725.398548,262.967153,725.398548]],["closePath",[]]]}]},{"coordinates":[250.0],"glyphs":[{"char":"A","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[98.600598,0]],["lineTo",[98.600598,659.500214]],["lineTo",[455.899188,659.500214]],["lineTo",[455.899188,625.702691]],["lineTo",[137.597608,625.702691]],["lineTo",[137.597608,367.899188]],["lineTo",[404.698847,367.899188]],["lineTo",[404.698847,334.101666]],["lineTo",[137.597608,334.101666]],["lineTo",[137.597608,33.797522]],["lineTo",[465.899188,33.797522]],["lineTo",[465.899188,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[325.082915,-12]],["curveTo",[272.694176,-12.0,226.208188,2.1389,185.624952,30.416699]],["curveTo",[145.041715,58.694498,113.194563,98.638964,90.083494,150.250097]],["curveTo",[66.972426,201.861229,55.416892,262.388996,55.416892,331.833398]],["curveTo",[55.416892,401.277799,66.972426,461.458349,90.083494,512.375048]],["curveTo",[113.194563,563.291747,145.041715,602.541779,185.624952,630.125145]],["curveTo",[226.208188,657.70851,272.694176,671.500193,325.082915,671.500193]],["curveTo",[377.527187,671.500193,424.040942,657.70851,464.624179,630.125145]],["curveTo",[505.207415,602.541779,537.054568,563.291747,560.165636,512.375048]],["curveTo",[583.276704,461.458349,594.832238,401.277799,594.832238,331.833398]],["curveTo",[594.832238,262.388996,583.276704,201.861229,560.165636,150.250097]],["curveTo",[537.054568,98.638964,505.207415,58.694498,464.624179,30.416699]],["curveTo",[424.040942,2.1389,377.527187,-12.0,325.082915,-12]],["closePath",[]],["moveTo",[325.082915,23.91438]],["curveTo",[370.749968,23.91438,410.666989,36.650593,444.833977,62.123019]],["curveTo",[479.000966,87.595445,505.681774,123.387386,524.876401,169.498841]],["curveTo",[544.071028,215.610295,553.668341,269.721814,553.668341,331.833398]],["curveTo",[553.668341,393.944981,544.071028,447.6954,524.876401,493.084654]],["curveTo",[505.681774,538.473908,479.000966,573.571414,444.833977,598.377174]],["curveTo",[410.666989,623.182934,370.749968,635.585813,325.082915,635.585813]],["curveTo",[279.471396,635.585813,239.568258,623.182934,205.373502,598.377174]],["curveTo",[171.178747,573.571414,144.484055,538.473908,125.289428,493.084654]],["curveTo",[106.094801,447.6954,96.497488,393.944981,96.497488,331.833398]],["curveTo",[96.497488,269.721814,106.094801,215.610295,125.289428,169.498841]],["curveTo",[144.484055,123.387386,171.178747,87.595445,205.373502,62.123019]],["curveTo",[239.568258,36.650593,279.471396,23.91438,325.082915,23.91438]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.499786,-12]],["curveTo",[220.132992,-12.0,177.616332,-2.599957,140.949808,16.200128]],["curveTo",[104.283283,35.000214,73.033461,58.833547,47.200342,87.700128]],["lineTo",[72.598889,112.298163]],["curveTo",[95.932223,85.397978,124.62395,63.939485,158.674071,47.922683]],["curveTo",[192.724192,31.905881,229.399402,23.89748,268.699701,23.89748]],["curveTo",[320.666951,23.89748,361.825787,36.606009,392.176207,62.023067]],["curveTo",[422.526627,87.440125,437.701837,120.465613,437.701837,161.09953]],["curveTo",[437.701837,189.499929,432.143457,212.250249,421.026698,229.350491]],["curveTo",[409.909939,246.450733,395.318169,260.525915,377.251388,271.576036]],["curveTo",[359.184608,282.626157,339.234515,293.084579,317.401111,302.951303]],["lineTo",[216.450662,348.301581]],["curveTo",[196.917414,357.034885,176.834116,367.984836,156.200769,381.151431]],["curveTo",[135.567421,394.318026,118.142389,411.309626,103.925673,432.126228]],["curveTo",[89.708956,452.942831,82.600598,479.334472,82.600598,511.301153]],["curveTo",[82.600598,542.934359,90.717215,570.75089,106.950449,594.750748]],["curveTo",[123.183682,618.750605,145.508543,637.542147,173.925032,651.125374]],["curveTo",[202.341521,664.7086,234.832977,671.500214,271.399402,671.500214]],["curveTo",[311.366083,671.500214,346.857682,663.716859,377.874199,648.15015]],["curveTo",[408.890716,632.58344,433.998861,614.300157,453.198633,593.300299]],["lineTo",[431.600171,568.702264]],["curveTo",[411.500214,588.46903,388.250178,604.5608,361.850064,616.977574]],["curveTo",[335.44995,629.394347,305.299729,635.602734,271.399402,635.602734]],["curveTo",[226.665528,635.602734,190.531753,624.494233,162.998078,602.277232]],["curveTo",[135.464403,580.060231,121.697565,550.534814,121.697565,513.700982]],["curveTo",[121.697565,487.233946,127.905952,465.75032,140.322725,449.250107]],["curveTo",[152.739499,432.749893,168.022925,419.449737,186.173003,409.349637]],["curveTo",[204.323081,399.249537,221.164744,390.516161,236.697992,383.149509]],["lineTo",[337.548484,337.899188]],["curveTo",[362.981774,326.599174,386.298448,313.890859,407.498505,299.774242]],["curveTo",[428.698562,285.657625,445.565285,267.899331,458.098676,246.499359]],["curveTo",[470.632066,225.099388,476.898761,197.499359,476.898761,163.699274]],["curveTo",[476.898761,130.199345,468.49046,100.149438,451.673857,73.549551]],["curveTo",[434.857255,46.949665,410.774028,26.041435,379.424178,10.824861]],["curveTo",[348.074327,-4.391713,311.09953,-12.0,268.499786,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.500193,-12]],["curveTo",[172.500322,-12.0,149.458736,-7.375048,128.375435,1.874855]],["curveTo",[107.292134,11.124758,90.431033,25.360778,77.792134,44.582915]],["curveTo",[65.153234,63.805051,58.833784,88.360585,58.833784,118.249517]],["curveTo",[58.833784,171.749452,83.986632,212.360521,134.292327,240.082721]],["curveTo",[184.598022,267.804922,263.445432,287.610488,370.834557,299.49942]],["curveTo",[371.390156,324.721943,368.834622,349.527831,363.167955,373.917085]],["curveTo",[357.501288,398.306339,346.181774,418.334428,329.209412,434.001353]],["curveTo",[312.237051,449.668277,287.556243,457.501739,255.166989,457.501739]],["curveTo",[222.0,457.501739,191.930325,451.182225,164.957963,438.543197]],["curveTo",[137.985601,425.904168,116.555083,413.834686,100.666409,402.33475]],["lineTo",[82.50058,428.999614]],["curveTo",[93.166989,437.277284,107.333462,446.249452,125.0,455.916119]],["curveTo",[142.666538,465.582786,162.888674,473.860585,185.666409,480.749517]],["curveTo",[208.444144,487.638449,232.833011,491.082915,258.833011,491.082915]],["curveTo",[296.444015,491.082915,326.152203,482.763465,347.957576,466.124565]],["curveTo",[369.762949,449.485666,385.207222,427.319063,394.290394,399.624758]],["curveTo",[403.373567,371.930454,407.915153,341.500193,407.915153,308.333977]],["lineTo",[407.915153,0]],["lineTo",[376.167375,0]],["lineTo",[371.834171,63.166989]],["lineTo",[369.667568,63.166989]],["curveTo",[345.167504,43.278057,318.292391,25.777992,289.04223,10.666795]],["curveTo",[259.792069,-4.444402,229.278057,-12.0,197.500193,-12]],["closePath",[]],["moveTo",[202.332432,21.497874]],["curveTo",[230.888288,21.497874,258.402493,28.261854,284.875048,41.789815]],["curveTo",[311.347603,55.317775,340.000773,74.998583,370.834557,100.832238]],["lineTo",[370.834557,269.001159]],["curveTo",[302.944852,261.223296,249.124758,250.709283,209.374275,237.459122]],["curveTo",[169.623792,224.208961,141.040105,207.903202,123.623212,188.541844]],["curveTo",[106.20632,169.180486,97.497874,146.277413,97.497874,119.832625]],["curveTo",[97.497874,84.498776,107.98412,59.276189,128.95661,44.164863]],["curveTo",[149.929101,29.053537,174.387708,21.497874,202.332432,21.497874]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[272.999614,-12]],["curveTo",[232.499807,-12.0,195.583301,-1.986116,162.250097,18.041651]],["curveTo",[128.916892,38.069418,102.347539,66.791618,82.542037,104.208253]],["curveTo",[62.736535,141.624887,52.833784,186.444273,52.833784,238.666409]],["curveTo",[52.833784,290.777477,62.764302,335.694112,82.625338,373.416312]],["curveTo",[102.486374,411.138513,128.222394,440.180164,159.833398,460.541264]],["curveTo",[191.444402,480.902364,225.055405,491.082915,260.666409,491.082915]],["curveTo",[297.999485,491.082915,330.290974,482.457963,357.540878,465.20806]],["curveTo",[384.790781,447.958156,405.762949,422.902687,420.457383,390.041651]],["curveTo",[435.151817,357.180615,442.499034,317.166731,442.499034,270]],["curveTo",[442.499034,264.833527,442.457383,259.389254,442.374082,253.667182]],["curveTo",[442.290781,247.94511,441.554697,241.945239,440.165829,235.667568]],["lineTo",[74.664863,235.667568]],["lineTo",[74.664863,268.665636]],["lineTo",[406.501353,268.665636]],["curveTo",[406.501353,332.111068,393.306726,379.500644,366.917472,410.834364]],["curveTo",[340.528218,442.168084,305.166731,457.834944,260.833011,457.834944]],["curveTo",[233.610488,457.834944,207.040749,449.932064,181.123792,434.126304]],["curveTo",[155.206836,418.320545,133.817711,394.29252,116.956417,362.04223]],["curveTo",[100.095123,329.79194,91.664477,289.277799,91.664477,240.499807]],["curveTo",[91.664477,195.721814,99.706192,156.888223,115.789621,123.999034]],["curveTo",[131.873051,91.109844,153.970429,65.776253,182.081755,47.998261]],["curveTo",[210.193081,30.220268,241.498905,21.331272,275.999227,21.331272]],["curveTo",[302.832754,21.331272,327.124565,24.942405,348.874662,32.16467]],["curveTo",[370.624758,39.386935,389.83314,48.942533,406.499807,60.831465]],["lineTo",[422.665636,34.499807]],["curveTo",[404.276897,22.166602,383.638191,11.333333,360.749517,2.0]],["curveTo",[337.860843,-7.333333,308.610875,-12.0,272.999614,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.500214,-232.600598]],["curveTo",[208.966823,-232.600598,173.966823,-227.008899,144.500214,-215.825502]],["curveTo",[115.033604,-204.642105,92.383668,-189.100384,76.550406,-169.200342]],["curveTo",[60.717144,-149.300299,52.800513,-125.933647,52.800513,-99.100384]],["curveTo",[52.800513,-77.80037,59.275523,-56.941976,72.225545,-36.525203]],["curveTo",[85.175566,-16.108429,102.667236,2.0,124.700555,17.800085]],["lineTo",[124.700555,21.800085]],["curveTo",[112.667236,29.233376,102.625587,39.308344,94.575609,52.024989]],["curveTo",[86.52563,64.741635,82.500641,80.166595,82.500641,98.299872]],["curveTo",[82.500641,120.066638,88.617329,138.49167,100.850705,153.574968]],["curveTo",[113.084081,168.658266,124.367364,179.666524,134.700555,186.599744]],["lineTo",[134.700555,190.599744]],["curveTo",[119.867293,203.866439,106.092339,221.983127,93.375694,244.949808]],["curveTo",[80.659049,267.916489,74.300726,293.733162,74.300726,322.399829]],["curveTo",[74.300726,354.866296,81.775666,383.807846,96.725545,409.224477]],["curveTo",[111.675424,434.641108,131.683611,454.641108,156.750107,469.224477]],["curveTo",[181.816603,483.807846,209.83312,491.09953,240.799658,491.09953]],["curveTo",[254.166311,491.09953,266.257938,489.941193,277.074541,487.624519]],["curveTo",[287.891143,485.307846,296.766054,482.466183,303.699274,479.09953]],["lineTo",[464.998718,479.09953]],["lineTo",[464.998718,446.301581]],["lineTo",[350.997864,446.301581]],["lineTo",[350.997864,442.301581]],["curveTo",[366.43144,430.501495,379.57333,414.58465,390.423537,394.551047]],["curveTo",[401.273743,374.517443,406.698847,350.333761,406.698847,322]],["curveTo",[406.698847,289.666809,399.282216,260.708529,384.448953,235.12516]],["curveTo",[369.615691,209.541791,349.715791,189.341734,324.749252,174.524989]],["curveTo",[299.782714,159.708244,271.799516,152.299872,240.799658,152.299872]],["curveTo",[227.09953,152.299872,213.057739,154.224904,198.674284,158.074968]],["curveTo",[184.29083,161.925032,171.432436,167.08344,160.099103,173.550192]],["curveTo",[149.732308,165.08344,140.390503,155.216645,132.073686,143.949808]],["curveTo",[123.75687,132.68297,119.598462,118.632778,119.598462,101.799231]],["curveTo",[119.598462,84.165741,126.048484,69.05724,138.948526,56.473729]],["curveTo",[151.848569,43.890218,176.332052,37.598462,212.398975,37.598462]],["lineTo",[316.849637,37.598462]],["curveTo",[370.649438,37.598462,410.124235,28.856828,435.274028,11.373558]],["curveTo",[460.423822,-6.109711,472.998718,-33.734586,472.998718,-71.501068]],["curveTo",[472.998718,-98.634202,463.857112,-124.442404,445.5739,-148.925673]],["curveTo",[427.290688,-173.408942,401.390859,-193.47558,367.874413,-209.125587]],["curveTo",[334.357967,-224.775594,294.8999,-232.600598,249.500214,-232.600598]],["closePath",[]],["moveTo",[240.799658,183.998291]],["curveTo",[263.499929,183.998291,284.550192,189.823366,303.950449,201.473516]],["curveTo",[323.350705,213.123665,339.025915,229.340524,350.976079,250.124092]],["curveTo",[362.926242,270.907661,368.901324,294.999573,368.901324,322.399829]],["curveTo",[368.901324,364.73359,356.301153,398.050619,331.100812,422.350918]],["curveTo",[305.90047,446.651217,275.800085,458.801367,240.799658,458.801367]],["curveTo",[205.799231,458.801367,175.698847,446.651217,150.498505,422.350918]],["curveTo",[125.298163,398.050619,112.697992,364.73359,112.697992,322.399829]],["curveTo",[112.697992,294.999573,118.673074,270.907661,130.623238,250.124092]],["curveTo",[142.573402,229.340524,158.248612,213.123665,177.648868,201.473516]],["curveTo",[197.049124,189.823366,218.099388,183.998291,240.799658,183.998291]],["closePath",[]],["moveTo",[252.799658,-200.702264]],["curveTo",[289.833405,-200.702264,321.85875,-194.593835,348.875694,-182.376976]],["curveTo",[375.892638,-170.160117,396.901182,-154.376548,411.901324,-135.026271]],["curveTo",[426.901467,-115.675993,434.401538,-95.63406,434.401538,-74.90047]],["curveTo",[434.401538,-47.63349,424.768119,-28.216574,405.501282,-16.649722]],["curveTo",[386.234444,-5.082871,358.017514,0.700555,320.850491,0.700555]],["lineTo",[214.398975,0.700555]],["curveTo",[210.866012,0.700555,203.457853,1.358892,192.174498,2.675566]],["curveTo",[180.891143,3.99224,168.732735,5.96725,155.699274,8.600598]],["curveTo",[132.598889,-6.899616,115.906949,-23.808202,105.623452,-42.12516]],["curveTo",[95.339954,-60.442119,90.198206,-78.334045,90.198206,-95.80094]],["curveTo",[90.198206,-127.001282,104.431653,-152.284921,132.898548,-171.651858]],["curveTo",[161.365442,-191.018795,201.332479,-200.702264,252.799658,-200.702264]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.199915,-12]],["curveTo",[170.499786,-12.0,136.983127,-5.975011,107.649936,6.074968]],["curveTo",[78.316745,18.124947,53.500214,34.266553,33.200342,54.499786]],["lineTo",[53.899188,78.298163]],["curveTo",[74.065784,61.564858,95.940766,47.814823,119.524135,37.048056]],["curveTo",[143.107504,26.28129,173.366083,20.897907,210.299872,20.897907]],["curveTo",[249.200342,20.897907,278.659049,30.839741,298.675993,50.723409]],["curveTo",[318.692937,70.607077,328.70141,93.465755,328.70141,119.299445]],["curveTo",[328.70141,139.999715,322.64303,157.149936,310.526271,170.750107]],["curveTo",[298.409512,184.350278,283.301082,195.708743,265.200982,204.825502]],["curveTo",[247.100883,213.942261,228.734159,221.833974,210.100812,228.500641]],["curveTo",[185.500783,237.167307,161.63406,246.908942,138.500641,257.725545]],["curveTo",[115.367222,268.542147,96.38381,282.35042,81.550406,299.150363]],["curveTo",[66.717001,315.950306,59.300299,337.966966,59.300299,365.200342]],["curveTo",[59.300299,399.333333,72.408515,428.849779,98.624947,453.74968]],["curveTo",[124.841378,478.64958,162.532821,491.09953,211.699274,491.09953]],["curveTo",[238.299018,491.09953,263.148797,486.516232,286.248612,477.349637]],["curveTo",[309.348427,468.183041,329.364944,456.466467,346.298163,442.199915]],["lineTo",[325.599317,418.401538]],["curveTo",[309.46604,429.834828,292.399402,439.326499,274.399402,446.876548]],["curveTo",[256.399402,454.426598,234.965969,458.201623,210.099103,458.201623]],["curveTo",[171.365229,458.201623,142.94817,448.74313,124.847928,429.826143]],["curveTo",[106.747686,410.909156,97.697565,390.133846,97.697565,367.500214]],["curveTo",[97.697565,348.766624,102.905952,333.408088,113.322725,321.424605]],["curveTo",[123.739499,309.441122,137.347928,299.366012,154.148014,291.199274]],["curveTo",[170.948099,283.032536,189.064787,275.29916,208.498078,267.999146]],["curveTo",[233.831411,258.099103,258.714794,247.899117,283.148227,237.399188]],["curveTo",[307.58166,226.89926,327.68176,212.765983,343.448526,194.999359]],["curveTo",[359.215293,177.232735,367.098676,152.799373,367.098676,121.699274]],["curveTo",[367.098676,97.966111,360.990389,75.974584,348.773815,55.72469]],["curveTo",[336.55724,35.474797,318.574042,19.124875,294.82422,6.674925]],["curveTo",[271.074398,-5.775025,242.19963,-12.0,208.199915,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[225.599317,-12]],["curveTo",[191.466325,-12.0,160.066567,-4.800014,131.400043,9.599957]],["curveTo",[102.733518,23.999929,79.866937,44.574897,62.800299,71.324861]],["curveTo",[45.733661,98.074826,37.200342,129.799801,37.200342,166.499786]],["curveTo",[37.200342,196.832977,44.375338,224.032892,58.725331,248.09953]],["curveTo",[73.075324,272.166168,91.441976,294.132849,113.825288,313.999573]],["curveTo",[136.2086,333.866296,160.083583,352.833049,185.450235,370.899829]],["curveTo",[210.816887,388.96661,234.69187,407.166738,257.075182,425.500214]],["curveTo",[279.458494,443.833689,297.825146,463.183825,312.175139,483.550619]],["curveTo",[326.525132,503.917414,333.700128,526.634202,333.700128,551.700982]],["curveTo",[333.700128,573.301153,328.158408,593.301367,317.074968,611.701623]],["curveTo",[305.991528,630.10188,286.832977,639.302008,259.599317,639.302008]],["curveTo",[229.832265,639.302008,206.456998,628.426883,189.473516,606.676634]],["curveTo",[172.490033,584.926385,163.998291,558.634487,163.998291,527.80094]],["curveTo",[163.998291,493.63406,171.364944,457.658835,186.098249,419.875267]],["curveTo",[200.831553,382.091699,220.273174,344.31646,244.42311,306.549551]],["curveTo",[268.573046,268.782643,295.564645,232.982415,325.397907,199.148868]],["curveTo",[355.231169,165.315321,385.231098,135.548484,415.397693,109.848355]],["curveTo",[440.864303,88.114908,465.555888,69.89812,489.472448,55.197992]],["curveTo",[513.389008,40.497864,535.564004,29.264417,555.997437,21.497651]],["lineTo",[542.598035,-12]],["curveTo",[521.231667,-4.099957,497.915278,7.508401,472.648868,22.825075]],["curveTo",[447.382458,38.141749,420.882671,57.750107,393.149509,81.65015]],["curveTo",[360.616403,110.216859,328.666595,142.450235,297.300085,178.350278]],["curveTo",[265.933575,214.25032,237.483696,252.133704,211.950449,292.000427]],["curveTo",[186.417201,331.867151,166.108928,371.708885,151.02563,411.52563]],["curveTo",[135.942332,451.342375,128.400683,489.467464,128.400683,525.900897]],["curveTo",[128.400683,554.067493,134.008971,579.10907,145.225545,601.02563]],["curveTo",[156.442119,622.94219,172.091983,640.167094,192.175139,652.700342]],["curveTo",[212.258294,665.23359,235.299729,671.500214,261.299445,671.500214]],["curveTo",[296.19906,671.500214,322.823722,660.358607,341.17343,638.075395]],["curveTo",[359.523138,615.792183,368.697992,587.63406,368.697992,553.601025]],["curveTo",[368.697992,526.167735,361.714652,501.284423,347.747971,478.951089]],["curveTo",[333.78129,456.617756,315.672932,435.826071,293.422896,416.576036]],["curveTo",[271.172861,397.326,247.431155,378.467606,222.197779,360.000854]],["curveTo",[196.964403,341.534102,173.222697,322.775666,150.972661,303.725545]],["curveTo",[128.722626,284.675424,110.614267,264.166951,96.647587,242.200128]],["curveTo",[82.680906,220.233305,75.697565,195.799801,75.697565,168.899616]],["curveTo",[75.697565,138.965969,82.689307,112.982344,96.672789,90.94874]],["curveTo",[110.656272,68.915136,129.206464,51.764915,152.323366,39.498078]],["curveTo",[175.440268,27.23124,200.698847,21.097821,228.099103,21.097821]],["curveTo",[258.93265,21.097821,287.891143,29.139542,314.974584,45.222982]],["curveTo",[342.058024,61.306422,366.566425,81.531539,388.499786,105.898334]],["curveTo",[416.36651,137.498505,440.141535,173.423679,459.824861,213.673857]],["curveTo",[479.508187,253.924035,495.633205,297.232522,508.199915,343.599317]],["lineTo",[544.497223,343.599317]],["curveTo",[530.563862,294.965969,512.980564,248.465969,491.74733,204.099317]],["curveTo",[470.514097,159.732664,445.030898,120.03275,415.297736,84.999573]],["curveTo",[390.331197,56.666382,362.564716,33.416489,331.998291,15.249893]],["curveTo",[301.431867,-2.916702,265.965542,-12.0,225.599317,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[384.832238,-143.832625]],["curveTo",[336.72117,-143.832625,292.346251,-136.013175,251.70748,-120.374275]],["curveTo",[211.068709,-104.735376,175.888288,-82.179842,146.166216,-52.707673]],["curveTo",[116.444144,-23.235505,93.29149,12.972748,76.708253,55.917085]],["curveTo",[60.125016,98.861422,51.833398,147.611261,51.833398,202.166602]],["curveTo",[51.833398,267.166473,62.416699,325.971911,83.583301,378.582915]],["curveTo",[104.749903,431.193918,133.65259,476.277155,170.291361,513.832625]],["curveTo",[206.930131,551.388094,248.971653,580.221363,296.415926,600.332432]],["curveTo",[343.860199,620.4435,393.554439,630.499034,445.498647,630.499034]],["curveTo",[509.387321,630.499034,564.539911,617.374082,610.956417,591.124179]],["curveTo",[657.372923,564.874275,693.206127,528.207737,718.45603,481.124565]],["curveTo",[743.705934,434.041393,756.330886,378.944337,756.330886,315.833398]],["curveTo",[756.330886,273.555727,750.761468,236.319707,739.622633,204.125338]],["curveTo",[728.483798,171.930969,714.108878,144.792134,696.497874,122.708833]],["curveTo",[678.886871,100.625531,659.400883,84.069997,638.039911,73.04223]],["curveTo",[616.67894,62.014463,595.776318,56.50058,575.332045,56.50058]],["curveTo",[549.109909,56.50058,527.207222,62.708897,509.623985,75.125531]],["curveTo",[492.040749,87.542166,482.165829,106.77825,479.999227,132.833784]],["lineTo",[477.999227,132.833784]],["curveTo",[460.332432,113.833913,441.387966,97.389512,421.165829,83.50058]],["curveTo",[400.943693,69.611648,379.221492,62.667182,355.999227,62.667182]],["curveTo",[325.388352,62.667182,298.819063,74.014334,276.291361,96.708639]],["curveTo",[253.763658,119.402944,242.499807,153.166731,242.499807,198]],["curveTo",[242.499807,221.83314,246.583108,246.846895,254.74971,273.041264]],["curveTo",[262.916312,299.235633,274.930131,323.555019,290.791167,345.99942]],["curveTo",[306.652203,368.443822,326.152139,386.666023,349.290974,400.666023]],["curveTo",[372.429809,414.666023,398.665894,421.666023,427.999227,421.666023]],["curveTo",[445.943693,421.666023,461.874275,417.652139,475.790974,409.624372]],["curveTo",[489.707673,401.596605,501.610488,389.49942,511.49942,373.332818]],["lineTo",[513.49942,373.332818]],["lineTo",[521.915926,413.49942]],["lineTo",[551.831079,413.49942]],["lineTo",[517.16467,227.166989]],["curveTo",[505.608942,180.27767,505.053344,145.499549,515.497874,122.832625]],["curveTo",[525.942405,100.1657,546.775931,88.832238,577.998454,88.832238]],["curveTo",[599.165185,88.832238,620.873631,97.58227,643.123792,115.082335]],["curveTo",[665.373953,132.582399,683.999098,158.096412,698.999227,191.624372]],["curveTo",[713.999356,225.152332,721.49942,265.83314,721.49942,313.666795]],["curveTo",[721.49942,357.000386,715.110488,396.18113,702.332625,431.209026]],["curveTo",[689.554761,466.236922,670.957448,496.056436,646.540684,520.667568]],["curveTo",[622.123921,545.278701,592.804407,564.264818,558.582142,577.625918]],["curveTo",[524.359876,590.987018,485.804342,597.667568,442.915539,597.667568]],["curveTo",[397.248744,597.667568,353.206965,588.403653,310.790201,569.875821]],["curveTo",[268.373438,551.34799,230.262241,524.861809,196.45661,490.417279]],["curveTo",[162.65098,455.972748,135.956481,414.583752,116.373116,366.25029]],["curveTo",[96.78975,317.916828,86.998068,263.944466,86.998068,204.333205]],["curveTo",[86.998068,155.83314,94.178618,112.221943,108.539718,73.499614]],["curveTo",[122.900818,34.777284,143.373051,1.721621,169.956417,-25.667375]],["curveTo",[196.539783,-53.056371,228.220397,-74.125918,264.998261,-88.876015]],["curveTo",[301.776124,-103.626111,342.554053,-111.001159,387.332045,-111.001159]],["curveTo",[420.832238,-111.001159,451.360134,-106.445561,478.915733,-97.334364]],["curveTo",[506.471331,-88.223167,532.165829,-76.500837,555.999227,-62.167375]],["lineTo",[571.415346,-90.165829]],["curveTo",[543.19321,-107.999227,513.568258,-121.402042,482.540491,-130.374275]],["curveTo",[451.512724,-139.346508,418.943306,-143.832625,384.832238,-143.832625]],["closePath",[]],["moveTo",[359.915153,95.165443]],["curveTo",[376.804214,95.165443,394.498841,100.679391,412.999034,111.707287]],["curveTo",[431.499227,122.735182,450.832754,139.999227,470.999614,163.49942]],["lineTo",[502.499807,338.833784]],["curveTo",[490.332947,358.111841,478.513368,371.320352,467.041071,378.459316]],["curveTo",[455.568773,385.59828,442.026994,389.167762,426.415733,389.167762]],["curveTo",[395.859876,389.167762,369.526285,379.473264,347.41496,360.084267]],["curveTo",[325.303634,340.695271,308.234023,316.431227,296.206127,287.292134]],["curveTo",[284.178231,258.153041,278.164283,229.194498,278.164283,200.416506]],["curveTo",[278.164283,161.582721,286.23383,134.332432,302.372923,118.665636]],["curveTo",[318.512015,102.998841,337.692759,95.165443,359.915153,95.165443]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[241.399402,-12]],["curveTo",[202.532821,-12.0,167.991243,-4.349993,137.774669,10.950021]],["curveTo",[107.558095,26.250036,83.724833,47.216717,66.274883,73.850064]],["curveTo",[48.824932,100.483412,40.099957,130.500071,40.099957,163.900043]],["curveTo",[40.099957,193.000142,46.658337,219.250249,59.775096,242.650363]],["curveTo",[72.891855,266.050477,89.341948,286.49224,109.125374,303.975651]],["curveTo",[128.9088,321.459063,148.800513,335.534102,168.800513,346.200769]],["lineTo",[168.800513,350.200769]],["curveTo",[146.033889,365.734017,125.075538,385.500641,105.925459,409.500641]],["curveTo",[86.775381,433.500641,77.200342,462.467322,77.200342,496.400683]],["curveTo",[77.200342,526.767194,84.616973,553.708743,99.450235,577.225331]],["curveTo",[114.283497,600.741919,134.05838,619.033533,158.774883,632.100171]],["curveTo",[183.491385,645.166809,211.132849,651.700128,241.699274,651.700128]],["curveTo",[295.965827,651.700128,338.099032,635.866795,368.098889,604.200128]],["curveTo",[398.098747,572.533461,413.098676,532.866866,413.098676,485.200342]],["curveTo",[413.098676,461.433575,408.006977,439.000142,397.82358,417.900043]],["curveTo",[387.640182,396.799943,375.440125,377.84985,361.223409,361.049765]],["curveTo",[347.006692,344.24968,333.815036,330.849637,321.648441,320.849637]],["lineTo",[321.648441,316.849637]],["curveTo",[339.781717,305.549623,357.490033,292.866296,374.773387,278.799658]],["curveTo",[392.056742,264.73302,406.1651,247.674712,417.098462,227.624733]],["curveTo",[428.031824,207.574754,433.498505,183.233091,433.498505,154.599744]],["curveTo",[433.498505,123.766482,425.573544,95.808202,409.723622,70.724904]],["curveTo",[393.873701,45.641606,371.465471,25.583298,342.498932,10.549979]],["curveTo",[313.532394,-4.48334,279.83255,-12.0,241.399402,-12]],["closePath",[]],["moveTo",[288.899616,332.398975]],["curveTo",[317.233376,355.232522,338.80037,379.324434,353.600598,404.674712]],["curveTo",[368.400826,430.024989,375.80094,456.666951,375.80094,484.600598]],["curveTo",[375.80094,508.767478,370.609213,531.084366,360.225758,551.55126]],["curveTo",[349.842304,572.018155,334.592126,588.41001,314.475224,600.726826]],["curveTo",[294.358323,613.043642,270.033034,619.20205,241.499359,619.20205]],["curveTo",[204.632208,619.20205,174.348498,607.560231,150.648227,584.276591]],["curveTo",[126.947957,560.992952,115.097821,531.700982,115.097821,496.400683]],["curveTo",[115.097821,465.300299,123.672932,439.683326,140.823152,419.549765]],["curveTo",[157.973373,399.416204,179.881959,382.649366,206.548911,369.249252]],["curveTo",[233.215862,355.849139,260.666097,343.565713,288.899616,332.398975]],["closePath",[]],["moveTo",[241.599317,20.498078]],["curveTo",[272.633063,20.498078,299.650078,26.46483,322.650363,38.398334]],["curveTo",[345.650648,50.331838,363.484195,66.515378,376.151004,86.948953]],["curveTo",[388.817813,107.382529,395.151217,130.03275,395.151217,154.899616]],["curveTo",[395.151217,188.600028,385.576107,216.000356,366.425886,237.100598]],["curveTo",[347.275666,258.20084,322.375409,275.776022,291.725117,289.826143]],["curveTo",[261.074826,303.876264,228.966254,317.501353,195.399402,330.70141]],["curveTo",[161.998861,311.167877,134.056742,287.717642,111.573046,260.350705]],["curveTo",[89.089349,232.983768,77.847501,201.166738,77.847501,164.899616]],["curveTo",[77.847501,138.099388,84.847572,113.80749,98.847715,92.023921]],["curveTo",[112.847857,70.240353,132.198064,52.873487,156.898334,39.923323]],["curveTo",[181.598605,26.97316,209.832265,20.498078,241.599317,20.498078]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.499786,257.500214]],["curveTo",[151.666524,257.500214,126.799943,265.516873,105.900043,281.550192]],["curveTo",[85.000142,297.583511,68.958565,321.100171,57.77531,352.100171]],["curveTo",[46.592055,383.100171,41.000427,420.900185,41.000427,465.500214]],["curveTo",[41.000427,531.466894,53.517016,582.291898,78.550192,617.975224]],["curveTo",[103.583369,653.65855,137.566567,671.500214,180.499786,671.500214]],["curveTo",[224.066353,671.500214,258.207888,653.65855,282.924391,617.975224]],["curveTo",[307.640894,582.291898,319.999146,531.466894,319.999146,465.500214]],["curveTo",[319.999146,420.900185,314.407518,383.100171,303.224263,352.100171]],["curveTo",[292.041008,321.100171,275.99943,297.583511,255.09953,281.550192]],["curveTo",[234.19963,265.516873,209.333049,257.500214,180.499786,257.500214]],["closePath",[]],["moveTo",[180.499786,286.898761]],["curveTo",[212.833547,286.898761,238.43386,302.657269,257.300726,334.174284]],["curveTo",[276.167592,365.6913,285.601025,409.46661,285.601025,465.500214]],["curveTo",[285.601025,522.167165,276.167592,565.767478,257.300726,596.301153]],["curveTo",[238.43386,626.834828,212.833547,642.101666,180.499786,642.101666]],["curveTo",[148.832693,642.101666,123.399046,626.834828,104.198847,596.301153]],["curveTo",[84.998647,565.767478,75.398548,522.167165,75.398548,465.500214]],["curveTo",[75.398548,409.46661,84.998647,365.6913,104.198847,334.174284]],["curveTo",[123.399046,302.657269,148.832693,286.898761,180.499786,286.898761]],["closePath",[]],["moveTo",[196.899616,-12.0]],["lineTo",[565.800085,671.500214]],["lineTo",[600.998718,671.500214]],["lineTo",[232.098249,-12.0]],["closePath",[]],["moveTo",[621.098676,-12.0]],["curveTo",[592.265414,-12.0,567.398832,-3.98334,546.498932,12.049979]],["curveTo",[525.599032,28.083298,509.557454,51.599957,498.374199,82.599957]],["curveTo",[487.190944,113.599957,481.599317,151.399972,481.599317,196.0]],["curveTo",[481.599317,261.966681,494.115905,312.791684,519.149082,348.475011]],["curveTo",[544.182258,384.158337,578.165456,402.0,621.098676,402.0]],["curveTo",[664.665243,402.0,698.806778,384.158337,723.523281,348.475011]],["curveTo",[748.239784,312.791684,760.598035,261.966681,760.598035,196.0]],["curveTo",[760.598035,151.399972,755.006408,113.599957,743.823152,82.599957]],["curveTo",[732.639897,51.599957,716.59832,28.083298,695.698419,12.049979]],["curveTo",[674.798519,-3.98334,649.931938,-12.0,621.098676,-12.0]],["closePath",[]],["moveTo",[621.098676,17.398548]],["curveTo",[653.432436,17.398548,679.03275,33.157055,697.899616,64.674071]],["curveTo",[716.766482,96.191086,726.199915,139.966396,726.199915,196.0]],["curveTo",[726.199915,252.666951,716.766482,296.267265,697.899616,326.80094]],["curveTo",[679.03275,357.334615,653.432436,372.601452,621.098676,372.601452]],["curveTo",[589.431582,372.601452,563.997935,357.334615,544.797736,326.80094]],["curveTo",[525.597537,296.267265,515.997437,252.666951,515.997437,196.0]],["curveTo",[515.997437,139.966396,525.597537,96.191086,544.797736,64.674071]],["curveTo",[563.997935,33.157055,589.431582,17.398548,621.098676,17.398548]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]],["moveTo",[164.599744,726.300726]],["curveTo",[155.233376,726.300726,147.175281,729.367293,140.425459,735.500427]],["curveTo",[133.675637,741.633561,130.300726,750.0,130.300726,760.599744]],["curveTo",[130.300726,771.199487,133.675637,779.565926,140.425459,785.69906]],["curveTo",[147.175281,791.832194,155.233376,794.898761,164.599744,794.898761]],["curveTo",[174.632778,794.898761,182.85754,791.832194,189.274028,785.69906]],["curveTo",[195.690517,779.565926,198.898761,771.199487,198.898761,760.599744]],["curveTo",[198.898761,750.0,195.690517,741.633561,189.274028,735.500427]],["curveTo",[182.85754,729.367293,174.632778,726.300726,164.599744,726.300726]],["closePath",[]],["moveTo",[358.398975,726.300726]],["curveTo",[349.032607,726.300726,340.974512,729.367293,334.22469,735.500427]],["curveTo",[327.474868,741.633561,324.099957,750.0,324.099957,760.599744]],["curveTo",[324.099957,771.199487,327.474868,779.565926,334.22469,785.69906]],["curveTo",[340.974512,791.832194,349.032607,794.898761,358.398975,794.898761]],["curveTo",[368.432009,794.898761,376.656771,791.832194,383.073259,785.69906]],["curveTo",[389.489748,779.565926,392.697992,771.199487,392.697992,760.599744]],["curveTo",[392.697992,750.0,389.489748,741.633561,383.073259,735.500427]],["curveTo",[376.656771,729.367293,368.432009,726.300726,358.398975,726.300726]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[272.999614,-12]],["curveTo",[232.499807,-12.0,195.583301,-1.986116,162.250097,18.041651]],["curveTo",[128.916892,38.069418,102.347539,66.791618,82.542037,104.208253]],["curveTo",[62.736535,141.624887,52.833784,186.444273,52.833784,238.666409]],["curveTo",[52.833784,290.777477,62.764302,335.694112,82.625338,373.416312]],["curveTo",[102.486374,411.138513,128.222394,440.180164,159.833398,460.541264]],["curveTo",[191.444402,480.902364,225.055405,491.082915,260.666409,491.082915]],["curveTo",[297.999485,491.082915,330.290974,482.457963,357.540878,465.20806]],["curveTo",[384.790781,447.958156,405.762949,422.902687,420.457383,390.041651]],["curveTo",[435.151817,357.180615,442.499034,317.166731,442.499034,270]],["curveTo",[442.499034,264.833527,442.457383,259.389254,442.374082,253.667182]],["curveTo",[442.290781,247.94511,441.554697,241.945239,440.165829,235.667568]],["lineTo",[74.664863,235.667568]],["lineTo",[74.664863,268.665636]],["lineTo",[406.501353,268.665636]],["curveTo",[406.501353,332.111068,393.306726,379.500644,366.917472,410.834364]],["curveTo",[340.528218,442.168084,305.166731,457.834944,260.833011,457.834944]],["curveTo",[233.610488,457.834944,207.040749,449.932064,181.123792,434.126304]],["curveTo",[155.206836,418.320545,133.817711,394.29252,116.956417,362.04223]],["curveTo",[100.095123,329.79194,91.664477,289.277799,91.664477,240.499807]],["curveTo",[91.664477,195.721814,99.706192,156.888223,115.789621,123.999034]],["curveTo",[131.873051,91.109844,153.970429,65.776253,182.081755,47.998261]],["curveTo",[210.193081,30.220268,241.498905,21.331272,275.999227,21.331272]],["curveTo",[302.832754,21.331272,327.124565,24.942405,348.874662,32.16467]],["curveTo",[370.624758,39.386935,389.83314,48.942533,406.499807,60.831465]],["lineTo",[422.665636,34.499807]],["curveTo",[404.276897,22.166602,383.638191,11.333333,360.749517,2.0]],["curveTo",[337.860843,-7.333333,308.610875,-12.0,272.999614,-12]],["closePath",[]],["moveTo",[189.500214,570.0]],["lineTo",[328.000854,721.999146]],["lineTo",[373.097821,721.999146]],["lineTo",[375.497651,717.299445]],["lineTo",[224.498078,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]],["moveTo",[261.4827,699.800085]],["curveTo",[233.927524,699.800085,210.99167,707.391713,192.675139,722.574968]],["curveTo",[174.358607,737.758223,165.200342,758.474797,165.200342,784.72469]],["curveTo",[165.200342,810.974584,174.358607,831.682828,192.675139,846.849423]],["curveTo",[210.99167,862.016019,233.927524,869.599317,261.4827,869.599317]],["curveTo",[289.726755,869.599317,312.840382,862.018994,330.82358,846.858348]],["curveTo",[348.806778,831.697702,357.798377,810.978153,357.798377,784.699701]],["curveTo",[357.798377,758.466467,348.806778,737.758223,330.82358,722.574968]],["curveTo",[312.840382,707.391713,289.726755,699.800085,261.4827,699.800085]],["closePath",[]],["moveTo",[261.483577,723.699274]],["curveTo",[280.460922,723.699274,296.291328,728.807632,308.974797,739.024349]],["curveTo",[321.658266,749.241065,328.0,764.461423,328.0,784.685421]],["curveTo",[328.0,805.561807,321.658266,820.950021,308.974797,830.850064]],["curveTo",[296.291328,840.750107,280.460922,845.700128,261.483577,845.700128]],["curveTo",[243.160623,845.700128,227.499074,840.750107,214.498932,830.850064]],["curveTo",[201.49879,820.950021,194.998718,805.561807,194.998718,784.685421]],["curveTo",[194.998718,764.461423,201.49879,749.241065,214.498932,739.024349]],["curveTo",[227.499074,728.807632,243.160623,723.699274,261.483577,723.699274]],["closePath",[]]]}]},{"coordinates":[777.0],"glyphs":[{"char":"A","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[75.03125,0]],["lineTo",[75.03125,651.082589]],["lineTo",[487.886161,651.082589]],["lineTo",[487.886161,519.640624]],["lineTo",[231.875001,519.640624]],["lineTo",[231.875001,399.886161]],["lineTo",[450.154018,399.886161]],["lineTo",[450.154018,268.444196]],["lineTo",[231.875001,268.444196]],["lineTo",[231.875001,131.441965]],["lineTo",[497.886161,131.441965]],["lineTo",[497.886161,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[343.564323,-12]],["curveTo",[284.466426,-12.0,232.521441,1.59021,187.729369,28.770631]],["curveTo",[142.937297,55.951051,108.00991,94.9197,82.947208,145.676577]],["curveTo",[57.884505,196.433454,45.353154,257.388349,45.353154,328.541262]],["curveTo",[45.353154,399.694174,57.884505,460.149069,82.947208,509.905946]],["curveTo",[108.00991,559.662823,142.937297,597.631472,187.729369,623.811892]],["curveTo",[232.521441,649.992313,284.466426,663.082523,343.564323,663.082523]],["curveTo",[402.842641,663.082523,454.877836,649.992313,499.669908,623.811892]],["curveTo",[544.461979,597.631472,579.389366,559.662823,604.452069,509.905946]],["curveTo",[629.514772,460.149069,642.046123,399.694174,642.046123,328.541262]],["curveTo",[642.046123,257.388349,629.514772,196.433454,604.452069,145.676577]],["curveTo",[579.389366,94.9197,544.461979,55.951051,499.669908,28.770631]],["curveTo",[454.877836,1.59021,402.842641,-12.0,343.564323,-12]],["closePath",[]],["moveTo",[343.564323,123.280353]],["curveTo",[371.90979,123.280353,396.394415,131.654948,417.0182,148.404138]],["curveTo",[437.641985,165.153328,453.495139,188.898674,464.577662,219.640177]],["curveTo",[475.660185,250.381679,481.201447,286.682041,481.201447,328.541262]],["curveTo",[481.201447,370.400482,475.660185,406.155739,464.577662,435.807031]],["curveTo",[453.495139,465.458323,437.641985,488.20367,417.0182,504.04307]],["curveTo",[396.394415,519.88247,371.90979,527.80217,343.564323,527.80217]],["curveTo",[315.399277,527.80217,290.959756,519.88247,270.245761,504.04307]],["curveTo",[249.531766,488.20367,233.633507,465.458323,222.550984,435.807031]],["curveTo",[211.468461,406.155739,205.927199,370.400482,205.927199,328.541262]],["curveTo",[205.927199,286.682041,211.468461,250.381679,222.550984,219.640177]],["curveTo",[233.633507,188.898674,249.531766,165.153328,270.245761,148.404138]],["curveTo",[290.959756,131.654948,315.399277,123.280353,343.564323,123.280353]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[276.917411,-12]],["curveTo",[233.601191,-12.0,190.803944,-4.283482,148.52567,11.149554]],["curveTo",[106.247396,26.582589,67.982887,50.415923,33.732143,82.649554]],["lineTo",[116.370536,184.689733]],["curveTo",[139.70387,165.084822,166.010603,149.939547,195.290737,139.253907]],["curveTo",[224.570871,128.568267,252.96875,123.225448,280.484375,123.225448]],["curveTo",[309.443452,123.225448,330.82087,128.217821,344.616629,138.202568]],["curveTo",[358.412388,148.187315,365.310267,161.99256,365.310267,179.618304]],["curveTo",[365.310267,192.305804,361.575706,202.429687,354.106584,209.989955]],["curveTo",[346.637462,217.550223,336.114211,224.470424,322.53683,230.750558]],["curveTo",[308.959449,237.030691,292.656994,243.98177,273.629464,251.603794]],["lineTo",[190.356026,286.01116]],["curveTo",[167.455729,295.866815,145.969494,308.780877,125.897321,324.753348]],["curveTo",[105.825148,340.725818,89.66276,360.382998,77.410156,383.724888]],["curveTo",[65.157552,407.066778,59.03125,434.440476,59.03125,465.845982]],["curveTo",[59.03125,502.529761,69.111979,535.677455,89.273437,565.289062]],["curveTo",[109.434896,594.900669,137.231213,618.602492,172.662388,636.394531]],["curveTo",[208.093564,654.18657,248.862351,663.082589,294.96875,663.082589]],["curveTo",[334.374256,663.082589,372.531436,656.140997,409.440291,642.257812]],["curveTo",[446.349145,628.374628,478.892858,608.127232,507.071429,581.515625]],["lineTo",[424.866071,479.475445]],["curveTo",[403.082589,495.313987,381.235491,507.337238,359.324777,515.5452]],["curveTo",[337.414063,523.753161,315.962054,527.857142,294.96875,527.857142]],["curveTo",[271.559524,527.857142,252.822173,523.342447,238.756697,514.313057]],["curveTo",[224.691221,505.283667,217.658483,492.172618,217.658483,474.97991]],["curveTo",[217.658483,463.103422,221.762464,453.123884,229.970425,445.041295]],["curveTo",[238.178386,436.958705,249.814175,429.831473,264.877791,423.659598]],["curveTo",[279.941407,417.487723,296.923364,410.437872,315.823662,402.510045]],["lineTo",[397.313617,369.886161]],["curveTo",[424.430432,359.147322,447.466519,345.737538,466.421876,329.656808]],["curveTo",[485.377233,313.576079,499.999257,294.274554,510.287947,271.752232]],["curveTo",[520.576637,249.229911,525.720983,222.752232,525.720983,192.319197]],["curveTo",[525.720983,156.013393,516.050038,122.315848,496.708148,91.226563]],["curveTo",[477.366258,60.137277,449.074219,35.160528,411.832032,16.296317]],["curveTo",[374.589844,-2.567894,329.618304,-12.0,276.917411,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[189.082523,-12]],["curveTo",[158.470872,-12.0,132.043485,-5.149069,109.800362,8.552792]],["curveTo",[87.557239,22.254654,70.390572,40.546726,58.300362,63.429008]],["curveTo",[46.210151,86.31129,40.165046,111.599518,40.165046,139.293692]],["curveTo",[40.165046,192.195795,61.994536,233.299759,105.653516,262.605585]],["curveTo",[149.312495,291.91141,219.773457,311.654533,317.0364,321.834954]],["curveTo",[315.883487,335.097897,312.595259,346.4197,307.171716,355.800362]],["curveTo",[301.748172,365.181023,293.630454,372.336159,282.818562,377.265769]],["curveTo",[272.006669,382.19538,258.275077,384.660185,241.623785,384.660185]],["curveTo",[222.0,384.660185,201.782161,381.177782,180.970269,374.212977]],["curveTo",[160.158377,367.248172,137.293692,356.85598,112.376215,343.0364]],["lineTo",[57.247569,444.376215]],["curveTo",[79.137539,457.654533,101.721682,469.248587,125.0,479.158377]],["curveTo",[148.278318,489.068167,172.1606,496.613272,196.646846,501.793692]],["curveTo",[221.133092,506.974113,246.376215,509.564323,272.376215,509.564323]],["curveTo",[314.987867,509.564323,351.252431,501.397656,381.169908,485.064323]],["curveTo",[411.087384,468.73099,433.97351,443.758497,449.828284,410.146846]],["curveTo",[465.683059,376.535195,473.610446,333.568769,473.610446,281.247569]],["lineTo",[473.610446,0]],["lineTo",[345.788831,0]],["lineTo",[334.871354,49.623785]],["lineTo",[330.871354,49.623785]],["curveTo",[310.149672,31.443364,288.306428,16.627628,265.341623,5.176577]],["curveTo",[242.376818,-6.274474,216.957118,-12.0,189.082523,-12]],["closePath",[]],["moveTo",[242.587384,109.71603]],["curveTo",[258.211169,109.71603,271.727146,113.153328,283.135315,120.027923]],["curveTo",[294.543485,126.902518,305.843846,136.241918,317.0364,148.046123]],["lineTo",[317.0364,225.788831]],["curveTo",[285.012133,221.42799,259.768408,215.643605,241.305223,208.435677]],["curveTo",[222.842038,201.227749,209.690746,192.794295,201.851346,183.135315]],["curveTo",[194.011946,173.476336,190.092246,162.987867,190.092246,151.669908]],["curveTo",[190.092246,137.810687,194.813928,127.353569,204.257292,120.298553]],["curveTo",[213.700656,113.243538,226.477354,109.71603,242.587384,109.71603]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[288.376215,-12]],["curveTo",[240.431231,-12.0,197.229369,-1.711772,158.770631,18.864685]],["curveTo",[120.311892,39.441141,89.92961,69.139159,67.623785,107.958738]],["curveTo",[45.317959,146.778318,34.165046,193.764564,34.165046,248.917477]],["curveTo",[34.165046,302.737056,45.529731,349.056636,68.2591,387.876215]],["curveTo",[90.988469,426.695795,120.415856,456.682041,156.541262,477.834954]],["curveTo",[192.666667,498.987867,230.792072,509.564323,270.917477,509.564323]],["curveTo",[318.987867,509.564323,358.873992,498.956515,390.575854,477.7409]],["curveTo",[422.277715,456.525285,445.871769,427.809669,461.358015,391.594054]],["curveTo",[476.844261,355.378438,484.587384,314.847087,484.587384,270]],["curveTo",[484.587384,256.415856,483.844261,243.286608,482.358015,230.612254]],["curveTo",[480.871769,217.9379,479.371769,208.149672,477.858015,201.247569]],["lineTo",[156.633507,201.247569]],["lineTo",[155.174769,306.752431]],["lineTo",[350.495139,306.752431]],["curveTo",[350.495139,330.415856,344.804808,350.226128,333.424146,366.183246]],["curveTo",[322.043485,382.140364,302.360841,390.118923,274.376215,390.118923]],["curveTo",[259.113272,390.118923,244.107205,385.969854,229.358015,377.671716]],["curveTo",[214.608825,369.373577,202.494951,354.926369,193.016392,334.330092]],["curveTo",[183.537833,313.733816,179.2848,285.262944,180.257292,248.917477]],["curveTo",[181.229784,211.709549,188.139574,182.883902,200.986661,162.440538]],["curveTo",[213.833748,141.997174,229.920115,127.765582,249.245761,119.745761]],["curveTo",[268.571407,111.725941,288.71279,107.71603,309.669908,107.71603]],["curveTo",[328.085764,107.71603,346.064323,110.351346,363.605585,115.621977]],["curveTo",[381.146846,120.892607,398.737056,128.618133,416.376215,138.798553]],["lineTo",[466.587384,44.376215]],["curveTo",[441.003241,26.431231,412.130869,12.548949,379.970269,2.729369]],["curveTo",[347.809669,-7.09021,317.278318,-12.0,288.376215,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[241.082589,-209.03125]],["curveTo",[202.793899,-209.03125,167.793899,-204.702195,136.082589,-196.044085]],["curveTo",[104.37128,-187.385975,79.196056,-173.948661,60.556919,-155.732143]],["curveTo",[41.917783,-137.515625,32.598214,-113.587797,32.598214,-83.948661]],["curveTo",[32.598214,-63.209821,38.652344,-44.736421,50.760602,-28.52846]],["curveTo",[62.868861,-12.320498,80.220238,2.0,102.814732,14.433036]],["lineTo",[102.814732,18.433036]],["curveTo",[90.220238,27.549851,79.47712,38.887463,70.585379,52.445871]],["curveTo",[61.693638,66.004278,57.247768,82.97247,57.247768,103.350446]],["curveTo",[57.247768,121.188988,62.522693,138.351376,73.072544,154.837612]],["curveTo",[83.622395,171.323847,96.869791,185.278274,112.814732,196.700893]],["lineTo",[112.814732,200.700893]],["curveTo",[95.175595,212.845238,79.576823,230.120164,66.018415,252.52567]],["curveTo",[52.460007,274.931176,45.680803,300.467262,45.680803,329.133929]],["curveTo",[45.680803,369.456845,55.540736,403.028088,75.260602,429.847656]],["curveTo",[94.980469,456.667225,120.740699,476.667225,152.541295,489.847656]],["curveTo",[184.34189,503.028088,218.250744,509.618304,254.267857,509.618304]],["curveTo",[268.195685,509.618304,281.830543,508.600261,295.172433,506.564174]],["curveTo",[308.514323,504.528088,320.896578,501.546131,332.319197,497.618304]],["lineTo",[515.504465,497.618304]],["lineTo",[515.504465,384.01116]],["lineTo",[435.174108,384.01116]],["lineTo",[435.174108,380.01116]],["curveTo",[441.067709,371.578124,445.371094,362.675967,448.084264,353.304687]],["curveTo",[450.797434,343.933407,452.154018,333.498512,452.154018,322]],["curveTo",[452.154018,284.055059,443.33445,252.992373,425.695313,228.811942]],["curveTo",[408.056176,204.63151,384.228051,186.676153,354.210938,174.945871]],["curveTo",[324.193825,163.215588,290.879465,157.350446,254.267857,157.350446]],["curveTo",[245.618304,157.350446,236.486793,158.012835,226.873326,159.337612]],["curveTo",[217.259859,160.662388,206.786459,162.874628,195.453125,165.97433]],["curveTo",[190.136905,160.874628,186.266556,156.058408,183.842076,151.52567]],["curveTo",[181.417597,146.992932,180.205358,140.518601,180.205358,132.102679]],["curveTo",[180.205358,120.642114,185.813617,112.127419,197.030135,106.558594]],["curveTo",[208.246652,100.98977,226.837798,98.205358,252.803572,98.205358]],["lineTo",[331.159598,98.205358]],["curveTo",[392.815848,98.205358,440.287389,88.20108,473.574219,68.192523]],["curveTo",[506.86105,48.183966,523.504465,15.64881,523.504465,-29.412946]],["curveTo",[523.504465,-64.402529,511.977865,-95.401599,488.924666,-122.410156]],["curveTo",[465.871466,-149.418713,433.237538,-170.607701,391.02288,-185.97712]],["curveTo",[348.808222,-201.34654,298.828125,-209.03125,241.082589,-209.03125]],["closePath",[]],["moveTo",[254.267857,251.339286]],["curveTo",[266.305804,251.339286,276.97433,254.218193,286.273437,259.976005]],["curveTo",[295.572544,265.733818,302.970424,274.374814,308.467075,285.898996]],["curveTo",[313.963727,297.423177,316.712053,311.834822,316.712053,329.133929]],["curveTo",[316.712053,354.63244,310.845982,373.639509,299.113839,386.155134]],["curveTo",[287.381696,398.670758,272.433036,404.928571,254.267857,404.928571]],["curveTo",[236.102679,404.928571,221.154018,398.670758,209.421876,386.155134]],["curveTo",[197.689733,373.639509,191.823662,354.63244,191.823662,329.133929]],["curveTo",[191.823662,311.834822,194.571987,297.423177,200.068639,285.898996]],["curveTo",[205.565291,274.374814,212.96317,265.733818,222.262277,259.976005]],["curveTo",[231.561384,254.218193,242.229911,251.339286,254.267857,251.339286]],["closePath",[]],["moveTo",[266.267857,-111.475445]],["curveTo",[287.02753,-111.475445,305.444382,-109.154947,321.518415,-104.51395]],["curveTo",[337.592447,-99.872953,350.32366,-93.348772,359.712053,-84.941406]],["curveTo",[369.100446,-76.53404,373.794642,-67.014137,373.794642,-56.381696]],["curveTo",[373.794642,-41.460565,367.528273,-31.864211,354.995535,-27.592634]],["curveTo",[342.462797,-23.321057,324.627604,-21.185268,301.489955,-21.185268]],["lineTo",[254.803572,-21.185268]],["curveTo",[236.68006,-21.185268,222.397508,-20.667225,211.955915,-19.631139]],["curveTo",[201.514323,-18.595052,192.302084,-17.040923,184.319197,-14.96875]],["curveTo",[176.370536,-22.051339,170.481213,-28.99907,166.651228,-35.811942]],["curveTo",[162.821243,-42.624814,160.906251,-50.275297,160.906251,-58.763392]],["curveTo",[160.906251,-76.495535,170.650298,-89.713913,190.138394,-98.418526]],["curveTo",[209.626489,-107.123139,235.002977,-111.475445,266.267857,-111.475445]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[211.566964,-12]],["curveTo",[178.917411,-12.0,145.120164,-5.554129,110.175223,7.337612]],["curveTo",[75.230283,20.229353,45.082589,38.755952,19.732143,62.917411]],["lineTo",[85.886161,150.689733]],["curveTo",[108.858631,132.834078,131.435082,120.48698,153.615514,113.648438]],["curveTo",[175.795945,106.809897,196.374256,103.390626,215.350446,103.390626]],["curveTo",[235.732143,103.390626,250.460007,106.738654,259.53404,113.434711]],["curveTo",[268.608072,120.130767,273.145089,129.380953,273.145089,141.185268]],["curveTo",[273.145089,151.223214,268.910528,159.675223,260.441406,166.541295]],["curveTo",[251.972284,173.407366,240.651785,179.574963,226.47991,185.044085]],["curveTo",[212.308035,190.513207,196.186012,196.581101,178.113839,203.247768]],["curveTo",[154.63616,211.914434,133.014137,222.918713,113.247768,236.260602]],["curveTo",[93.481399,249.602492,77.584449,265.795759,65.556919,284.840402]],["curveTo",[53.52939,303.885045,47.515625,326.182292,47.515625,351.732143]],["curveTo",[47.515625,399.333333,65.253534,437.547991,100.729353,466.376116]],["curveTo",[136.205171,495.204241,182.735119,509.618304,240.319197,509.618304]],["curveTo",[277.02009,509.618304,310.568081,503.632069,340.96317,491.659598]],["curveTo",[371.35826,479.687128,397.267114,464.322917,418.689733,445.566964]],["lineTo",[352.535715,357.794642]],["curveTo",[334.157738,370.911458,315.96875,380.262834,297.96875,385.848772]],["curveTo",[279.96875,391.434709,262.463542,394.227678,245.453125,394.227678]],["curveTo",[228.043899,394.227678,215.059153,391.082403,206.498885,384.791852]],["curveTo",[197.938617,378.501302,193.658483,369.931547,193.658483,359.082589]],["curveTo",[193.658483,350.450149,196.762464,343.088356,202.970425,336.99721]],["curveTo",[209.178386,330.906064,218.998885,325.18006,232.431921,319.819197]],["curveTo",[245.864956,314.458334,263.139882,308.408483,284.256697,301.669643]],["curveTo",[309.590031,293.453125,332.509301,282.691965,353.01451,269.386161]],["curveTo",[373.519718,256.080357,389.691593,239.702381,401.530135,220.252232]],["curveTo",[413.368676,200.802084,419.287947,177.491072,419.287947,150.319197]],["curveTo",[419.287947,119.851935,411.355841,92.388951,395.49163,67.930246]],["curveTo",[379.627419,43.47154,356.313058,24.035156,325.548549,9.621094]],["curveTo",[294.78404,-4.792969,256.790179,-12.0,211.566964,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[252.535715,-12]],["curveTo",[204.934524,-12.0,163.994792,-4.238839,129.716518,11.283482]],["curveTo",[95.438244,26.805804,69.204613,48.643415,51.015625,76.796317]],["curveTo",[32.826637,104.949219,23.732143,137.65625,23.732143,174.917411]],["curveTo",[23.732143,210.862351,31.047433,241.429316,45.678013,266.618304]],["curveTo",[60.308594,291.807292,79.236421,313.212798,102.461495,330.834822]],["curveTo",[125.68657,348.456845,150.263021,364.056548,176.190848,377.633929]],["curveTo",[202.118675,391.21131,226.695126,404.360863,249.920201,417.082589]],["curveTo",[273.145275,429.804315,292.073103,443.823288,306.703683,459.139509]],["curveTo",[321.334263,474.455729,328.649554,492.402529,328.649554,512.97991]],["curveTo",[328.649554,527.845982,325.21224,539.428571,318.337612,547.727678]],["curveTo",[311.462984,556.026785,300.862351,560.176338,286.535715,560.176338]],["curveTo",[271.920387,560.176338,259.067151,554.211495,247.976005,542.281807]],["curveTo",[236.884859,530.35212,231.339286,513.179315,231.339286,490.763392]],["curveTo",[231.339286,465.014137,239.267114,437.877418,255.122769,409.353236]],["curveTo",[270.978423,380.829055,292.243862,352.453497,318.919086,324.226563]],["curveTo",[345.594309,295.999628,375.251489,269.1782,407.890626,243.762277]],["curveTo",[440.529763,218.346355,473.335566,195.313617,506.308037,174.664063]],["curveTo",[534.019346,157.420016,561.937687,143.973215,590.063059,134.323662]],["curveTo",[618.188431,124.674108,640.503721,117.930061,657.00893,114.091519]],["lineTo",[620.040179,-12]],["curveTo",[586.889138,-5.783482,552.629837,4.84282,517.262277,19.878906]],["curveTo",[481.894718,34.914993,446.977307,53.541295,412.510045,75.757812]],["curveTo",[370.99814,102.640997,331.47247,133.190848,293.933036,167.407366]],["curveTo",[256.393601,201.623884,223.173735,237.543155,194.273437,275.165178]],["curveTo",[165.37314,312.787202,142.679873,349.963355,126.193638,386.693638]],["curveTo",[109.707403,423.423921,101.464285,458.041666,101.464285,490.546875]],["curveTo",[101.464285,521.519345,108.896391,550.068266,123.760602,576.193638]],["curveTo",[138.624814,602.31901,159.605841,623.331845,186.703683,639.232143]],["curveTo",[213.801525,655.13244,245.962054,663.082589,283.185268,663.082589]],["curveTo",[333.236608,663.082589,373.189175,649.555989,403.042969,622.50279]],["curveTo",[432.896764,595.449591,447.823662,559.014137,447.823662,513.196428]],["curveTo",[447.823662,484.079613,441.120909,458.354538,427.715403,436.021205]],["curveTo",[414.309897,413.687872,397.183595,393.597656,376.336496,375.750558]],["curveTo",[355.489398,357.903459,333.290924,341.430059,309.741072,326.330357]],["curveTo",[286.191221,311.230654,263.992747,296.540736,243.145648,282.260602]],["curveTo",[222.29855,267.980469,205.172248,252.943452,191.766742,237.149554]],["curveTo",[178.361236,221.355655,171.658483,203.65625,171.658483,184.051339]],["curveTo",[171.658483,166.463542,175.704056,151.984003,183.795202,140.612724]],["curveTo",[191.886348,129.241444,202.860678,120.789435,216.718193,115.256697]],["curveTo",[230.575707,109.723959,246.154018,106.95759,263.453125,106.95759]],["curveTo",[285.869048,106.95759,308.514323,112.894904,331.388951,124.769532]],["curveTo",[354.263579,136.64416,376.106399,152.239584,396.917411,171.555804]],["curveTo",[422.539435,196.421876,445.332403,225.472657,465.296317,258.708148]],["curveTo",[485.260231,291.943639,500.68378,329.219494,511.566964,370.535715]],["lineTo",[653.926341,370.535715]],["curveTo",[641.115329,322.463542,622.129093,275.963542,596.967635,231.035715]],["curveTo",[571.806177,186.107887,540.991817,143.040923,504.524555,101.834822]],["curveTo",[474.507441,67.889881,439.445685,40.431176,399.339286,19.458705]],["curveTo",[359.232888,-1.513765,310.298364,-12.0,252.535715,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[432.046123,-174.211169]],["curveTo",[381.740297,-174.211169,333.614892,-166.544502,287.669908,-151.211169]],["curveTo",[241.724923,-135.877836,200.779938,-112.589608,164.834954,-81.346484]],["curveTo",[128.889969,-50.103361,100.521441,-10.630869,79.729369,37.070992]],["curveTo",[58.937297,84.772854,48.541262,140.902103,48.541262,205.458738]],["curveTo",[48.541262,276.07039,60.527508,339.938918,84.5,397.064323]],["curveTo",[108.472492,454.189728,141.431231,503.360238,183.376215,544.575854]],["curveTo",[225.3212,585.791469,273.797536,617.430628,328.805223,639.493331]],["curveTo",[383.81291,661.556033,442.351948,672.587384,504.422338,672.587384]],["curveTo",[577.339815,672.587384,640.63573,657.236454,694.310084,626.534592]],["curveTo",[747.984438,595.832731,789.429423,553.432851,818.645038,499.334954]],["curveTo",[847.860653,445.237056,862.468461,382.972492,862.468461,312.541262]],["curveTo",[862.468461,266.235436,855.52732,225.794295,841.645038,191.217839]],["curveTo",[827.762756,156.641382,809.515789,127.853154,786.904138,104.853154]],["curveTo",[764.292487,81.853154,739.347502,64.564926,712.069184,52.988469]],["curveTo",[684.790866,41.412013,657.755672,35.623785,630.9636,35.623785]],["curveTo",[601.810687,35.623785,576.338195,42.290451,554.546123,55.623785]],["curveTo",[532.754051,68.957118,519.128646,87.984626,513.669908,112.706308]],["lineTo",[511.669908,112.706308]],["curveTo",[498.6424,91.984626,480.430628,75.533574,457.034592,63.353154]],["curveTo",[433.638556,51.172733,411.669908,45.082523,391.128646,45.082523]],["curveTo",[351.364082,45.082523,319.026905,59.023664,294.117115,86.905946]],["curveTo",[269.207326,114.788228,256.752431,151.819579,256.752431,198]],["curveTo",[256.752431,229.278318,261.995554,259.691951,272.4818,289.2409]],["curveTo",[282.968046,318.789849,297.575854,345.276095,316.305223,368.699638]],["curveTo",[335.034592,392.123182,356.73261,410.834954,381.399277,424.834954]],["curveTo",[406.065943,438.834954,432.82282,445.834954,461.669908,445.834954]],["curveTo",[478.030749,445.834954,492.589608,441.91141,505.346484,434.064323]],["curveTo",[518.103361,426.217236,528.57201,413.293692,536.752431,395.293692]],["lineTo",[538.752431,395.293692]],["lineTo",[553.940538,437.293692]],["lineTo",[649.550984,437.293692]],["lineTo",[603.174769,213.623785]],["curveTo",[596.994348,186.25081,597.539453,165.99171,604.810084,152.846484]],["curveTo",[612.080715,139.701259,626.076871,133.128646,646.798553,133.128646]],["curveTo",[666.618133,133.128646,685.239695,140.354172,702.663238,154.805223]],["curveTo",[720.086782,169.256274,734.222097,189.675974,745.069184,216.064323]],["curveTo",[755.916271,242.452672,761.339815,273.278318,761.339815,308.541262]],["curveTo",[761.339815,343.082523,756.416271,376.308049,746.569184,408.217839]],["curveTo",[736.722097,440.127628,721.208343,468.568769,700.027923,493.541262]],["curveTo",[678.847502,518.513754,651.149484,538.319579,616.933869,552.958738]],["curveTo",[582.718254,567.597897,540.998795,574.917477,491.775492,574.917477]],["curveTo",[449.775492,574.917477,408.820597,566.295915,368.910807,549.052792]],["curveTo",[329.001018,531.809669,293.091228,507.188108,261.181438,475.188108]],["curveTo",[229.271648,443.188108,203.861859,404.733213,184.952069,359.823423]],["curveTo",[166.042279,314.913633,156.587384,264.792072,156.587384,209.458738]],["curveTo",[156.587384,160.847087,164.406964,118.586367,180.046123,82.676577]],["curveTo",[195.685282,46.766787,216.946002,17.00991,243.828284,-6.594054]],["curveTo",[270.710566,-30.198018,301.439936,-47.756877,336.016392,-59.270631]],["curveTo",[370.592848,-70.784385,406.728164,-76.541262,444.422338,-76.541262]],["curveTo",[469.504861,-76.541262,495.577474,-73.086367,522.640177,-66.176577]],["curveTo",[549.702879,-59.266787,573.504861,-50.721682,594.046123,-40.541262]],["lineTo",[628.692969,-127.128646]],["curveTo",[598.026302,-143.128646,566.540056,-154.975733,534.234231,-162.669908]],["curveTo",[501.928405,-170.364082,467.865702,-174.211169,432.046123,-174.211169]],["closePath",[]],["moveTo",[425.610446,144.587384]],["curveTo",[435.665461,144.587384,445.612669,147.81291,455.452069,154.263961]],["curveTo",[465.291469,160.715013,476.085764,171.724923,487.834954,187.293692]],["lineTo",[510.917477,318.706308]],["curveTo",[506.682041,327.122164,500.748587,333.586969,493.117115,338.100723]],["curveTo",[485.485643,342.614477,476.73261,344.871354,466.858015,344.871354]],["curveTo",[448.261738,344.871354,432.179215,337.434057,418.610446,322.559462]],["curveTo",[405.041677,307.684867,394.653328,289.553395,387.4454,268.165046]],["curveTo",[380.237471,246.776697,376.633507,226.451051,376.633507,207.188108]],["curveTo",[376.633507,186.536815,380.949243,170.940538,389.580715,160.399277]],["curveTo",[398.212187,149.858015,410.222097,144.587384,425.610446,144.587384]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[264.96875,-12]],["curveTo",[222.735119,-12.0,184.686198,-4.63058,150.821987,10.108259]],["curveTo",[116.957775,24.847098,90.318638,45.252604,70.904576,71.324777]],["curveTo",[51.490513,97.396949,41.783482,127.694196,41.783482,162.216518]],["curveTo",[41.783482,187.388393,46.518043,209.429687,55.987165,228.340402]],["curveTo",[65.456287,247.251116,78.258742,263.904948,94.394531,278.301897]],["curveTo",[110.53032,292.698846,128.598214,305.230654,148.598214,315.897321]],["lineTo",[148.598214,319.897321]],["curveTo",[124.148065,338.797619,103.891183,360.247768,87.827567,384.247768]],["curveTo",[71.763951,408.247768,63.732143,436.653274,63.732143,469.464285]],["curveTo",[63.732143,506.00372,72.551711,537.574963,90.190848,564.178013]],["curveTo",[107.829985,590.781064,132.234561,611.177083,163.404576,625.366071]],["curveTo",[194.574591,639.555059,230.212798,646.649554,270.319197,646.649554]],["curveTo",[329.075149,646.649554,376.258929,630.81622,411.870536,599.149554]],["curveTo",[447.482143,567.482887,465.287947,525.010417,465.287947,471.732143]],["curveTo",[465.287947,451.893601,461.458892,433.388393,453.800782,416.216518]],["curveTo",[446.142672,399.044643,436.187315,383.742188,423.934711,370.309152]],["curveTo",[411.682106,356.876116,398.069569,345.159598,383.097099,335.159598]],["lineTo",[383.097099,331.159598]],["curveTo",[403.475075,320.420759,421.884859,307.456845,438.326452,292.267857]],["curveTo",[454.768044,277.078869,467.894346,259.038505,477.705358,238.146764]],["curveTo",[487.51637,217.255022,492.421876,192.773066,492.421876,164.700893]],["curveTo",[492.421876,131.061756,482.953684,100.99907,464.0173,74.512835]],["curveTo",[445.080916,48.0266,418.604167,26.986235,384.587054,11.391741]],["curveTo",[350.569941,-4.202753,310.697173,-12.0,264.96875,-12]],["closePath",[]],["moveTo",[304.051339,372.803572]],["curveTo",[315.549851,387.219494,324.209821,401.631139,330.03125,416.038505]],["curveTo",[335.852678,430.445871,338.763392,445.443452,338.763392,461.03125]],["curveTo",[338.763392,476.780506,335.956659,490.399181,330.343192,501.887276]],["curveTo",[324.729724,513.375371,316.494233,522.331658,305.636719,528.756138]],["curveTo",[294.779204,535.180617,281.817708,538.392856,266.752232,538.392856]],["curveTo",[248.96503,538.392856,233.552456,532.783667,220.51451,521.565289]],["curveTo",[207.476563,510.346912,200.95759,492.97991,200.95759,469.464285]],["curveTo",[200.95759,453.515625,205.183595,439.963914,213.635604,428.809152]],["curveTo",[222.087612,417.65439,234.035343,407.621652,249.478795,398.710938]],["curveTo",[264.922247,389.800224,283.113095,381.164435,304.051339,372.803572]],["closePath",[]],["moveTo",[268.535715,96.256697]],["curveTo",[283.295387,96.256697,296.563616,98.8564,308.340402,104.055804]],["curveTo",[320.117187,109.255209,329.53311,117.301712,336.588169,128.195313]],["curveTo",[343.643229,139.088914,347.170758,153.040923,347.170758,170.051339]],["curveTo",[347.170758,187.477679,341.944754,201.970982,331.492745,213.53125]],["curveTo",[321.040736,225.091517,306.241629,235.511718,287.095424,244.791852]],["curveTo",[267.949219,254.071986,245.240328,264.189732,218.96875,275.145089]],["curveTo",[206.892858,263.468005,196.768044,249.277157,188.594309,232.572544]],["curveTo",[180.420574,215.867931,176.333706,198.360863,176.333706,180.051339]],["curveTo",[176.333706,162.229911,180.527903,147.057106,188.916296,134.532925]],["curveTo",[197.304688,122.008743,208.517858,112.498327,222.555804,106.001675]],["curveTo",[236.593751,99.505023,251.920387,96.256697,268.535715,96.256697]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[188.917411,249.082589]],["curveTo",[157.278274,249.082589,129.044643,257.379836,104.216518,273.97433]],["curveTo",[79.388393,290.568824,59.839472,314.366071,45.569754,345.366071]],["curveTo",[31.300037,376.366071,24.165178,413.604911,24.165178,457.082589]],["curveTo",[24.165178,522.488095,39.768229,573.172805,70.97433,609.136719]],["curveTo",[102.180432,645.100632,141.494792,663.082589,188.917411,663.082589]],["curveTo",[236.412203,663.082589,275.744606,645.100632,306.914621,609.136719]],["curveTo",[338.084636,573.172805,353.669643,522.488095,353.669643,457.082589]],["curveTo",[353.669643,413.604911,346.534785,376.366071,332.265067,345.366071]],["curveTo",[317.99535,314.366071,298.446429,290.568824,273.618304,273.97433]],["curveTo",[248.790179,257.379836,220.556548,249.082589,188.917411,249.082589]],["closePath",[]],["moveTo",[188.917411,335.720983]],["curveTo",[204.415923,335.720983,217.670387,344.605097,228.680803,362.373326]],["curveTo",[239.69122,380.141555,245.196428,411.71131,245.196428,457.082589]],["curveTo",[245.196428,502.526041,239.69122,533.780506,228.680803,550.845982]],["curveTo",[217.670387,567.911458,204.415923,576.444196,188.917411,576.444196]],["curveTo",[174.085566,576.444196,160.997768,567.911458,149.654018,550.845982]],["curveTo",[138.310268,533.780506,132.638394,502.526041,132.638394,457.082589]],["curveTo",[132.638394,411.71131,138.310268,380.141555,149.654018,362.373326]],["curveTo",[160.997768,344.605097,174.085566,335.720983,188.917411,335.720983]],["closePath",[]],["moveTo",[212.051339,-12.0]],["lineTo",[562.433036,663.082589]],["lineTo",[651.504465,663.082589]],["lineTo",[301.122769,-12.0]],["closePath",[]],["moveTo",[673.287947,-12.0]],["curveTo",[641.64881,-12.0,613.415179,-3.702753,588.587054,12.891741]],["curveTo",[563.758929,29.486235,544.210008,53.283482,529.940291,84.283482]],["curveTo",[515.670573,115.283482,508.535715,152.522321,508.535715,196.0]],["curveTo",[508.535715,261.405506,524.138765,312.090216,555.344866,348.054129]],["curveTo",[586.550968,384.018043,625.865328,402.0,673.287947,402.0]],["curveTo",[720.782739,402.0,760.115142,384.018043,791.285157,348.054129]],["curveTo",[822.455172,312.090216,838.040179,261.405506,838.040179,196.0]],["curveTo",[838.040179,152.522321,830.905321,115.283482,816.635604,84.283482]],["curveTo",[802.365886,53.283482,782.816965,29.486235,757.98884,12.891741]],["curveTo",[733.160715,-3.702753,704.927084,-12.0,673.287947,-12.0]],["closePath",[]],["moveTo",[673.287947,74.638394]],["curveTo",[688.786459,74.638394,702.040923,83.522508,713.051339,101.290737]],["curveTo",[724.061756,119.058966,729.566964,150.62872,729.566964,196.0]],["curveTo",[729.566964,241.443452,724.061756,272.697916,713.051339,289.763392]],["curveTo",[702.040923,306.828868,688.786459,315.361606,673.287947,315.361606]],["curveTo",[658.456102,315.361606,645.368304,306.828868,634.024555,289.763392]],["curveTo",[622.680805,272.697916,617.00893,241.443452,617.00893,196.0]],["curveTo",[617.00893,150.62872,622.680805,119.058966,634.024555,101.290737]],["curveTo",[645.368304,83.522508,658.456102,74.638394,673.287947,74.638394]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]],["moveTo",[174.700893,697.680803]],["curveTo",[153.549851,697.680803,136.092076,704.675595,122.327567,718.665178]],["curveTo",[108.563058,732.654762,101.680803,750.0,101.680803,770.700893]],["curveTo",[101.680803,791.401786,108.563058,808.747024,122.327567,822.736608]],["curveTo",[136.092076,836.726191,153.549851,843.720983,174.700893,843.720983]],["curveTo",[196.518601,843.720983,214.143044,836.726191,227.574219,822.736608]],["curveTo",[241.005395,808.747024,247.720983,791.401786,247.720983,770.700893]],["curveTo",[247.720983,750.0,241.005395,732.654762,227.574219,718.665178]],["curveTo",[214.143044,704.675595,196.518601,697.680803,174.700893,697.680803]],["closePath",[]],["moveTo",[398.803572,697.680803]],["curveTo",[377.65253,697.680803,360.194755,704.675595,346.430246,718.665178]],["curveTo",[332.665737,732.654762,325.783482,750.0,325.783482,770.700893]],["curveTo",[325.783482,791.401786,332.665737,808.747024,346.430246,822.736608]],["curveTo",[360.194755,836.726191,377.65253,843.720983,398.803572,843.720983]],["curveTo",[420.62128,843.720983,438.245722,836.726191,451.676898,822.736608]],["curveTo",[465.108074,808.747024,471.823662,791.401786,471.823662,770.700893]],["curveTo",[471.823662,750.0,465.108074,732.654762,451.676898,718.665178]],["curveTo",[438.245722,704.675595,420.62128,697.680803,398.803572,697.680803]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[288.376215,-12]],["curveTo",[240.431231,-12.0,197.229369,-1.711772,158.770631,18.864685]],["curveTo",[120.311892,39.441141,89.92961,69.139159,67.623785,107.958738]],["curveTo",[45.317959,146.778318,34.165046,193.764564,34.165046,248.917477]],["curveTo",[34.165046,302.737056,45.529731,349.056636,68.2591,387.876215]],["curveTo",[90.988469,426.695795,120.415856,456.682041,156.541262,477.834954]],["curveTo",[192.666667,498.987867,230.792072,509.564323,270.917477,509.564323]],["curveTo",[318.987867,509.564323,358.873992,498.956515,390.575854,477.7409]],["curveTo",[422.277715,456.525285,445.871769,427.809669,461.358015,391.594054]],["curveTo",[476.844261,355.378438,484.587384,314.847087,484.587384,270]],["curveTo",[484.587384,256.415856,483.844261,243.286608,482.358015,230.612254]],["curveTo",[480.871769,217.9379,479.371769,208.149672,477.858015,201.247569]],["lineTo",[156.633507,201.247569]],["lineTo",[155.174769,306.752431]],["lineTo",[350.495139,306.752431]],["curveTo",[350.495139,330.415856,344.804808,350.226128,333.424146,366.183246]],["curveTo",[322.043485,382.140364,302.360841,390.118923,274.376215,390.118923]],["curveTo",[259.113272,390.118923,244.107205,385.969854,229.358015,377.671716]],["curveTo",[214.608825,369.373577,202.494951,354.926369,193.016392,334.330092]],["curveTo",[183.537833,313.733816,179.2848,285.262944,180.257292,248.917477]],["curveTo",[181.229784,211.709549,188.139574,182.883902,200.986661,162.440538]],["curveTo",[213.833748,141.997174,229.920115,127.765582,249.245761,119.745761]],["curveTo",[268.571407,111.725941,288.71279,107.71603,309.669908,107.71603]],["curveTo",[328.085764,107.71603,346.064323,110.351346,363.605585,115.621977]],["curveTo",[381.146846,120.892607,398.737056,128.618133,416.376215,138.798553]],["lineTo",[466.587384,44.376215]],["curveTo",[441.003241,26.431231,412.130869,12.548949,379.970269,2.729369]],["curveTo",[347.809669,-7.09021,317.278318,-12.0,288.376215,-12]],["closePath",[]],["moveTo",[181.082589,570.0]],["lineTo",[294.330357,755.669643]],["lineTo",[458.95759,755.669643]],["lineTo",[468.091519,739.185268]],["lineTo",[300.256697,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]],["moveTo",[286.454985,696.433036]],["curveTo",[243.935144,696.433036,210.851376,705.567894,187.203683,723.837612]],["curveTo",[163.555989,742.107329,151.732143,766.47154,151.732143,796.930246]],["curveTo",[151.732143,827.388951,163.555989,851.604539,187.203683,869.577009]],["curveTo",[210.851376,887.549479,243.935144,896.535715,286.454985,896.535715]],["curveTo",[330.037823,896.535715,363.486422,887.602559,386.800782,869.736249]],["curveTo",[410.115142,851.869938,421.772322,827.452647,421.772322,796.484375]],["curveTo",[421.772322,766.322917,410.115142,742.107329,386.800782,723.837612]],["curveTo",[363.486422,705.567894,330.037823,696.433036,286.454985,696.433036]],["closePath",[]],["moveTo",[286.47063,752.319197]],["curveTo",[299.452264,752.319197,309.619234,756.445499,316.97154,764.698103]],["curveTo",[324.323847,772.950707,328.0,783.461203,328.0,796.229592]],["curveTo",[328.0,809.409864,324.323847,820.108259,316.97154,828.324777]],["curveTo",[309.619234,836.541295,299.452264,840.649554,286.47063,840.649554]],["curveTo",[273.936639,840.649554,263.975447,836.541295,256.587054,828.324777]],["curveTo",[249.198661,820.108259,245.504465,809.409864,245.504465,796.229592]],["curveTo",[245.504465,783.461203,249.198661,772.950707,256.587054,764.698103]],["curveTo",[263.975447,756.445499,273.936639,752.319197,286.47063,752.319197]],["closePath",[]]]}]}],"chars":" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÄÖÜäöüéÅ","glyphs":[1,79,82,96,98,100,64,81,85,86,94,101,76,83,75,91,65,66,67,68,69,70,71,72,73,74,77,78,104,103,105,80,95,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,87,93,88,106,84,108,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,89,92,90,107,54,56,57,58,62,63,59,55]},{"file":"VarTestSans-NoHVAR.ttf","family":"VarTestSansNoHVAR","instances":["ExtraLight","Light","Regular","Medium","Semibold","Bold","Black"],"advances":[{"coordinates":[200.0],"widths":[200,248,336,472,472,798,562,208,270,270,386,472,208,294,208,360,472,472,472,472,472,472,472,472,472,472,208,208,472,472,472,394,802,520,574,562,600,510,470,600,634,232,456,550,460,698,632,648,542,648,534,516,520,630,482,764,470,436,538,270,360,270,472,500,532,486,538,446,540,478,252,480,522,222,222,452,230,806,526,532,540,540,306,400,302,524,422,672,392,424,396,270,220,270,472,520,648,630,486,532,524,478,520],"strikeout":[286,50]},{"coordinates":[300.0],"widths":[200,259,360,479,479,805,575,219,279,279,395,479,219,299,219,357,479,479,479,479,479,479,479,479,479,479,219,219,479,479,479,402,814,526,578,564,604,515,477,605,639,240,462,558,467,706,636,652,549,652,544,521,524,634,491,770,482,447,538,279,357,279,479,500,535,491,542,449,544,483,263,487,528,229,229,464,237,812,532,535,544,544,317,405,312,529,434,685,407,436,404,279,226,279,479,526,652,634,491,535,529,483,526],"strikeout":[287,50]},{"coordinates":[400.0],"widths":[200,289,426,497,497,824,609,249,303,303,418,497,249,311,249,350,497,497,497,497,497,497,497,497,497,497,249,249,497,497,497,425,847,543,588,571,615,527,494,617,652,263,479,579,486,727,647,664,566,664,569,534,536,645,515,786,513,476,539,303,350,303,497,500,542,504,553,456,555,496,292,504,544,246,247,495,255,829,547,542,555,555,347,419,338,544,467,719,446,467,425,303,241,303,497,544,664,645,504,542,544,496,544],"strikeout":[291,50]},{"coordinates":[500.0],"widths":[200,302,455,505,505,833,624,262,314,314,428,505,262,316,262,347,505,505,505,505,505,505,505,505,505,505,262,262,505,505,505,435,861,551,592,573,620,532,502,622,657,273,487,588,494,736,652,669,574,669,581,539,541,650,526,793,527,489,540,314,347,314,505,500,546,510,558,459,559,501,304,512,551,254,255,508,263,836,553,546,559,559,360,425,350,550,481,734,464,481,434,314,248,314,505,551,669,650,510,546,550,501,551],"strikeout":[292,50]},{"coordinates":[600.0],"widths":[200,315,482,513,513,841,639,275,324,324,438,513,275,322,275,344,513,513,513,513,513,513,513,513,513,513,275,275,513,513,513,444,875,558,597,576,625,538,510,628,663,282,494,597,502,745,657,674,582,674,592,545,546,655,536,800,541,501,540,324,344,324,513,500,549,516,563,462,564,507,317,520,558,262,263,522,271,843,560,549,564,564,373,431,361,556,495,748,481,495,443,324,255,324,513,558,674,655,516,549,556,507,558],"strikeout":[294,50]},{"coordinates":[700.0],"widths":[200,340,537,528,528,857,667,300,344,344,457,528,300,332,300,339,528,528,528,528,528,528,528,528,528,528,300,300,528,528,528,463,902,573,605,582,635,548,524,638,674,301,509,614,518,762,665,684,596,684,613,556,556,665,556,813,567,525,541,344,339,344,528,500,555,527,573,468,573,518,341,534,571,276,278,548,286,857,572,555,573,573,398,443,383,568,523,776,514,521,460,344,268,344,528,573,684,665,527,555,568,518,573],"strikeout":[297,50]},{"coordinates":[900.0],"widths":[200,360,580,540,540,870,690,320,360,360,472,540,320,340,320,334,540,540,540,540,540,540,540,540,540,540,320,320,540,540,540,478,924,584,612,586,642,556,536,646,682,316,520,628,530,776,672,692,608,692,630,564,564,672,572,824,588,544,542,360,334,360,540,500,560,536,580,472,580,526,360,546,582,288,290,568,298,868,582,560,580,580,418,452,400,578,544,798,540,542,474,360,278,360,540,584,692,672,536,560,578,526,584],"strikeout":[299,50]}],"locations":[{"coordinates":[300.0],"glyphs":[{"char":"A","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[97.201196,0]],["lineTo",[97.201196,659.000427]],["lineTo",[457.798377,659.000427]],["lineTo",[457.798377,619.405382]],["lineTo",[143.195216,619.405382]],["lineTo",[143.195216,369.798377]],["lineTo",[407.397693,369.798377]],["lineTo",[407.397693,330.203332]],["lineTo",[143.195216,330.203332]],["lineTo",[143.195216,39.595045]],["lineTo",[467.798377,39.595045]],["lineTo",[467.798377,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[326.165829,-12]],["curveTo",[273.388352,-12.0,226.583043,2.111133,185.749903,30.333398]],["curveTo",[144.916763,58.555663,112.889125,98.444595,89.666989,150.000193]],["curveTo",[66.444852,201.555792,54.833784,262.111326,54.833784,331.666795]],["curveTo",[54.833784,401.222265,66.444852,461.416699,89.666989,512.250097]],["curveTo",[112.889125,563.083494,144.916763,602.250225,185.749903,629.75029]],["curveTo",[226.583043,657.250354,273.388352,671.000386,326.165829,671.000386]],["curveTo",[379.054375,671.000386,425.915217,657.250354,466.748357,629.75029]],["curveTo",[507.581497,602.250225,539.609136,563.083494,562.831272,512.250097]],["curveTo",[586.053408,461.416699,597.664477,401.222265,597.664477,331.666795]],["curveTo",[597.664477,262.111326,586.053408,201.555792,562.831272,150.000193]],["curveTo",[539.609136,98.444595,507.581497,58.555663,466.748357,30.333398]],["curveTo",[425.915217,2.111133,379.054375,-12.0,326.165829,-12]],["closePath",[]],["moveTo",[326.165829,29.82876]],["curveTo",[370.833269,29.82876,409.833977,42.301186,443.167955,67.246038]],["curveTo",[476.501932,92.190891,502.530215,127.274772,521.252802,172.497681]],["curveTo",[539.975389,217.72059,549.336683,270.776962,549.336683,331.666795]],["curveTo",[549.336683,392.556629,539.975389,445.224133,521.252802,489.669308]],["curveTo",[502.530215,534.114482,476.501932,568.476162,443.167955,592.754348]],["curveTo",[409.833977,617.032534,370.833269,629.171627,326.165829,629.171627]],["curveTo",[281.609458,629.171627,242.636516,617.032534,209.247005,592.754348]],["curveTo",[175.857493,568.476162,149.801444,534.114482,131.078856,489.669308]],["curveTo",[112.356269,445.224133,102.994976,392.556629,102.994976,331.666795]],["curveTo",[102.994976,270.776962,112.356269,217.72059,131.078856,172.497681]],["curveTo",[149.801444,127.274772,175.857493,92.190891,209.247005,67.246038]],["curveTo",[242.636516,42.301186,281.609458,29.82876,326.165829,29.82876]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.999573,-12]],["curveTo",[220.93265,-12.0,178.399331,-2.699915,141.399616,15.900256]],["curveTo",[104.3999,34.500427,72.73359,58.333761,46.400683,87.400256]],["lineTo",[75.197779,116.596326]],["curveTo",[98.531112,90.129289,127.081233,69.045636,160.848142,53.345365]],["curveTo",[194.615051,37.645095,230.798804,29.794959,269.399402,29.794959]],["curveTo",[320.00057,29.794959,359.984907,42.045351,389.352413,66.546134]],["curveTo",[418.71992,91.046917,433.403674,122.931226,433.403674,162.19906]],["curveTo",[433.403674,189.666524,427.953581,211.667165,417.053396,228.200982]],["curveTo",[406.153211,244.7348,391.803004,258.385163,374.002777,269.152072]],["curveTo",[356.202549,279.91898,336.46903,290.169158,314.802221,299.902606]],["lineTo",[214.901324,344.603161]],["curveTo",[195.168162,353.403104,175.001566,364.469671,154.401538,377.802862]],["curveTo",[133.801509,391.136053,116.451445,408.285918,102.351346,429.252456]],["curveTo",[88.251246,450.218995,81.201196,476.668945,81.201196,508.602307]],["curveTo",[81.201196,540.535384,89.43443,568.668447,105.900897,593.001495]],["curveTo",[122.367364,617.334544,145.017087,636.417628,173.850064,650.250748]],["curveTo",[202.683041,664.083867,235.665955,671.000427,272.798804,671.000427]],["curveTo",[312.732166,671.000427,348.38203,663.267051,379.748398,647.800299]],["curveTo",[411.114766,632.333547,436.664388,613.933647,456.397266,592.600598]],["lineTo",[431.200342,563.404528]],["curveTo",[411.000427,582.938061,387.833689,598.788267,361.700128,610.955147]],["curveTo",[335.566567,623.122028,305.932792,629.205468,272.798804,629.205468]],["curveTo",[229.331055,629.205468,194.230172,618.488466,167.496155,597.054464]],["curveTo",[140.762139,575.620461,127.39513,547.069628,127.39513,511.401965]],["curveTo",[127.39513,485.801225,133.47857,465.000641,145.645451,449.000214]],["curveTo",[157.812331,432.999786,172.879183,420.06614,190.846006,410.199274]],["curveTo",[208.812829,400.332408,225.662822,391.698989,241.395985,384.299018]],["lineTo",[341.096967,339.798377]],["curveTo",[366.630215,328.531682,389.930229,315.781717,410.99701,301.548484]],["curveTo",[432.06379,287.31525,448.797238,269.465328,461.197352,247.998718]],["curveTo",[473.597465,226.532109,479.797522,198.998718,479.797522,165.398548]],["curveTo",[479.797522,131.732023,471.314253,101.465542,454.347715,74.599103]],["curveTo",[437.381176,47.732664,413.048056,26.582871,381.348355,11.149722]],["curveTo",[349.648654,-4.283426,312.19906,-12.0,268.999573,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.000386,-12]],["curveTo",[171.667311,-12.0,148.417472,-7.250097,127.25087,2.24971]],["curveTo",[106.084267,11.749517,89.1954,26.221557,76.584267,45.665829]],["curveTo",[63.973135,65.110102,57.667568,89.72117,57.667568,119.499034]],["curveTo",[57.667568,173.165572,82.63993,213.887708,132.584654,241.665443]],["curveTo",[182.529377,269.443178,260.890864,289.220977,367.669114,300.998841]],["curveTo",[368.113645,325.443886,365.502577,349.388996,359.83591,372.834171]],["curveTo",[354.169243,396.279345,343.030215,415.50219,326.418825,430.502705]],["curveTo",[309.807434,445.503221,285.779152,453.003478,254.333977,453.003478]],["curveTo",[222.0,453.003478,192.527316,446.86445,165.915926,434.586393]],["curveTo",[139.304536,422.308336,117.776833,410.336039,101.332818,398.669501]],["lineTo",[81.001159,429.999227]],["curveTo",[92.333977,438.554568,107.000258,447.665572,125.0,457.332238]],["curveTo",[142.999742,466.998905,163.444015,475.22117,186.332818,481.999034]],["curveTo",[209.221621,488.776897,233.666023,492.165829,259.666023,492.165829]],["curveTo",[297.554697,492.165829,327.63774,483.860263,349.915153,467.24913]],["curveTo",[372.192566,450.637998,388.081111,428.304793,397.580789,400.249517]],["curveTo",[407.080467,372.19424,411.830306,341.000386,411.830306,306.667955]],["lineTo",[411.830306,0]],["lineTo",[374.33475,0]],["lineTo",[369.668341,62.333977]],["lineTo",[367.335137,62.333977]],["curveTo",[343.001675,42.556114,316.418116,25.222652,287.584461,10.333591]],["curveTo",[258.750805,-4.55547,228.556114,-12.0,197.000386,-12]],["closePath",[]],["moveTo",[204.664863,26.995749]],["curveTo",[232.443242,26.995749,259.13832,33.523709,284.750097,46.579629]],["curveTo",[310.361873,59.63555,338.001546,78.663832,367.669114,103.664477]],["lineTo",[367.669114,266.002319]],["curveTo",[301.889705,258.446592,249.749517,248.2519,211.248551,235.418245]],["curveTo",[172.747584,222.584589,145.080209,206.806404,128.246425,188.083688]],["curveTo",[111.412641,169.360972,102.995749,147.221492,102.995749,121.66525]],["curveTo",[102.995749,87.664219,113.134906,63.385711,133.41322,48.829726]],["curveTo",[153.691535,34.273741,177.442083,26.995749,204.664863,26.995749]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[273.999227,-12]],["curveTo",[232.999614,-12.0,195.666602,-1.972233,162.000193,18.083301]],["curveTo",[128.333784,38.138835,101.528411,66.91657,81.584074,104.416506]],["curveTo",[61.639737,141.916441,51.667568,186.888545,51.667568,239.332818]],["curveTo",[51.667568,291.554954,61.695271,336.55489,81.750676,374.332625]],["curveTo",[101.806082,412.11036,127.778121,441.193661,159.666795,461.582528]],["curveTo",[191.55547,481.971396,225.444144,492.165829,261.332818,492.165829]],["curveTo",[299.332303,492.165829,332.081948,483.415926,359.581755,465.916119]],["curveTo",[387.081562,448.416312,408.192566,423.138706,422.914766,390.083301]],["curveTo",[437.636967,357.027896,444.998068,317.000129,444.998068,270]],["curveTo",[444.998068,264.33372,444.914766,258.445174,444.748164,252.334364]],["curveTo",[444.581562,246.223553,443.77606,239.890478,442.331659,233.335137]],["lineTo",[79.329726,233.335137]],["lineTo",[79.329726,271.331272]],["lineTo",[403.002705,271.331272]],["curveTo",[403.002705,332.222136,390.280118,377.834622,364.834944,408.168728]],["curveTo",[339.389769,438.502834,305.000129,453.669887,261.666023,453.669887]],["curveTo",[235.220977,453.669887,209.414831,446.030795,184.247584,430.752609]],["curveTo",[159.080338,415.474423,138.302088,392.08504,121.912834,360.584461]],["curveTo",[105.52358,329.083881,97.328953,289.222265,97.328953,240.999614]],["curveTo",[97.328953,196.776962,105.245716,158.60978,121.079243,126.498068]],["curveTo",[136.91277,94.386355,158.607525,69.719173,186.16351,52.496522]],["curveTo",[213.719495,35.27387,244.331143,26.662544,277.998454,26.662544]],["curveTo",[304.332174,26.662544,328.24913,30.218143,349.749324,37.32934]],["curveTo",[371.249517,44.440537,390.332947,53.885067,406.999614,65.662931]],["lineTo",[425.331272,34.999614]],["curveTo",[406.553795,22.333205,385.443049,11.333333,361.999034,2.0]],["curveTo",[338.555019,-7.333333,309.22175,-12.0,273.999227,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.000427,-231.201196]],["curveTo",[208.600313,-231.201196,173.600313,-225.684465,144.000427,-214.651004]],["curveTo",[114.400541,-203.617542,91.600669,-188.200769,75.600812,-168.400683]],["curveTo",[59.600954,-148.600598,51.601025,-125.200627,51.601025,-98.200769]],["curveTo",[51.601025,-76.934074,58.051047,-56.217286,70.951089,-36.050406]],["curveTo",[83.851132,-15.883526,101.334472,2.0,123.401111,17.600171]],["lineTo",[123.401111,21.600171]],["curveTo",[111.334472,29.133419,101.251175,39.283355,93.151217,52.049979]],["curveTo",[85.05126,64.816603,81.001282,80.333191,81.001282,98.599744]],["curveTo",[81.001282,120.133276,87.067991,138.48334,99.20141,153.649936]],["curveTo",[111.334828,168.816531,122.734729,179.999715,133.401111,187.199487]],["lineTo",[133.401111,191.199487]],["curveTo",[118.401253,204.399544,104.518012,222.466254,91.751388,245.399616]],["curveTo",[78.984764,268.332977,72.601452,294.132992,72.601452,322.799658]],["curveTo",[72.601452,355.732593,80.217998,384.949025,95.451089,410.448953]],["curveTo",[110.684181,435.948882,131.033889,455.948882,156.500214,470.448953]],["curveTo",[181.966539,484.949025,210.332906,492.19906,241.599317,492.19906]],["curveTo",[254.999288,492.19906,267.182543,491.049053,278.149082,488.749039]],["curveTo",[289.11562,486.449025,298.198775,483.599032,305.398548,480.19906]],["lineTo",[467.997437,480.19906]],["lineTo",[467.997437,442.603161]],["lineTo",[355.995728,442.603161]],["lineTo",[355.995728,438.603161]],["curveTo",[370.862879,427.00299,383.479994,411.502634,393.847074,392.102093]],["curveTo",[404.214153,372.701552,409.397693,349.334188,409.397693,322]],["curveTo",[409.397693,289.333618,401.897764,260.250392,386.897907,234.75032]],["curveTo",[371.898049,209.250249,351.764915,189.183469,326.498505,174.549979]],["curveTo",[301.232095,159.916489,272.932365,152.599744,241.599317,152.599744]],["curveTo",[228.19906,152.599744,214.448811,154.449808,200.348569,158.149936]],["curveTo",[186.248327,161.850064,173.531539,166.833547,162.198206,173.100384]],["curveTo",[152.131283,164.833547,143.114339,155.266624,135.147373,144.399616]],["curveTo",[127.180407,133.532607,123.196924,119.932223,123.196924,103.598462]],["curveTo",[123.196924,86.331482,129.596967,71.614481,142.397053,59.447458]],["curveTo",[155.197138,47.280436,179.33077,41.196924,214.79795,41.196924]],["lineTo",[317.699274,41.196924]],["curveTo",[371.965542,41.196924,411.915136,32.380322,437.548056,14.747117]],["curveTo",[463.180977,-2.886089,475.997437,-30.802506,475.997437,-69.002136]],["curveTo",[475.997437,-96.601737,466.714225,-122.71814,448.1478,-147.351346]],["curveTo",[429.581375,-171.984551,403.281717,-192.117827,369.248825,-207.751175]],["curveTo",[335.215933,-223.384522,295.133134,-231.201196,249.000427,-231.201196]],["closePath",[]],["moveTo",[241.599317,187.996583]],["curveTo",[263.666524,187.996583,284.100384,193.646732,302.900897,204.947031]],["curveTo",[321.70141,216.24733,336.885163,232.014381,348.452157,252.248185]],["curveTo",[360.019151,272.481988,365.802648,295.999146,365.802648,322.799658]],["curveTo",[365.802648,364.133846,353.602307,396.601239,329.201623,420.201837]],["curveTo",[304.80094,443.802435,275.600171,455.602734,241.599317,455.602734]],["curveTo",[207.598462,455.602734,178.397693,443.802435,153.99701,420.201837]],["curveTo",[129.596326,396.601239,117.395985,364.133846,117.395985,322.799658]],["curveTo",[117.395985,295.999146,123.179482,272.481988,134.746476,252.248185]],["curveTo",[146.31347,232.014381,161.497223,216.24733,180.297736,204.947031]],["curveTo",[199.098249,193.646732,219.532109,187.996583,241.599317,187.996583]],["closePath",[]],["moveTo",[253.599317,-195.404528]],["curveTo",[289.666809,-195.404528,320.884166,-189.521002,347.251388,-177.753951]],["curveTo",[373.61861,-165.9869,394.135697,-150.753097,408.802648,-132.052542]],["curveTo",[423.4696,-113.351986,430.803076,-93.934786,430.803076,-73.80094]],["curveTo",[430.803076,-47.26698,421.369571,-28.433148,402.502563,-17.299445]],["curveTo",[383.635555,-6.165741,356.035028,-0.598889,319.700982,-0.598889]],["lineTo",[216.79795,-0.598889]],["curveTo",[212.39869,-0.598889,204.582372,0.051118,193.348996,1.351132]],["curveTo",[182.11562,2.651146,170.132137,4.601168,157.398548,7.201196]],["curveTo",[135.197779,-7.799231,119.147231,-24.116403,109.246903,-41.75032]],["curveTo",[99.346576,-59.384238,94.396412,-76.668091,94.396412,-93.60188]],["curveTo",[94.396412,-124.002563,108.363306,-148.569842,136.297095,-167.303716]],["curveTo",[164.230884,-186.037591,203.331625,-195.404528,253.599317,-195.404528]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.399829,-12]],["curveTo",[170.999573,-12.0,137.466254,-5.950021,107.799872,6.149936]],["curveTo",[78.13349,18.249893,53.000427,34.533106,32.400683,54.999573]],["lineTo",[55.798377,82.596326]],["curveTo",[76.131568,65.796383,98.048199,52.129645,121.54827,41.596113]],["curveTo",[145.048341,31.06258,174.732166,25.795814,210.599744,25.795814]],["curveTo",[248.400683,25.795814,276.984764,35.346148,296.351986,54.446818]],["curveTo",[315.719208,73.547487,325.402819,95.598177,325.402819,120.598889]],["curveTo",[325.402819,140.666097,319.452727,157.299872,307.552542,170.500214]],["curveTo",[295.652357,183.700555,280.768831,194.750819,262.901965,203.651004]],["curveTo",[245.035099,212.551189,226.801652,220.334615,208.201623,227.001282]],["curveTo",[183.668233,235.667948,159.934786,245.484551,137.001282,256.451089]],["curveTo",[114.067777,267.417628,95.267621,281.367507,80.600812,298.300726]],["curveTo",[65.934003,315.233946,58.600598,337.267265,58.600598,364.400683]],["curveTo",[58.600598,399.333333,71.983696,429.366225,98.749893,454.499359]],["curveTo",[125.51609,479.632493,163.732308,492.19906,213.398548,492.19906]],["curveTo",[240.598035,492.19906,265.96426,487.532465,289.497223,478.199274]],["curveTo",[313.030187,468.866083,333.396554,456.932935,350.596326,442.399829]],["lineTo",[327.198633,414.803076]],["curveTo",[310.93208,426.336324,293.798804,435.819664,275.798804,443.253097]],["curveTo",[257.798804,450.68653,236.598605,454.403246,212.198206,454.403246]],["curveTo",[174.730457,454.403246,147.229674,445.319593,129.695856,427.152285]],["curveTo",[112.162039,408.984978,103.39513,388.934359,103.39513,367.000427]],["curveTo",[103.39513,348.866581,108.47857,333.982842,118.645451,322.34921]],["curveTo",[128.812331,310.715577,142.195856,300.89869,158.796027,292.898548]],["curveTo",[175.396198,284.898405,193.462908,277.264986,212.996155,269.998291]],["curveTo",[238.329489,260.198206,263.096255,249.964901,287.296455,239.298377]],["curveTo",[311.496654,228.631852,331.36352,214.3653,346.897053,196.498718]],["curveTo",[362.430585,178.632137,370.197352,154.265414,370.197352,123.398548]],["curveTo",[370.197352,99.265556,363.980777,76.949167,351.547629,56.449381]],["curveTo",[339.114481,35.949594,320.814752,19.416417,296.648441,6.84985]],["curveTo",[272.48213,-5.716717,243.065926,-12.0,208.399829,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[227.198633,-12]],["curveTo",[192.265983,-12.0,160.299801,-4.766695,131.300085,9.699915]],["curveTo",[102.30037,24.166524,79.233874,44.81646,62.100598,71.649722]],["curveTo",[44.967322,98.482984,36.400683,130.266268,36.400683,166.999573]],["curveTo",[36.400683,197.665955,43.58401,225.065784,57.950662,249.19906]],["curveTo",[72.317315,273.332337,90.717286,295.265698,113.150577,314.999146]],["curveTo",[135.583867,334.732593,159.500498,353.49943,184.90047,371.299658]],["curveTo",[210.300441,389.099886,234.217072,407.000142,256.650363,425.000427]],["curveTo",[279.083654,443.000712,297.483625,462.034316,311.850278,482.101239]],["curveTo",[326.21693,502.168162,333.400256,524.601737,333.400256,549.401965]],["curveTo",[333.400256,570.602307,327.983483,590.102734,317.149936,607.903246]],["curveTo",[306.316389,625.703759,287.665955,634.604015,261.198633,634.604015]],["curveTo",[232.331197,634.604015,209.580664,624.020433,192.947031,602.853268]],["curveTo",[176.313399,581.686103,167.996583,555.93564,167.996583,525.60188]],["curveTo",[167.996583,491.934786,175.396554,456.484337,190.196497,419.250534]],["curveTo",[204.99644,382.016731,224.546348,344.799587,248.84622,307.599103]],["curveTo",[273.146091,270.398619,300.295956,235.131497,330.295814,201.797736]],["curveTo",[360.295671,168.463976,390.462196,139.096967,420.795387,113.696711]],["curveTo",[446.395273,92.229816,471.278442,74.296241,495.444895,59.895985]],["curveTo",[519.611348,45.495728,541.794675,34.528834,561.994874,26.995301]],["lineTo",[547.19607,-12]],["curveTo",[525.130001,-4.199915,501.16389,7.350135,475.297736,22.65015]],["curveTo",[449.431582,37.950164,422.432009,57.500214,394.299018,81.300299]],["curveTo",[361.232806,109.767051,328.833191,141.90047,297.100171,177.700555]],["curveTo",[265.367151,213.500641,236.63406,251.267407,210.900897,291.000854]],["curveTo",[185.167735,330.734302,164.717856,370.41777,149.55126,410.05126]],["curveTo",[134.384665,449.68475,126.801367,487.601595,126.801367,523.801794]],["curveTo",[126.801367,552.134985,132.517941,577.384807,143.951089,599.55126]],["curveTo",[155.384238,621.717713,171.350634,639.167521,191.850278,651.900683]],["curveTo",[212.349922,664.633846,235.932792,671.000427,262.598889,671.000427]],["curveTo",[298.39812,671.000427,325.814111,659.717215,344.84686,637.15079]],["curveTo",[363.87961,614.584366,373.395985,585.934786,373.395985,551.20205]],["curveTo",[373.395985,523.668803,366.429304,498.735512,352.495942,476.402179]],["curveTo",[338.56258,454.068845,320.51253,433.31881,298.345792,414.152072]],["curveTo",[276.179055,394.985334,252.528976,376.268546,227.395557,358.001709]],["curveTo",[202.262139,339.734871,178.61206,321.217998,156.445323,302.451089]],["curveTo",[134.278585,283.684181,116.228535,263.50057,102.295173,241.900256]],["curveTo",[88.361811,220.299943,81.39513,196.266268,81.39513,169.799231]],["curveTo",[81.39513,140.598605,88.211946,115.298021,101.845579,93.89748]],["curveTo",[115.479211,72.496939,133.579596,55.863164,156.146732,43.996155]],["curveTo",[178.713869,32.129147,203.397693,26.195643,230.198206,26.195643]],["curveTo",[260.531966,26.195643,289.11562,34.112416,315.949167,49.945963]],["curveTo",[342.782714,65.77951,367.132849,85.729745,388.999573,109.796668]],["curveTo",[416.73302,140.99701,440.449737,176.514025,460.149722,216.347715]],["curveTo",[479.849708,256.181404,495.933077,299.13171,508.399829,345.198633]],["lineTo",[550.994447,345.198633]],["curveTo",[537.127723,296.598605,519.461128,250.098605,497.99466,205.698633]],["curveTo",[476.528193,161.298662,450.728464,121.398832,420.595472,85.999146]],["curveTo",[395.329062,57.332764,367.129432,33.832977,335.996583,15.499786]],["curveTo",[304.863733,-2.833405,268.59775,-12.0,227.198633,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[387.664477,-145.66525]],["curveTo",[339.44234,-145.66525,294.859168,-137.859683,253.91496,-122.248551]],["curveTo",[212.970751,-106.637418,177.443242,-84.02635,147.332432,-54.415346]],["curveTo",[117.221621,-24.804342,93.749646,11.612163,76.916506,54.834171]],["curveTo",[60.083366,98.056178,51.666795,147.222523,51.666795,202.333205]],["curveTo",[51.666795,267.66628,62.333398,326.777155,83.666602,379.665829]],["curveTo",[104.999807,432.554503,134.138513,477.887644,171.082721,515.66525]],["curveTo",[208.02693,553.442856,250.443306,582.442727,298.331852,602.664863]],["curveTo",[346.220397,622.886999,396.442211,632.998068,448.997295,632.998068]],["curveTo",[513.44131,632.998068,569.079823,619.748164,615.912834,593.248357]],["curveTo",[662.745845,566.748551,698.912254,529.748808,724.412061,482.24913]],["curveTo",[749.911868,434.749452,762.661771,379.222008,762.661771,315.666795]],["curveTo",[762.661771,273.111455,757.022936,235.639415,745.745265,203.250676]],["curveTo",[734.467595,170.861938,719.884423,143.584267,701.995749,121.417665]],["curveTo",[684.107074,99.251063,664.301766,82.639995,642.579823,71.584461]],["curveTo",[620.85788,60.528927,599.552635,55.001159,578.66409,55.001159]],["curveTo",[552.219817,55.001159,530.081111,61.251127,512.247971,73.751063]],["curveTo",[494.414831,86.250998,484.331659,105.5565,481.998454,131.667568]],["lineTo",[479.998454,131.667568]],["curveTo",[462.664863,112.334493,443.775931,95.779023,423.331659,82.001159]],["curveTo",[402.887386,68.223296,381.109651,61.334364,357.998454,61.334364]],["curveTo",[326.776704,61.334364,299.804793,72.862002,277.082721,95.917279]],["curveTo",[254.360649,118.972555,242.999614,153.000129,242.999614,198]],["curveTo",[242.999614,222.332947,247.166216,247.69379,255.49942,274.082528]],["curveTo",[263.832625,300.471267,276.02693,324.943371,292.082335,347.498841]],["curveTo",[308.13774,370.05431,327.804278,388.332045,351.081948,402.332045]],["curveTo",[374.359619,416.332045,400.665121,423.332045,429.998454,423.332045]],["curveTo",[447.887386,423.332045,463.748551,419.304278,477.581948,411.248744]],["curveTo",[491.415346,403.19321,503.220977,390.998841,512.998841,374.665636]],["lineTo",[514.998841,374.665636]],["lineTo",[523.831852,414.998841]],["lineTo",[557.662158,414.998841]],["lineTo",[522.32934,226.333977]],["curveTo",[511.217885,180.555341,510.773355,146.665765,520.995749,124.66525]],["curveTo",[531.218143,102.664734,551.551862,91.664477,581.996908,91.664477]],["curveTo",[602.997037,91.664477,624.413929,100.331208,646.247584,117.66467]],["curveTo",[668.08124,134.998132,686.33153,160.192823,700.998454,193.248744]],["curveTo",[715.665378,226.304664,722.998841,266.332947,722.998841,313.333591]],["curveTo",[722.998841,356.000773,716.720977,394.695593,704.16525,429.418052]],["curveTo",[691.609522,464.14051,673.248229,493.779538,649.081369,518.335137]],["curveTo",[624.914509,542.890735,595.77548,561.862968,561.664283,575.251836]],["curveTo",[527.553086,588.640703,488.942018,595.335137,445.831079,595.335137]],["curveTo",[400.497488,595.335137,356.747262,586.140639,314.580402,567.751643]],["curveTo",[272.413543,549.362646,234.524482,523.056951,200.91322,488.834557]],["curveTo",[167.301959,454.612163,140.746296,413.500837,121.246232,365.50058]],["curveTo",[101.746167,317.500322,91.996135,263.888932,91.996135,204.666409]],["curveTo",[91.996135,156.332947,99.190569,112.943886,113.579436,74.499227]],["curveTo",[127.968304,36.054568,148.41277,3.276575,174.912834,-23.83475]],["curveTo",[201.412898,-50.946076,232.940794,-71.751836,269.496522,-86.252029]],["curveTo",[306.052249,-100.752222,346.441438,-108.002319,390.66409,-108.002319]],["curveTo",[423.664477,-108.002319,453.886935,-103.557789,481.331465,-94.668728]],["curveTo",[508.775996,-85.779667,534.331659,-74.335008,557.998454,-60.33475]],["lineTo",[574.830692,-92.331659]],["curveTo",[546.38642,-109.998454,516.636516,-123.304085,485.580982,-132.248551]],["curveTo",[454.525448,-141.193017,421.886613,-145.66525,387.664477,-145.66525]],["closePath",[]],["moveTo",[363.830306,98.330886]],["curveTo",[380.275094,98.330886,397.497681,103.692115,415.498068,114.414573]],["curveTo",[433.498454,125.137032,452.332174,141.998454,471.999227,164.998841]],["lineTo",[502.999614,337.667568]],["curveTo",[491.33256,356.223682,479.86007,368.974037,468.582142,375.918631]],["curveTo",[457.304214,382.863226,444.053988,386.335523,428.831465,386.335523]],["curveTo",[399.053086,386.335523,373.385904,376.779861,351.829919,357.668535]],["curveTo",[330.273934,338.557209,313.634713,314.695786,301.912254,286.084267]],["curveTo",[290.189796,257.472748,284.328567,229.055663,284.328567,200.833011]],["curveTo",[284.328567,163.165443,292.134326,136.664863,307.745845,121.331272]],["curveTo",[323.357364,105.997681,342.052184,98.330886,363.830306,98.330886]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[242.798804,-12]],["curveTo",[203.732308,-12.0,168.982486,-4.366652,138.549338,10.900043]],["curveTo",[108.11619,26.166738,84.116332,47.1001,66.549765,73.700128]],["curveTo",[48.983198,100.300157,40.199915,130.333476,40.199915,163.800085]],["curveTo",[40.199915,192.666951,46.650007,218.667165,59.550192,241.800726]],["curveTo",[72.450377,264.934287,88.683896,285.151146,108.250748,302.451303]],["curveTo",[127.817599,319.751459,147.601025,333.734871,167.601025,344.401538]],["lineTo",[167.601025,348.401538]],["curveTo",[144.734444,364.1347,123.817742,384.001282,104.850918,408.001282]],["curveTo",[85.884095,432.001282,76.400683,460.934643,76.400683,494.801367]],["curveTo",[76.400683,525.534387,83.900612,552.750819,98.90047,576.450662]],["curveTo",[113.900327,600.150505,133.950093,618.567065,159.049765,631.700342]],["curveTo",[184.149438,644.833618,212.265698,651.400256,243.398548,651.400256]],["curveTo",[297.931653,651.400256,340.36473,635.566923,370.697779,603.900256]],["curveTo",[401.030827,572.23359,416.197352,532.400399,416.197352,484.400683]],["curveTo",[416.197352,460.867151,411.180621,438.666951,401.147159,417.800085]],["curveTo",[391.113698,396.933219,379.046917,378.199701,364.946818,361.59953]],["curveTo",[350.846718,344.999359,337.630073,331.699274,325.296882,321.699274]],["lineTo",[325.296882,317.699274]],["curveTo",[343.563434,306.432579,361.313399,293.732593,378.546775,279.599317]],["curveTo",[395.780151,265.46604,409.830201,248.349423,420.696924,228.249466]],["curveTo",[431.563648,208.149509,436.99701,183.799516,436.99701,155.199487]],["curveTo",[436.99701,124.19963,428.980421,96.116403,412.947245,70.949808]],["curveTo",[396.914068,45.783212,374.264275,25.666595,344.997864,10.599957]],["curveTo",[315.731454,-4.466681,281.6651,-12.0,242.798804,-12]],["closePath",[]],["moveTo",[289.799231,334.79795]],["curveTo",[317.133419,357.13171,337.934074,380.648868,352.201196,405.349423]],["curveTo",[366.468318,430.049979,373.60188,456.00057,373.60188,483.201196]],["curveTo",[373.60188,506.86829,368.551759,528.668731,358.451516,548.60252]],["curveTo",[348.351274,568.536309,333.517585,584.486687,313.950449,596.453652]],["curveTo",[294.383312,608.420618,270.732735,614.404101,242.998718,614.404101]],["curveTo",[207.264417,614.404101,177.863662,603.120461,154.796455,580.553182]],["curveTo",[131.729247,557.985903,120.195643,529.401965,120.195643,494.801367]],["curveTo",[120.195643,464.600598,128.51253,439.699986,145.146305,420.09953]],["curveTo",[161.78008,400.499074,183.097252,384.132066,209.097821,370.998505]],["curveTo",[235.098391,357.864944,261.998861,345.798092,289.799231,334.79795]],["closePath",[]],["moveTo",[243.198633,24.996155]],["curveTo",[273.266126,24.996155,299.466823,30.762993,321.800726,42.296668]],["curveTo",[344.134629,53.830343,361.46839,69.530756,373.802008,89.397907]],["curveTo",[386.135626,109.265058,392.302435,131.398832,392.302435,155.799231]],["curveTo",[392.302435,188.53339,382.985547,215.167379,364.351773,235.701196]],["curveTo",[345.717998,256.235014,321.417485,273.385377,291.450235,287.152285]],["curveTo",[261.482984,300.919194,229.932507,314.336039,196.798804,327.402819]],["curveTo",[164.664388,308.335754,137.780151,285.435284,116.146091,258.70141]],["curveTo",[94.512032,231.967535,83.695002,201.000142,83.695002,165.799231]],["curveTo",[83.695002,139.532109,90.528478,115.781646,104.195429,94.547843]],["curveTo",[117.862381,73.31404,136.72946,56.413641,160.796668,43.846647]],["curveTo",[184.863876,31.279653,212.331197,24.996155,243.198633,24.996155]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.999573,257.000427]],["curveTo",[151.999715,257.000427,126.933219,265.033746,105.800085,281.100384]],["curveTo",[84.666951,297.167023,68.417129,320.700342,57.050619,351.700342]],["curveTo",[45.684109,382.700342,40.000854,420.467037,40.000854,465.000427]],["curveTo",[40.000854,530.933789,52.700698,581.750463,78.100384,617.450449]],["curveTo",[103.500071,653.150434,137.799801,671.000427,180.999573,671.000427]],["curveTo",[224.799373,671.000427,259.24911,653.150434,284.348783,617.450449]],["curveTo",[309.448455,581.750463,321.998291,530.933789,321.998291,465.000427]],["curveTo",[321.998291,420.467037,316.315036,382.700342,304.948526,351.700342]],["curveTo",[293.582016,320.700342,277.332194,297.167023,256.19906,281.100384]],["curveTo",[235.065926,265.033746,209.99943,257.000427,180.999573,257.000427]],["closePath",[]],["moveTo",[180.999573,289.797522]],["curveTo",[212.333761,289.797522,237.201054,305.147871,255.601452,335.848569]],["curveTo",[274.001851,366.549267,283.20205,409.599886,283.20205,465.000427]],["curveTo",[283.20205,521.000997,274.001851,563.86829,255.601452,593.602307]],["curveTo",[237.201054,623.336324,212.333761,638.203332,180.999573,638.203332]],["curveTo",[150.332052,638.203332,125.631425,623.336324,106.897693,593.602307]],["curveTo",[88.163961,563.86829,78.797095,521.000997,78.797095,465.000427]],["curveTo",[78.797095,409.599886,88.163961,366.549267,106.897693,335.848569]],["curveTo",[125.631425,305.147871,150.332052,289.797522,180.999573,289.797522]],["closePath",[]],["moveTo",[197.799231,-12.0]],["lineTo",[565.600171,671.000427]],["lineTo",[603.997437,671.000427]],["lineTo",[236.196497,-12.0]],["closePath",[]],["moveTo",[624.197352,-12.0]],["curveTo",[595.197494,-12.0,570.130998,-3.966681,548.997864,12.099957]],["curveTo",[527.86473,28.166595,511.614908,51.699915,500.248398,82.699915]],["curveTo",[488.881888,113.699915,483.198633,151.46661,483.198633,196.0]],["curveTo",[483.198633,261.933362,495.898476,312.750036,521.298163,348.450021]],["curveTo",[546.69785,384.150007,580.997579,402.0,624.197352,402.0]],["curveTo",[667.997152,402.0,702.446889,384.150007,727.546561,348.450021]],["curveTo",[752.646234,312.750036,765.19607,261.933362,765.19607,196.0]],["curveTo",[765.19607,151.46661,759.512815,113.699915,748.146305,82.699915]],["curveTo",[736.779795,51.699915,720.529973,28.166595,699.396839,12.099957]],["curveTo",[678.263705,-3.966681,653.197209,-12.0,624.197352,-12.0]],["closePath",[]],["moveTo",[624.197352,20.797095]],["curveTo",[655.531539,20.797095,680.398832,36.147444,698.799231,66.848142]],["curveTo",[717.19963,97.54884,726.399829,140.599459,726.399829,196.0]],["curveTo",[726.399829,252.00057,717.19963,294.867863,698.799231,324.60188]],["curveTo",[680.398832,354.335896,655.531539,369.202905,624.197352,369.202905]],["curveTo",[593.529831,369.202905,568.829204,354.335896,550.095472,324.60188]],["curveTo",[531.36174,294.867863,521.994874,252.00057,521.994874,196.0]],["curveTo",[521.994874,140.599459,531.36174,97.54884,550.095472,66.848142]],["curveTo",[568.829204,36.147444,593.529831,20.797095,624.197352,20.797095]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]],["moveTo",[165.199487,724.601452]],["curveTo",[155.133419,724.601452,146.517229,727.901253,139.350918,734.500854]],["curveTo",[132.184608,741.100456,128.601452,750.0,128.601452,761.199487]],["curveTo",[128.601452,772.398975,132.184608,781.298519,139.350918,787.89812]],["curveTo",[146.517229,794.497722,155.133419,797.797522,165.199487,797.797522]],["curveTo",[175.932223,797.797522,184.715079,794.497722,191.548056,787.89812]],["curveTo",[198.381034,781.298519,201.797522,772.398975,201.797522,761.199487]],["curveTo",[201.797522,750.0,198.381034,741.100456,191.548056,734.500854]],["curveTo",[184.715079,727.901253,175.932223,724.601452,165.199487,724.601452]],["closePath",[]],["moveTo",[360.79795,724.601452]],["curveTo",[350.731881,724.601452,342.115691,727.901253,334.949381,734.500854]],["curveTo",[327.78307,741.100456,324.199915,750.0,324.199915,761.199487]],["curveTo",[324.199915,772.398975,327.78307,781.298519,334.949381,787.89812]],["curveTo",[342.115691,794.497722,350.731881,797.797522,360.79795,797.797522]],["curveTo",[371.530685,797.797522,380.313541,794.497722,387.146519,787.89812]],["curveTo",[393.979496,781.298519,397.395985,772.398975,397.395985,761.199487]],["curveTo",[397.395985,750.0,393.979496,741.100456,387.146519,734.500854]],["curveTo",[380.313541,727.901253,371.530685,724.601452,360.79795,724.601452]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[273.999227,-12]],["curveTo",[232.999614,-12.0,195.666602,-1.972233,162.000193,18.083301]],["curveTo",[128.333784,38.138835,101.528411,66.91657,81.584074,104.416506]],["curveTo",[61.639737,141.916441,51.667568,186.888545,51.667568,239.332818]],["curveTo",[51.667568,291.554954,61.695271,336.55489,81.750676,374.332625]],["curveTo",[101.806082,412.11036,127.778121,441.193661,159.666795,461.582528]],["curveTo",[191.55547,481.971396,225.444144,492.165829,261.332818,492.165829]],["curveTo",[299.332303,492.165829,332.081948,483.415926,359.581755,465.916119]],["curveTo",[387.081562,448.416312,408.192566,423.138706,422.914766,390.083301]],["curveTo",[437.636967,357.027896,444.998068,317.000129,444.998068,270]],["curveTo",[444.998068,264.33372,444.914766,258.445174,444.748164,252.334364]],["curveTo",[444.581562,246.223553,443.77606,239.890478,442.331659,233.335137]],["lineTo",[79.329726,233.335137]],["lineTo",[79.329726,271.331272]],["lineTo",[403.002705,271.331272]],["curveTo",[403.002705,332.222136,390.280118,377.834622,364.834944,408.168728]],["curveTo",[339.389769,438.502834,305.000129,453.669887,261.666023,453.669887]],["curveTo",[235.220977,453.669887,209.414831,446.030795,184.247584,430.752609]],["curveTo",[159.080338,415.474423,138.302088,392.08504,121.912834,360.584461]],["curveTo",[105.52358,329.083881,97.328953,289.222265,97.328953,240.999614]],["curveTo",[97.328953,196.776962,105.245716,158.60978,121.079243,126.498068]],["curveTo",[136.91277,94.386355,158.607525,69.719173,186.16351,52.496522]],["curveTo",[213.719495,35.27387,244.331143,26.662544,277.998454,26.662544]],["curveTo",[304.332174,26.662544,328.24913,30.218143,349.749324,37.32934]],["curveTo",[371.249517,44.440537,390.332947,53.885067,406.999614,65.662931]],["lineTo",[425.331272,34.999614]],["curveTo",[406.553795,22.333205,385.443049,11.333333,361.999034,2.0]],["curveTo",[338.555019,-7.333333,309.22175,-12.0,273.999227,-12]],["closePath",[]],["moveTo",[189.000427,570.0]],["lineTo",[326.001709,723.998291]],["lineTo",[378.195643,723.998291]],["lineTo",[380.995301,718.598889]],["lineTo",[228.996155,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[8.000773,0]],["lineTo",[240.168921,659.000386]],["lineTo",[286.161964,659.000386]],["lineTo",[518.330113,0]],["lineTo",[469.669114,0]],["lineTo",[338,388.003092]],["curveTo",[324.555341,427.114547,311.888481,464.864708,299.99942,501.253575]],["curveTo",[288.11036,537.642442,276.387966,575.781471,264.832238,615.67066]],["lineTo",[260.832238,615.67066]],["curveTo",[249.387579,575.670403,237.720719,537.503607,225.831659,501.170274]],["curveTo",[213.942598,464.836941,201.275738,427.114547,187.831079,388.003092]],["lineTo",[54.328567,0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[113.999227,264.666409]],["lineTo",[409.498261,264.666409]],["lineTo",[409.498261,226.170467]],["closePath",[]],["moveTo",[262.965399,699.600171]],["curveTo",[234.521714,699.600171,210.98334,707.283426,192.350278,722.649936]],["curveTo",[173.717215,738.016446,164.400683,758.949594,164.400683,785.449381]],["curveTo",[164.400683,811.949167,173.717215,832.865656,192.350278,848.198847]],["curveTo",[210.98334,863.532038,234.521714,871.198633,262.965399,871.198633]],["curveTo",[292.120177,871.198633,315.84743,863.537987,334.147159,848.216696]],["curveTo",[352.446889,832.895405,361.596754,811.956307,361.596754,785.399402]],["curveTo",[361.596754,758.932935,352.446889,738.016446,334.147159,722.649936]],["curveTo",[315.84743,707.283426,292.120177,699.600171,262.965399,699.600171]],["closePath",[]],["moveTo",[262.967153,725.398548]],["curveTo",[281.58851,725.398548,297.082657,730.448597,309.449594,740.548697]],["curveTo",[321.816531,750.648797,328.0,765.589512,328.0,785.370843]],["curveTo",[328.0,805.790281,321.816531,820.900043,309.449594,830.700128]],["curveTo",[297.082657,840.500214,281.58851,845.400256,262.967153,845.400256]],["curveTo",[244.987912,845.400256,229.664816,840.500214,216.997864,830.700128]],["curveTo",[204.330913,820.900043,197.997437,805.790281,197.997437,785.370843]],["curveTo",[197.997437,765.589512,204.330913,750.648797,216.997864,740.548697]],["curveTo",[229.664816,730.448597,244.987912,725.398548,262.967153,725.398548]],["closePath",[]]]}]},{"coordinates":[250.0],"glyphs":[{"char":"A","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[98.600598,0]],["lineTo",[98.600598,659.500214]],["lineTo",[455.899188,659.500214]],["lineTo",[455.899188,625.702691]],["lineTo",[137.597608,625.702691]],["lineTo",[137.597608,367.899188]],["lineTo",[404.698847,367.899188]],["lineTo",[404.698847,334.101666]],["lineTo",[137.597608,334.101666]],["lineTo",[137.597608,33.797522]],["lineTo",[465.899188,33.797522]],["lineTo",[465.899188,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[325.082915,-12]],["curveTo",[272.694176,-12.0,226.208188,2.1389,185.624952,30.416699]],["curveTo",[145.041715,58.694498,113.194563,98.638964,90.083494,150.250097]],["curveTo",[66.972426,201.861229,55.416892,262.388996,55.416892,331.833398]],["curveTo",[55.416892,401.277799,66.972426,461.458349,90.083494,512.375048]],["curveTo",[113.194563,563.291747,145.041715,602.541779,185.624952,630.125145]],["curveTo",[226.208188,657.70851,272.694176,671.500193,325.082915,671.500193]],["curveTo",[377.527187,671.500193,424.040942,657.70851,464.624179,630.125145]],["curveTo",[505.207415,602.541779,537.054568,563.291747,560.165636,512.375048]],["curveTo",[583.276704,461.458349,594.832238,401.277799,594.832238,331.833398]],["curveTo",[594.832238,262.388996,583.276704,201.861229,560.165636,150.250097]],["curveTo",[537.054568,98.638964,505.207415,58.694498,464.624179,30.416699]],["curveTo",[424.040942,2.1389,377.527187,-12.0,325.082915,-12]],["closePath",[]],["moveTo",[325.082915,23.91438]],["curveTo",[370.749968,23.91438,410.666989,36.650593,444.833977,62.123019]],["curveTo",[479.000966,87.595445,505.681774,123.387386,524.876401,169.498841]],["curveTo",[544.071028,215.610295,553.668341,269.721814,553.668341,331.833398]],["curveTo",[553.668341,393.944981,544.071028,447.6954,524.876401,493.084654]],["curveTo",[505.681774,538.473908,479.000966,573.571414,444.833977,598.377174]],["curveTo",[410.666989,623.182934,370.749968,635.585813,325.082915,635.585813]],["curveTo",[279.471396,635.585813,239.568258,623.182934,205.373502,598.377174]],["curveTo",[171.178747,573.571414,144.484055,538.473908,125.289428,493.084654]],["curveTo",[106.094801,447.6954,96.497488,393.944981,96.497488,331.833398]],["curveTo",[96.497488,269.721814,106.094801,215.610295,125.289428,169.498841]],["curveTo",[144.484055,123.387386,171.178747,87.595445,205.373502,62.123019]],["curveTo",[239.568258,36.650593,279.471396,23.91438,325.082915,23.91438]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.499786,-12]],["curveTo",[220.132992,-12.0,177.616332,-2.599957,140.949808,16.200128]],["curveTo",[104.283283,35.000214,73.033461,58.833547,47.200342,87.700128]],["lineTo",[72.598889,112.298163]],["curveTo",[95.932223,85.397978,124.62395,63.939485,158.674071,47.922683]],["curveTo",[192.724192,31.905881,229.399402,23.89748,268.699701,23.89748]],["curveTo",[320.666951,23.89748,361.825787,36.606009,392.176207,62.023067]],["curveTo",[422.526627,87.440125,437.701837,120.465613,437.701837,161.09953]],["curveTo",[437.701837,189.499929,432.143457,212.250249,421.026698,229.350491]],["curveTo",[409.909939,246.450733,395.318169,260.525915,377.251388,271.576036]],["curveTo",[359.184608,282.626157,339.234515,293.084579,317.401111,302.951303]],["lineTo",[216.450662,348.301581]],["curveTo",[196.917414,357.034885,176.834116,367.984836,156.200769,381.151431]],["curveTo",[135.567421,394.318026,118.142389,411.309626,103.925673,432.126228]],["curveTo",[89.708956,452.942831,82.600598,479.334472,82.600598,511.301153]],["curveTo",[82.600598,542.934359,90.717215,570.75089,106.950449,594.750748]],["curveTo",[123.183682,618.750605,145.508543,637.542147,173.925032,651.125374]],["curveTo",[202.341521,664.7086,234.832977,671.500214,271.399402,671.500214]],["curveTo",[311.366083,671.500214,346.857682,663.716859,377.874199,648.15015]],["curveTo",[408.890716,632.58344,433.998861,614.300157,453.198633,593.300299]],["lineTo",[431.600171,568.702264]],["curveTo",[411.500214,588.46903,388.250178,604.5608,361.850064,616.977574]],["curveTo",[335.44995,629.394347,305.299729,635.602734,271.399402,635.602734]],["curveTo",[226.665528,635.602734,190.531753,624.494233,162.998078,602.277232]],["curveTo",[135.464403,580.060231,121.697565,550.534814,121.697565,513.700982]],["curveTo",[121.697565,487.233946,127.905952,465.75032,140.322725,449.250107]],["curveTo",[152.739499,432.749893,168.022925,419.449737,186.173003,409.349637]],["curveTo",[204.323081,399.249537,221.164744,390.516161,236.697992,383.149509]],["lineTo",[337.548484,337.899188]],["curveTo",[362.981774,326.599174,386.298448,313.890859,407.498505,299.774242]],["curveTo",[428.698562,285.657625,445.565285,267.899331,458.098676,246.499359]],["curveTo",[470.632066,225.099388,476.898761,197.499359,476.898761,163.699274]],["curveTo",[476.898761,130.199345,468.49046,100.149438,451.673857,73.549551]],["curveTo",[434.857255,46.949665,410.774028,26.041435,379.424178,10.824861]],["curveTo",[348.074327,-4.391713,311.09953,-12.0,268.499786,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.500193,-12]],["curveTo",[172.500322,-12.0,149.458736,-7.375048,128.375435,1.874855]],["curveTo",[107.292134,11.124758,90.431033,25.360778,77.792134,44.582915]],["curveTo",[65.153234,63.805051,58.833784,88.360585,58.833784,118.249517]],["curveTo",[58.833784,171.749452,83.986632,212.360521,134.292327,240.082721]],["curveTo",[184.598022,267.804922,263.445432,287.610488,370.834557,299.49942]],["curveTo",[371.390156,324.721943,368.834622,349.527831,363.167955,373.917085]],["curveTo",[357.501288,398.306339,346.181774,418.334428,329.209412,434.001353]],["curveTo",[312.237051,449.668277,287.556243,457.501739,255.166989,457.501739]],["curveTo",[222.0,457.501739,191.930325,451.182225,164.957963,438.543197]],["curveTo",[137.985601,425.904168,116.555083,413.834686,100.666409,402.33475]],["lineTo",[82.50058,428.999614]],["curveTo",[93.166989,437.277284,107.333462,446.249452,125.0,455.916119]],["curveTo",[142.666538,465.582786,162.888674,473.860585,185.666409,480.749517]],["curveTo",[208.444144,487.638449,232.833011,491.082915,258.833011,491.082915]],["curveTo",[296.444015,491.082915,326.152203,482.763465,347.957576,466.124565]],["curveTo",[369.762949,449.485666,385.207222,427.319063,394.290394,399.624758]],["curveTo",[403.373567,371.930454,407.915153,341.500193,407.915153,308.333977]],["lineTo",[407.915153,0]],["lineTo",[376.167375,0]],["lineTo",[371.834171,63.166989]],["lineTo",[369.667568,63.166989]],["curveTo",[345.167504,43.278057,318.292391,25.777992,289.04223,10.666795]],["curveTo",[259.792069,-4.444402,229.278057,-12.0,197.500193,-12]],["closePath",[]],["moveTo",[202.332432,21.497874]],["curveTo",[230.888288,21.497874,258.402493,28.261854,284.875048,41.789815]],["curveTo",[311.347603,55.317775,340.000773,74.998583,370.834557,100.832238]],["lineTo",[370.834557,269.001159]],["curveTo",[302.944852,261.223296,249.124758,250.709283,209.374275,237.459122]],["curveTo",[169.623792,224.208961,141.040105,207.903202,123.623212,188.541844]],["curveTo",[106.20632,169.180486,97.497874,146.277413,97.497874,119.832625]],["curveTo",[97.497874,84.498776,107.98412,59.276189,128.95661,44.164863]],["curveTo",[149.929101,29.053537,174.387708,21.497874,202.332432,21.497874]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[272.999614,-12]],["curveTo",[232.499807,-12.0,195.583301,-1.986116,162.250097,18.041651]],["curveTo",[128.916892,38.069418,102.347539,66.791618,82.542037,104.208253]],["curveTo",[62.736535,141.624887,52.833784,186.444273,52.833784,238.666409]],["curveTo",[52.833784,290.777477,62.764302,335.694112,82.625338,373.416312]],["curveTo",[102.486374,411.138513,128.222394,440.180164,159.833398,460.541264]],["curveTo",[191.444402,480.902364,225.055405,491.082915,260.666409,491.082915]],["curveTo",[297.999485,491.082915,330.290974,482.457963,357.540878,465.20806]],["curveTo",[384.790781,447.958156,405.762949,422.902687,420.457383,390.041651]],["curveTo",[435.151817,357.180615,442.499034,317.166731,442.499034,270]],["curveTo",[442.499034,264.833527,442.457383,259.389254,442.374082,253.667182]],["curveTo",[442.290781,247.94511,441.554697,241.945239,440.165829,235.667568]],["lineTo",[74.664863,235.667568]],["lineTo",[74.664863,268.665636]],["lineTo",[406.501353,268.665636]],["curveTo",[406.501353,332.111068,393.306726,379.500644,366.917472,410.834364]],["curveTo",[340.528218,442.168084,305.166731,457.834944,260.833011,457.834944]],["curveTo",[233.610488,457.834944,207.040749,449.932064,181.123792,434.126304]],["curveTo",[155.206836,418.320545,133.817711,394.29252,116.956417,362.04223]],["curveTo",[100.095123,329.79194,91.664477,289.277799,91.664477,240.499807]],["curveTo",[91.664477,195.721814,99.706192,156.888223,115.789621,123.999034]],["curveTo",[131.873051,91.109844,153.970429,65.776253,182.081755,47.998261]],["curveTo",[210.193081,30.220268,241.498905,21.331272,275.999227,21.331272]],["curveTo",[302.832754,21.331272,327.124565,24.942405,348.874662,32.16467]],["curveTo",[370.624758,39.386935,389.83314,48.942533,406.499807,60.831465]],["lineTo",[422.665636,34.499807]],["curveTo",[404.276897,22.166602,383.638191,11.333333,360.749517,2.0]],["curveTo",[337.860843,-7.333333,308.610875,-12.0,272.999614,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.500214,-232.600598]],["curveTo",[208.966823,-232.600598,173.966823,-227.008899,144.500214,-215.825502]],["curveTo",[115.033604,-204.642105,92.383668,-189.100384,76.550406,-169.200342]],["curveTo",[60.717144,-149.300299,52.800513,-125.933647,52.800513,-99.100384]],["curveTo",[52.800513,-77.80037,59.275523,-56.941976,72.225545,-36.525203]],["curveTo",[85.175566,-16.108429,102.667236,2.0,124.700555,17.800085]],["lineTo",[124.700555,21.800085]],["curveTo",[112.667236,29.233376,102.625587,39.308344,94.575609,52.024989]],["curveTo",[86.52563,64.741635,82.500641,80.166595,82.500641,98.299872]],["curveTo",[82.500641,120.066638,88.617329,138.49167,100.850705,153.574968]],["curveTo",[113.084081,168.658266,124.367364,179.666524,134.700555,186.599744]],["lineTo",[134.700555,190.599744]],["curveTo",[119.867293,203.866439,106.092339,221.983127,93.375694,244.949808]],["curveTo",[80.659049,267.916489,74.300726,293.733162,74.300726,322.399829]],["curveTo",[74.300726,354.866296,81.775666,383.807846,96.725545,409.224477]],["curveTo",[111.675424,434.641108,131.683611,454.641108,156.750107,469.224477]],["curveTo",[181.816603,483.807846,209.83312,491.09953,240.799658,491.09953]],["curveTo",[254.166311,491.09953,266.257938,489.941193,277.074541,487.624519]],["curveTo",[287.891143,485.307846,296.766054,482.466183,303.699274,479.09953]],["lineTo",[464.998718,479.09953]],["lineTo",[464.998718,446.301581]],["lineTo",[350.997864,446.301581]],["lineTo",[350.997864,442.301581]],["curveTo",[366.43144,430.501495,379.57333,414.58465,390.423537,394.551047]],["curveTo",[401.273743,374.517443,406.698847,350.333761,406.698847,322]],["curveTo",[406.698847,289.666809,399.282216,260.708529,384.448953,235.12516]],["curveTo",[369.615691,209.541791,349.715791,189.341734,324.749252,174.524989]],["curveTo",[299.782714,159.708244,271.799516,152.299872,240.799658,152.299872]],["curveTo",[227.09953,152.299872,213.057739,154.224904,198.674284,158.074968]],["curveTo",[184.29083,161.925032,171.432436,167.08344,160.099103,173.550192]],["curveTo",[149.732308,165.08344,140.390503,155.216645,132.073686,143.949808]],["curveTo",[123.75687,132.68297,119.598462,118.632778,119.598462,101.799231]],["curveTo",[119.598462,84.165741,126.048484,69.05724,138.948526,56.473729]],["curveTo",[151.848569,43.890218,176.332052,37.598462,212.398975,37.598462]],["lineTo",[316.849637,37.598462]],["curveTo",[370.649438,37.598462,410.124235,28.856828,435.274028,11.373558]],["curveTo",[460.423822,-6.109711,472.998718,-33.734586,472.998718,-71.501068]],["curveTo",[472.998718,-98.634202,463.857112,-124.442404,445.5739,-148.925673]],["curveTo",[427.290688,-173.408942,401.390859,-193.47558,367.874413,-209.125587]],["curveTo",[334.357967,-224.775594,294.8999,-232.600598,249.500214,-232.600598]],["closePath",[]],["moveTo",[240.799658,183.998291]],["curveTo",[263.499929,183.998291,284.550192,189.823366,303.950449,201.473516]],["curveTo",[323.350705,213.123665,339.025915,229.340524,350.976079,250.124092]],["curveTo",[362.926242,270.907661,368.901324,294.999573,368.901324,322.399829]],["curveTo",[368.901324,364.73359,356.301153,398.050619,331.100812,422.350918]],["curveTo",[305.90047,446.651217,275.800085,458.801367,240.799658,458.801367]],["curveTo",[205.799231,458.801367,175.698847,446.651217,150.498505,422.350918]],["curveTo",[125.298163,398.050619,112.697992,364.73359,112.697992,322.399829]],["curveTo",[112.697992,294.999573,118.673074,270.907661,130.623238,250.124092]],["curveTo",[142.573402,229.340524,158.248612,213.123665,177.648868,201.473516]],["curveTo",[197.049124,189.823366,218.099388,183.998291,240.799658,183.998291]],["closePath",[]],["moveTo",[252.799658,-200.702264]],["curveTo",[289.833405,-200.702264,321.85875,-194.593835,348.875694,-182.376976]],["curveTo",[375.892638,-170.160117,396.901182,-154.376548,411.901324,-135.026271]],["curveTo",[426.901467,-115.675993,434.401538,-95.63406,434.401538,-74.90047]],["curveTo",[434.401538,-47.63349,424.768119,-28.216574,405.501282,-16.649722]],["curveTo",[386.234444,-5.082871,358.017514,0.700555,320.850491,0.700555]],["lineTo",[214.398975,0.700555]],["curveTo",[210.866012,0.700555,203.457853,1.358892,192.174498,2.675566]],["curveTo",[180.891143,3.99224,168.732735,5.96725,155.699274,8.600598]],["curveTo",[132.598889,-6.899616,115.906949,-23.808202,105.623452,-42.12516]],["curveTo",[95.339954,-60.442119,90.198206,-78.334045,90.198206,-95.80094]],["curveTo",[90.198206,-127.001282,104.431653,-152.284921,132.898548,-171.651858]],["curveTo",[161.365442,-191.018795,201.332479,-200.702264,252.799658,-200.702264]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.199915,-12]],["curveTo",[170.499786,-12.0,136.983127,-5.975011,107.649936,6.074968]],["curveTo",[78.316745,18.124947,53.500214,34.266553,33.200342,54.499786]],["lineTo",[53.899188,78.298163]],["curveTo",[74.065784,61.564858,95.940766,47.814823,119.524135,37.048056]],["curveTo",[143.107504,26.28129,173.366083,20.897907,210.299872,20.897907]],["curveTo",[249.200342,20.897907,278.659049,30.839741,298.675993,50.723409]],["curveTo",[318.692937,70.607077,328.70141,93.465755,328.70141,119.299445]],["curveTo",[328.70141,139.999715,322.64303,157.149936,310.526271,170.750107]],["curveTo",[298.409512,184.350278,283.301082,195.708743,265.200982,204.825502]],["curveTo",[247.100883,213.942261,228.734159,221.833974,210.100812,228.500641]],["curveTo",[185.500783,237.167307,161.63406,246.908942,138.500641,257.725545]],["curveTo",[115.367222,268.542147,96.38381,282.35042,81.550406,299.150363]],["curveTo",[66.717001,315.950306,59.300299,337.966966,59.300299,365.200342]],["curveTo",[59.300299,399.333333,72.408515,428.849779,98.624947,453.74968]],["curveTo",[124.841378,478.64958,162.532821,491.09953,211.699274,491.09953]],["curveTo",[238.299018,491.09953,263.148797,486.516232,286.248612,477.349637]],["curveTo",[309.348427,468.183041,329.364944,456.466467,346.298163,442.199915]],["lineTo",[325.599317,418.401538]],["curveTo",[309.46604,429.834828,292.399402,439.326499,274.399402,446.876548]],["curveTo",[256.399402,454.426598,234.965969,458.201623,210.099103,458.201623]],["curveTo",[171.365229,458.201623,142.94817,448.74313,124.847928,429.826143]],["curveTo",[106.747686,410.909156,97.697565,390.133846,97.697565,367.500214]],["curveTo",[97.697565,348.766624,102.905952,333.408088,113.322725,321.424605]],["curveTo",[123.739499,309.441122,137.347928,299.366012,154.148014,291.199274]],["curveTo",[170.948099,283.032536,189.064787,275.29916,208.498078,267.999146]],["curveTo",[233.831411,258.099103,258.714794,247.899117,283.148227,237.399188]],["curveTo",[307.58166,226.89926,327.68176,212.765983,343.448526,194.999359]],["curveTo",[359.215293,177.232735,367.098676,152.799373,367.098676,121.699274]],["curveTo",[367.098676,97.966111,360.990389,75.974584,348.773815,55.72469]],["curveTo",[336.55724,35.474797,318.574042,19.124875,294.82422,6.674925]],["curveTo",[271.074398,-5.775025,242.19963,-12.0,208.199915,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[225.599317,-12]],["curveTo",[191.466325,-12.0,160.066567,-4.800014,131.400043,9.599957]],["curveTo",[102.733518,23.999929,79.866937,44.574897,62.800299,71.324861]],["curveTo",[45.733661,98.074826,37.200342,129.799801,37.200342,166.499786]],["curveTo",[37.200342,196.832977,44.375338,224.032892,58.725331,248.09953]],["curveTo",[73.075324,272.166168,91.441976,294.132849,113.825288,313.999573]],["curveTo",[136.2086,333.866296,160.083583,352.833049,185.450235,370.899829]],["curveTo",[210.816887,388.96661,234.69187,407.166738,257.075182,425.500214]],["curveTo",[279.458494,443.833689,297.825146,463.183825,312.175139,483.550619]],["curveTo",[326.525132,503.917414,333.700128,526.634202,333.700128,551.700982]],["curveTo",[333.700128,573.301153,328.158408,593.301367,317.074968,611.701623]],["curveTo",[305.991528,630.10188,286.832977,639.302008,259.599317,639.302008]],["curveTo",[229.832265,639.302008,206.456998,628.426883,189.473516,606.676634]],["curveTo",[172.490033,584.926385,163.998291,558.634487,163.998291,527.80094]],["curveTo",[163.998291,493.63406,171.364944,457.658835,186.098249,419.875267]],["curveTo",[200.831553,382.091699,220.273174,344.31646,244.42311,306.549551]],["curveTo",[268.573046,268.782643,295.564645,232.982415,325.397907,199.148868]],["curveTo",[355.231169,165.315321,385.231098,135.548484,415.397693,109.848355]],["curveTo",[440.864303,88.114908,465.555888,69.89812,489.472448,55.197992]],["curveTo",[513.389008,40.497864,535.564004,29.264417,555.997437,21.497651]],["lineTo",[542.598035,-12]],["curveTo",[521.231667,-4.099957,497.915278,7.508401,472.648868,22.825075]],["curveTo",[447.382458,38.141749,420.882671,57.750107,393.149509,81.65015]],["curveTo",[360.616403,110.216859,328.666595,142.450235,297.300085,178.350278]],["curveTo",[265.933575,214.25032,237.483696,252.133704,211.950449,292.000427]],["curveTo",[186.417201,331.867151,166.108928,371.708885,151.02563,411.52563]],["curveTo",[135.942332,451.342375,128.400683,489.467464,128.400683,525.900897]],["curveTo",[128.400683,554.067493,134.008971,579.10907,145.225545,601.02563]],["curveTo",[156.442119,622.94219,172.091983,640.167094,192.175139,652.700342]],["curveTo",[212.258294,665.23359,235.299729,671.500214,261.299445,671.500214]],["curveTo",[296.19906,671.500214,322.823722,660.358607,341.17343,638.075395]],["curveTo",[359.523138,615.792183,368.697992,587.63406,368.697992,553.601025]],["curveTo",[368.697992,526.167735,361.714652,501.284423,347.747971,478.951089]],["curveTo",[333.78129,456.617756,315.672932,435.826071,293.422896,416.576036]],["curveTo",[271.172861,397.326,247.431155,378.467606,222.197779,360.000854]],["curveTo",[196.964403,341.534102,173.222697,322.775666,150.972661,303.725545]],["curveTo",[128.722626,284.675424,110.614267,264.166951,96.647587,242.200128]],["curveTo",[82.680906,220.233305,75.697565,195.799801,75.697565,168.899616]],["curveTo",[75.697565,138.965969,82.689307,112.982344,96.672789,90.94874]],["curveTo",[110.656272,68.915136,129.206464,51.764915,152.323366,39.498078]],["curveTo",[175.440268,27.23124,200.698847,21.097821,228.099103,21.097821]],["curveTo",[258.93265,21.097821,287.891143,29.139542,314.974584,45.222982]],["curveTo",[342.058024,61.306422,366.566425,81.531539,388.499786,105.898334]],["curveTo",[416.36651,137.498505,440.141535,173.423679,459.824861,213.673857]],["curveTo",[479.508187,253.924035,495.633205,297.232522,508.199915,343.599317]],["lineTo",[544.497223,343.599317]],["curveTo",[530.563862,294.965969,512.980564,248.465969,491.74733,204.099317]],["curveTo",[470.514097,159.732664,445.030898,120.03275,415.297736,84.999573]],["curveTo",[390.331197,56.666382,362.564716,33.416489,331.998291,15.249893]],["curveTo",[301.431867,-2.916702,265.965542,-12.0,225.599317,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[384.832238,-143.832625]],["curveTo",[336.72117,-143.832625,292.346251,-136.013175,251.70748,-120.374275]],["curveTo",[211.068709,-104.735376,175.888288,-82.179842,146.166216,-52.707673]],["curveTo",[116.444144,-23.235505,93.29149,12.972748,76.708253,55.917085]],["curveTo",[60.125016,98.861422,51.833398,147.611261,51.833398,202.166602]],["curveTo",[51.833398,267.166473,62.416699,325.971911,83.583301,378.582915]],["curveTo",[104.749903,431.193918,133.65259,476.277155,170.291361,513.832625]],["curveTo",[206.930131,551.388094,248.971653,580.221363,296.415926,600.332432]],["curveTo",[343.860199,620.4435,393.554439,630.499034,445.498647,630.499034]],["curveTo",[509.387321,630.499034,564.539911,617.374082,610.956417,591.124179]],["curveTo",[657.372923,564.874275,693.206127,528.207737,718.45603,481.124565]],["curveTo",[743.705934,434.041393,756.330886,378.944337,756.330886,315.833398]],["curveTo",[756.330886,273.555727,750.761468,236.319707,739.622633,204.125338]],["curveTo",[728.483798,171.930969,714.108878,144.792134,696.497874,122.708833]],["curveTo",[678.886871,100.625531,659.400883,84.069997,638.039911,73.04223]],["curveTo",[616.67894,62.014463,595.776318,56.50058,575.332045,56.50058]],["curveTo",[549.109909,56.50058,527.207222,62.708897,509.623985,75.125531]],["curveTo",[492.040749,87.542166,482.165829,106.77825,479.999227,132.833784]],["lineTo",[477.999227,132.833784]],["curveTo",[460.332432,113.833913,441.387966,97.389512,421.165829,83.50058]],["curveTo",[400.943693,69.611648,379.221492,62.667182,355.999227,62.667182]],["curveTo",[325.388352,62.667182,298.819063,74.014334,276.291361,96.708639]],["curveTo",[253.763658,119.402944,242.499807,153.166731,242.499807,198]],["curveTo",[242.499807,221.83314,246.583108,246.846895,254.74971,273.041264]],["curveTo",[262.916312,299.235633,274.930131,323.555019,290.791167,345.99942]],["curveTo",[306.652203,368.443822,326.152139,386.666023,349.290974,400.666023]],["curveTo",[372.429809,414.666023,398.665894,421.666023,427.999227,421.666023]],["curveTo",[445.943693,421.666023,461.874275,417.652139,475.790974,409.624372]],["curveTo",[489.707673,401.596605,501.610488,389.49942,511.49942,373.332818]],["lineTo",[513.49942,373.332818]],["lineTo",[521.915926,413.49942]],["lineTo",[551.831079,413.49942]],["lineTo",[517.16467,227.166989]],["curveTo",[505.608942,180.27767,505.053344,145.499549,515.497874,122.832625]],["curveTo",[525.942405,100.1657,546.775931,88.832238,577.998454,88.832238]],["curveTo",[599.165185,88.832238,620.873631,97.58227,643.123792,115.082335]],["curveTo",[665.373953,132.582399,683.999098,158.096412,698.999227,191.624372]],["curveTo",[713.999356,225.152332,721.49942,265.83314,721.49942,313.666795]],["curveTo",[721.49942,357.000386,715.110488,396.18113,702.332625,431.209026]],["curveTo",[689.554761,466.236922,670.957448,496.056436,646.540684,520.667568]],["curveTo",[622.123921,545.278701,592.804407,564.264818,558.582142,577.625918]],["curveTo",[524.359876,590.987018,485.804342,597.667568,442.915539,597.667568]],["curveTo",[397.248744,597.667568,353.206965,588.403653,310.790201,569.875821]],["curveTo",[268.373438,551.34799,230.262241,524.861809,196.45661,490.417279]],["curveTo",[162.65098,455.972748,135.956481,414.583752,116.373116,366.25029]],["curveTo",[96.78975,317.916828,86.998068,263.944466,86.998068,204.333205]],["curveTo",[86.998068,155.83314,94.178618,112.221943,108.539718,73.499614]],["curveTo",[122.900818,34.777284,143.373051,1.721621,169.956417,-25.667375]],["curveTo",[196.539783,-53.056371,228.220397,-74.125918,264.998261,-88.876015]],["curveTo",[301.776124,-103.626111,342.554053,-111.001159,387.332045,-111.001159]],["curveTo",[420.832238,-111.001159,451.360134,-106.445561,478.915733,-97.334364]],["curveTo",[506.471331,-88.223167,532.165829,-76.500837,555.999227,-62.167375]],["lineTo",[571.415346,-90.165829]],["curveTo",[543.19321,-107.999227,513.568258,-121.402042,482.540491,-130.374275]],["curveTo",[451.512724,-139.346508,418.943306,-143.832625,384.832238,-143.832625]],["closePath",[]],["moveTo",[359.915153,95.165443]],["curveTo",[376.804214,95.165443,394.498841,100.679391,412.999034,111.707287]],["curveTo",[431.499227,122.735182,450.832754,139.999227,470.999614,163.49942]],["lineTo",[502.499807,338.833784]],["curveTo",[490.332947,358.111841,478.513368,371.320352,467.041071,378.459316]],["curveTo",[455.568773,385.59828,442.026994,389.167762,426.415733,389.167762]],["curveTo",[395.859876,389.167762,369.526285,379.473264,347.41496,360.084267]],["curveTo",[325.303634,340.695271,308.234023,316.431227,296.206127,287.292134]],["curveTo",[284.178231,258.153041,278.164283,229.194498,278.164283,200.416506]],["curveTo",[278.164283,161.582721,286.23383,134.332432,302.372923,118.665636]],["curveTo",[318.512015,102.998841,337.692759,95.165443,359.915153,95.165443]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[241.399402,-12]],["curveTo",[202.532821,-12.0,167.991243,-4.349993,137.774669,10.950021]],["curveTo",[107.558095,26.250036,83.724833,47.216717,66.274883,73.850064]],["curveTo",[48.824932,100.483412,40.099957,130.500071,40.099957,163.900043]],["curveTo",[40.099957,193.000142,46.658337,219.250249,59.775096,242.650363]],["curveTo",[72.891855,266.050477,89.341948,286.49224,109.125374,303.975651]],["curveTo",[128.9088,321.459063,148.800513,335.534102,168.800513,346.200769]],["lineTo",[168.800513,350.200769]],["curveTo",[146.033889,365.734017,125.075538,385.500641,105.925459,409.500641]],["curveTo",[86.775381,433.500641,77.200342,462.467322,77.200342,496.400683]],["curveTo",[77.200342,526.767194,84.616973,553.708743,99.450235,577.225331]],["curveTo",[114.283497,600.741919,134.05838,619.033533,158.774883,632.100171]],["curveTo",[183.491385,645.166809,211.132849,651.700128,241.699274,651.700128]],["curveTo",[295.965827,651.700128,338.099032,635.866795,368.098889,604.200128]],["curveTo",[398.098747,572.533461,413.098676,532.866866,413.098676,485.200342]],["curveTo",[413.098676,461.433575,408.006977,439.000142,397.82358,417.900043]],["curveTo",[387.640182,396.799943,375.440125,377.84985,361.223409,361.049765]],["curveTo",[347.006692,344.24968,333.815036,330.849637,321.648441,320.849637]],["lineTo",[321.648441,316.849637]],["curveTo",[339.781717,305.549623,357.490033,292.866296,374.773387,278.799658]],["curveTo",[392.056742,264.73302,406.1651,247.674712,417.098462,227.624733]],["curveTo",[428.031824,207.574754,433.498505,183.233091,433.498505,154.599744]],["curveTo",[433.498505,123.766482,425.573544,95.808202,409.723622,70.724904]],["curveTo",[393.873701,45.641606,371.465471,25.583298,342.498932,10.549979]],["curveTo",[313.532394,-4.48334,279.83255,-12.0,241.399402,-12]],["closePath",[]],["moveTo",[288.899616,332.398975]],["curveTo",[317.233376,355.232522,338.80037,379.324434,353.600598,404.674712]],["curveTo",[368.400826,430.024989,375.80094,456.666951,375.80094,484.600598]],["curveTo",[375.80094,508.767478,370.609213,531.084366,360.225758,551.55126]],["curveTo",[349.842304,572.018155,334.592126,588.41001,314.475224,600.726826]],["curveTo",[294.358323,613.043642,270.033034,619.20205,241.499359,619.20205]],["curveTo",[204.632208,619.20205,174.348498,607.560231,150.648227,584.276591]],["curveTo",[126.947957,560.992952,115.097821,531.700982,115.097821,496.400683]],["curveTo",[115.097821,465.300299,123.672932,439.683326,140.823152,419.549765]],["curveTo",[157.973373,399.416204,179.881959,382.649366,206.548911,369.249252]],["curveTo",[233.215862,355.849139,260.666097,343.565713,288.899616,332.398975]],["closePath",[]],["moveTo",[241.599317,20.498078]],["curveTo",[272.633063,20.498078,299.650078,26.46483,322.650363,38.398334]],["curveTo",[345.650648,50.331838,363.484195,66.515378,376.151004,86.948953]],["curveTo",[388.817813,107.382529,395.151217,130.03275,395.151217,154.899616]],["curveTo",[395.151217,188.600028,385.576107,216.000356,366.425886,237.100598]],["curveTo",[347.275666,258.20084,322.375409,275.776022,291.725117,289.826143]],["curveTo",[261.074826,303.876264,228.966254,317.501353,195.399402,330.70141]],["curveTo",[161.998861,311.167877,134.056742,287.717642,111.573046,260.350705]],["curveTo",[89.089349,232.983768,77.847501,201.166738,77.847501,164.899616]],["curveTo",[77.847501,138.099388,84.847572,113.80749,98.847715,92.023921]],["curveTo",[112.847857,70.240353,132.198064,52.873487,156.898334,39.923323]],["curveTo",[181.598605,26.97316,209.832265,20.498078,241.599317,20.498078]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.499786,257.500214]],["curveTo",[151.666524,257.500214,126.799943,265.516873,105.900043,281.550192]],["curveTo",[85.000142,297.583511,68.958565,321.100171,57.77531,352.100171]],["curveTo",[46.592055,383.100171,41.000427,420.900185,41.000427,465.500214]],["curveTo",[41.000427,531.466894,53.517016,582.291898,78.550192,617.975224]],["curveTo",[103.583369,653.65855,137.566567,671.500214,180.499786,671.500214]],["curveTo",[224.066353,671.500214,258.207888,653.65855,282.924391,617.975224]],["curveTo",[307.640894,582.291898,319.999146,531.466894,319.999146,465.500214]],["curveTo",[319.999146,420.900185,314.407518,383.100171,303.224263,352.100171]],["curveTo",[292.041008,321.100171,275.99943,297.583511,255.09953,281.550192]],["curveTo",[234.19963,265.516873,209.333049,257.500214,180.499786,257.500214]],["closePath",[]],["moveTo",[180.499786,286.898761]],["curveTo",[212.833547,286.898761,238.43386,302.657269,257.300726,334.174284]],["curveTo",[276.167592,365.6913,285.601025,409.46661,285.601025,465.500214]],["curveTo",[285.601025,522.167165,276.167592,565.767478,257.300726,596.301153]],["curveTo",[238.43386,626.834828,212.833547,642.101666,180.499786,642.101666]],["curveTo",[148.832693,642.101666,123.399046,626.834828,104.198847,596.301153]],["curveTo",[84.998647,565.767478,75.398548,522.167165,75.398548,465.500214]],["curveTo",[75.398548,409.46661,84.998647,365.6913,104.198847,334.174284]],["curveTo",[123.399046,302.657269,148.832693,286.898761,180.499786,286.898761]],["closePath",[]],["moveTo",[196.899616,-12.0]],["lineTo",[565.800085,671.500214]],["lineTo",[600.998718,671.500214]],["lineTo",[232.098249,-12.0]],["closePath",[]],["moveTo",[621.098676,-12.0]],["curveTo",[592.265414,-12.0,567.398832,-3.98334,546.498932,12.049979]],["curveTo",[525.599032,28.083298,509.557454,51.599957,498.374199,82.599957]],["curveTo",[487.190944,113.599957,481.599317,151.399972,481.599317,196.0]],["curveTo",[481.599317,261.966681,494.115905,312.791684,519.149082,348.475011]],["curveTo",[544.182258,384.158337,578.165456,402.0,621.098676,402.0]],["curveTo",[664.665243,402.0,698.806778,384.158337,723.523281,348.475011]],["curveTo",[748.239784,312.791684,760.598035,261.966681,760.598035,196.0]],["curveTo",[760.598035,151.399972,755.006408,113.599957,743.823152,82.599957]],["curveTo",[732.639897,51.599957,716.59832,28.083298,695.698419,12.049979]],["curveTo",[674.798519,-3.98334,649.931938,-12.0,621.098676,-12.0]],["closePath",[]],["moveTo",[621.098676,17.398548]],["curveTo",[653.432436,17.398548,679.03275,33.157055,697.899616,64.674071]],["curveTo",[716.766482,96.191086,726.199915,139.966396,726.199915,196.0]],["curveTo",[726.199915,252.666951,716.766482,296.267265,697.899616,326.80094]],["curveTo",[679.03275,357.334615,653.432436,372.601452,621.098676,372.601452]],["curveTo",[589.431582,372.601452,563.997935,357.334615,544.797736,326.80094]],["curveTo",[525.597537,296.267265,515.997437,252.666951,515.997437,196.0]],["curveTo",[515.997437,139.966396,525.597537,96.191086,544.797736,64.674071]],["curveTo",[563.997935,33.157055,589.431582,17.398548,621.098676,17.398548]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]],["moveTo",[164.599744,726.300726]],["curveTo",[155.233376,726.300726,147.175281,729.367293,140.425459,735.500427]],["curveTo",[133.675637,741.633561,130.300726,750.0,130.300726,760.599744]],["curveTo",[130.300726,771.199487,133.675637,779.565926,140.425459,785.69906]],["curveTo",[147.175281,791.832194,155.233376,794.898761,164.599744,794.898761]],["curveTo",[174.632778,794.898761,182.85754,791.832194,189.274028,785.69906]],["curveTo",[195.690517,779.565926,198.898761,771.199487,198.898761,760.599744]],["curveTo",[198.898761,750.0,195.690517,741.633561,189.274028,735.500427]],["curveTo",[182.85754,729.367293,174.632778,726.300726,164.599744,726.300726]],["closePath",[]],["moveTo",[358.398975,726.300726]],["curveTo",[349.032607,726.300726,340.974512,729.367293,334.22469,735.500427]],["curveTo",[327.474868,741.633561,324.099957,750.0,324.099957,760.599744]],["curveTo",[324.099957,771.199487,327.474868,779.565926,334.22469,785.69906]],["curveTo",[340.974512,791.832194,349.032607,794.898761,358.398975,794.898761]],["curveTo",[368.432009,794.898761,376.656771,791.832194,383.073259,785.69906]],["curveTo",[389.489748,779.565926,392.697992,771.199487,392.697992,760.599744]],["curveTo",[392.697992,750.0,389.489748,741.633561,383.073259,735.500427]],["curveTo",[376.656771,729.367293,368.432009,726.300726,358.398975,726.300726]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[272.999614,-12]],["curveTo",[232.499807,-12.0,195.583301,-1.986116,162.250097,18.041651]],["curveTo",[128.916892,38.069418,102.347539,66.791618,82.542037,104.208253]],["curveTo",[62.736535,141.624887,52.833784,186.444273,52.833784,238.666409]],["curveTo",[52.833784,290.777477,62.764302,335.694112,82.625338,373.416312]],["curveTo",[102.486374,411.138513,128.222394,440.180164,159.833398,460.541264]],["curveTo",[191.444402,480.902364,225.055405,491.082915,260.666409,491.082915]],["curveTo",[297.999485,491.082915,330.290974,482.457963,357.540878,465.20806]],["curveTo",[384.790781,447.958156,405.762949,422.902687,420.457383,390.041651]],["curveTo",[435.151817,357.180615,442.499034,317.166731,442.499034,270]],["curveTo",[442.499034,264.833527,442.457383,259.389254,442.374082,253.667182]],["curveTo",[442.290781,247.94511,441.554697,241.945239,440.165829,235.667568]],["lineTo",[74.664863,235.667568]],["lineTo",[74.664863,268.665636]],["lineTo",[406.501353,268.665636]],["curveTo",[406.501353,332.111068,393.306726,379.500644,366.917472,410.834364]],["curveTo",[340.528218,442.168084,305.166731,457.834944,260.833011,457.834944]],["curveTo",[233.610488,457.834944,207.040749,449.932064,181.123792,434.126304]],["curveTo",[155.206836,418.320545,133.817711,394.29252,116.956417,362.04223]],["curveTo",[100.095123,329.79194,91.664477,289.277799,91.664477,240.499807]],["curveTo",[91.664477,195.721814,99.706192,156.888223,115.789621,123.999034]],["curveTo",[131.873051,91.109844,153.970429,65.776253,182.081755,47.998261]],["curveTo",[210.193081,30.220268,241.498905,21.331272,275.999227,21.331272]],["curveTo",[302.832754,21.331272,327.124565,24.942405,348.874662,32.16467]],["curveTo",[370.624758,39.386935,389.83314,48.942533,406.499807,60.831465]],["lineTo",[422.665636,34.499807]],["curveTo",[404.276897,22.166602,383.638191,11.333333,360.749517,2.0]],["curveTo",[337.860843,-7.333333,308.610875,-12.0,272.999614,-12]],["closePath",[]],["moveTo",[189.500214,570.0]],["lineTo",[328.000854,721.999146]],["lineTo",[373.097821,721.999146]],["lineTo",[375.497651,717.299445]],["lineTo",[224.498078,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[9.000386,0]],["lineTo",[243.084461,659.500193]],["lineTo",[280.080982,659.500193]],["lineTo",[514.165056,0]],["lineTo",[472.834557,0]],["lineTo",[338,392.001546]],["curveTo",[324.27767,431.557273,311.360907,469.515687,299.24971,505.876788]],["curveTo",[287.138513,542.237888,275.193983,580.557402,263.416119,620.83533]],["lineTo",[259.416119,620.83533]],["curveTo",[247.69379,580.501868,235.777026,542.16847,223.665829,505.835137]],["curveTo",[211.554632,469.501804,198.637869,431.557273,184.915539,392.001546]],["lineTo",[48.164283,0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[112.999614,264.333205]],["lineTo",[407.24913,264.333205]],["lineTo",[407.24913,231.085234]],["closePath",[]],["moveTo",[261.4827,699.800085]],["curveTo",[233.927524,699.800085,210.99167,707.391713,192.675139,722.574968]],["curveTo",[174.358607,737.758223,165.200342,758.474797,165.200342,784.72469]],["curveTo",[165.200342,810.974584,174.358607,831.682828,192.675139,846.849423]],["curveTo",[210.99167,862.016019,233.927524,869.599317,261.4827,869.599317]],["curveTo",[289.726755,869.599317,312.840382,862.018994,330.82358,846.858348]],["curveTo",[348.806778,831.697702,357.798377,810.978153,357.798377,784.699701]],["curveTo",[357.798377,758.466467,348.806778,737.758223,330.82358,722.574968]],["curveTo",[312.840382,707.391713,289.726755,699.800085,261.4827,699.800085]],["closePath",[]],["moveTo",[261.483577,723.699274]],["curveTo",[280.460922,723.699274,296.291328,728.807632,308.974797,739.024349]],["curveTo",[321.658266,749.241065,328.0,764.461423,328.0,784.685421]],["curveTo",[328.0,805.561807,321.658266,820.950021,308.974797,830.850064]],["curveTo",[296.291328,840.750107,280.460922,845.700128,261.483577,845.700128]],["curveTo",[243.160623,845.700128,227.499074,840.750107,214.498932,830.850064]],["curveTo",[201.49879,820.950021,194.998718,805.561807,194.998718,784.685421]],["curveTo",[194.998718,764.461423,201.49879,749.241065,214.498932,739.024349]],["curveTo",[227.499074,728.807632,243.160623,723.699274,261.483577,723.699274]],["closePath",[]]]}]},{"coordinates":[777.0],"glyphs":[{"char":"A","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[75.03125,0]],["lineTo",[75.03125,651.082589]],["lineTo",[487.886161,651.082589]],["lineTo",[487.886161,519.640624]],["lineTo",[231.875001,519.640624]],["lineTo",[231.875001,399.886161]],["lineTo",[450.154018,399.886161]],["lineTo",[450.154018,268.444196]],["lineTo",[231.875001,268.444196]],["lineTo",[231.875001,131.441965]],["lineTo",[497.886161,131.441965]],["lineTo",[497.886161,0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[343.564323,-12]],["curveTo",[284.466426,-12.0,232.521441,1.59021,187.729369,28.770631]],["curveTo",[142.937297,55.951051,108.00991,94.9197,82.947208,145.676577]],["curveTo",[57.884505,196.433454,45.353154,257.388349,45.353154,328.541262]],["curveTo",[45.353154,399.694174,57.884505,460.149069,82.947208,509.905946]],["curveTo",[108.00991,559.662823,142.937297,597.631472,187.729369,623.811892]],["curveTo",[232.521441,649.992313,284.466426,663.082523,343.564323,663.082523]],["curveTo",[402.842641,663.082523,454.877836,649.992313,499.669908,623.811892]],["curveTo",[544.461979,597.631472,579.389366,559.662823,604.452069,509.905946]],["curveTo",[629.514772,460.149069,642.046123,399.694174,642.046123,328.541262]],["curveTo",[642.046123,257.388349,629.514772,196.433454,604.452069,145.676577]],["curveTo",[579.389366,94.9197,544.461979,55.951051,499.669908,28.770631]],["curveTo",[454.877836,1.59021,402.842641,-12.0,343.564323,-12]],["closePath",[]],["moveTo",[343.564323,123.280353]],["curveTo",[371.90979,123.280353,396.394415,131.654948,417.0182,148.404138]],["curveTo",[437.641985,165.153328,453.495139,188.898674,464.577662,219.640177]],["curveTo",[475.660185,250.381679,481.201447,286.682041,481.201447,328.541262]],["curveTo",[481.201447,370.400482,475.660185,406.155739,464.577662,435.807031]],["curveTo",[453.495139,465.458323,437.641985,488.20367,417.0182,504.04307]],["curveTo",[396.394415,519.88247,371.90979,527.80217,343.564323,527.80217]],["curveTo",[315.399277,527.80217,290.959756,519.88247,270.245761,504.04307]],["curveTo",[249.531766,488.20367,233.633507,465.458323,222.550984,435.807031]],["curveTo",[211.468461,406.155739,205.927199,370.400482,205.927199,328.541262]],["curveTo",[205.927199,286.682041,211.468461,250.381679,222.550984,219.640177]],["curveTo",[233.633507,188.898674,249.531766,165.153328,270.245761,148.404138]],["curveTo",[290.959756,131.654948,315.399277,123.280353,343.564323,123.280353]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[276.917411,-12]],["curveTo",[233.601191,-12.0,190.803944,-4.283482,148.52567,11.149554]],["curveTo",[106.247396,26.582589,67.982887,50.415923,33.732143,82.649554]],["lineTo",[116.370536,184.689733]],["curveTo",[139.70387,165.084822,166.010603,149.939547,195.290737,139.253907]],["curveTo",[224.570871,128.568267,252.96875,123.225448,280.484375,123.225448]],["curveTo",[309.443452,123.225448,330.82087,128.217821,344.616629,138.202568]],["curveTo",[358.412388,148.187315,365.310267,161.99256,365.310267,179.618304]],["curveTo",[365.310267,192.305804,361.575706,202.429687,354.106584,209.989955]],["curveTo",[346.637462,217.550223,336.114211,224.470424,322.53683,230.750558]],["curveTo",[308.959449,237.030691,292.656994,243.98177,273.629464,251.603794]],["lineTo",[190.356026,286.01116]],["curveTo",[167.455729,295.866815,145.969494,308.780877,125.897321,324.753348]],["curveTo",[105.825148,340.725818,89.66276,360.382998,77.410156,383.724888]],["curveTo",[65.157552,407.066778,59.03125,434.440476,59.03125,465.845982]],["curveTo",[59.03125,502.529761,69.111979,535.677455,89.273437,565.289062]],["curveTo",[109.434896,594.900669,137.231213,618.602492,172.662388,636.394531]],["curveTo",[208.093564,654.18657,248.862351,663.082589,294.96875,663.082589]],["curveTo",[334.374256,663.082589,372.531436,656.140997,409.440291,642.257812]],["curveTo",[446.349145,628.374628,478.892858,608.127232,507.071429,581.515625]],["lineTo",[424.866071,479.475445]],["curveTo",[403.082589,495.313987,381.235491,507.337238,359.324777,515.5452]],["curveTo",[337.414063,523.753161,315.962054,527.857142,294.96875,527.857142]],["curveTo",[271.559524,527.857142,252.822173,523.342447,238.756697,514.313057]],["curveTo",[224.691221,505.283667,217.658483,492.172618,217.658483,474.97991]],["curveTo",[217.658483,463.103422,221.762464,453.123884,229.970425,445.041295]],["curveTo",[238.178386,436.958705,249.814175,429.831473,264.877791,423.659598]],["curveTo",[279.941407,417.487723,296.923364,410.437872,315.823662,402.510045]],["lineTo",[397.313617,369.886161]],["curveTo",[424.430432,359.147322,447.466519,345.737538,466.421876,329.656808]],["curveTo",[485.377233,313.576079,499.999257,294.274554,510.287947,271.752232]],["curveTo",[520.576637,249.229911,525.720983,222.752232,525.720983,192.319197]],["curveTo",[525.720983,156.013393,516.050038,122.315848,496.708148,91.226563]],["curveTo",[477.366258,60.137277,449.074219,35.160528,411.832032,16.296317]],["curveTo",[374.589844,-2.567894,329.618304,-12.0,276.917411,-12]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[189.082523,-12]],["curveTo",[158.470872,-12.0,132.043485,-5.149069,109.800362,8.552792]],["curveTo",[87.557239,22.254654,70.390572,40.546726,58.300362,63.429008]],["curveTo",[46.210151,86.31129,40.165046,111.599518,40.165046,139.293692]],["curveTo",[40.165046,192.195795,61.994536,233.299759,105.653516,262.605585]],["curveTo",[149.312495,291.91141,219.773457,311.654533,317.0364,321.834954]],["curveTo",[315.883487,335.097897,312.595259,346.4197,307.171716,355.800362]],["curveTo",[301.748172,365.181023,293.630454,372.336159,282.818562,377.265769]],["curveTo",[272.006669,382.19538,258.275077,384.660185,241.623785,384.660185]],["curveTo",[222.0,384.660185,201.782161,381.177782,180.970269,374.212977]],["curveTo",[160.158377,367.248172,137.293692,356.85598,112.376215,343.0364]],["lineTo",[57.247569,444.376215]],["curveTo",[79.137539,457.654533,101.721682,469.248587,125.0,479.158377]],["curveTo",[148.278318,489.068167,172.1606,496.613272,196.646846,501.793692]],["curveTo",[221.133092,506.974113,246.376215,509.564323,272.376215,509.564323]],["curveTo",[314.987867,509.564323,351.252431,501.397656,381.169908,485.064323]],["curveTo",[411.087384,468.73099,433.97351,443.758497,449.828284,410.146846]],["curveTo",[465.683059,376.535195,473.610446,333.568769,473.610446,281.247569]],["lineTo",[473.610446,0]],["lineTo",[345.788831,0]],["lineTo",[334.871354,49.623785]],["lineTo",[330.871354,49.623785]],["curveTo",[310.149672,31.443364,288.306428,16.627628,265.341623,5.176577]],["curveTo",[242.376818,-6.274474,216.957118,-12.0,189.082523,-12]],["closePath",[]],["moveTo",[242.587384,109.71603]],["curveTo",[258.211169,109.71603,271.727146,113.153328,283.135315,120.027923]],["curveTo",[294.543485,126.902518,305.843846,136.241918,317.0364,148.046123]],["lineTo",[317.0364,225.788831]],["curveTo",[285.012133,221.42799,259.768408,215.643605,241.305223,208.435677]],["curveTo",[222.842038,201.227749,209.690746,192.794295,201.851346,183.135315]],["curveTo",[194.011946,173.476336,190.092246,162.987867,190.092246,151.669908]],["curveTo",[190.092246,137.810687,194.813928,127.353569,204.257292,120.298553]],["curveTo",[213.700656,113.243538,226.477354,109.71603,242.587384,109.71603]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[288.376215,-12]],["curveTo",[240.431231,-12.0,197.229369,-1.711772,158.770631,18.864685]],["curveTo",[120.311892,39.441141,89.92961,69.139159,67.623785,107.958738]],["curveTo",[45.317959,146.778318,34.165046,193.764564,34.165046,248.917477]],["curveTo",[34.165046,302.737056,45.529731,349.056636,68.2591,387.876215]],["curveTo",[90.988469,426.695795,120.415856,456.682041,156.541262,477.834954]],["curveTo",[192.666667,498.987867,230.792072,509.564323,270.917477,509.564323]],["curveTo",[318.987867,509.564323,358.873992,498.956515,390.575854,477.7409]],["curveTo",[422.277715,456.525285,445.871769,427.809669,461.358015,391.594054]],["curveTo",[476.844261,355.378438,484.587384,314.847087,484.587384,270]],["curveTo",[484.587384,256.415856,483.844261,243.286608,482.358015,230.612254]],["curveTo",[480.871769,217.9379,479.371769,208.149672,477.858015,201.247569]],["lineTo",[156.633507,201.247569]],["lineTo",[155.174769,306.752431]],["lineTo",[350.495139,306.752431]],["curveTo",[350.495139,330.415856,344.804808,350.226128,333.424146,366.183246]],["curveTo",[322.043485,382.140364,302.360841,390.118923,274.376215,390.118923]],["curveTo",[259.113272,390.118923,244.107205,385.969854,229.358015,377.671716]],["curveTo",[214.608825,369.373577,202.494951,354.926369,193.016392,334.330092]],["curveTo",[183.537833,313.733816,179.2848,285.262944,180.257292,248.917477]],["curveTo",[181.229784,211.709549,188.139574,182.883902,200.986661,162.440538]],["curveTo",[213.833748,141.997174,229.920115,127.765582,249.245761,119.745761]],["curveTo",[268.571407,111.725941,288.71279,107.71603,309.669908,107.71603]],["curveTo",[328.085764,107.71603,346.064323,110.351346,363.605585,115.621977]],["curveTo",[381.146846,120.892607,398.737056,128.618133,416.376215,138.798553]],["lineTo",[466.587384,44.376215]],["curveTo",[441.003241,26.431231,412.130869,12.548949,379.970269,2.729369]],["curveTo",[347.809669,-7.09021,317.278318,-12.0,288.376215,-12]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[241.082589,-209.03125]],["curveTo",[202.793899,-209.03125,167.793899,-204.702195,136.082589,-196.044085]],["curveTo",[104.37128,-187.385975,79.196056,-173.948661,60.556919,-155.732143]],["curveTo",[41.917783,-137.515625,32.598214,-113.587797,32.598214,-83.948661]],["curveTo",[32.598214,-63.209821,38.652344,-44.736421,50.760602,-28.52846]],["curveTo",[62.868861,-12.320498,80.220238,2.0,102.814732,14.433036]],["lineTo",[102.814732,18.433036]],["curveTo",[90.220238,27.549851,79.47712,38.887463,70.585379,52.445871]],["curveTo",[61.693638,66.004278,57.247768,82.97247,57.247768,103.350446]],["curveTo",[57.247768,121.188988,62.522693,138.351376,73.072544,154.837612]],["curveTo",[83.622395,171.323847,96.869791,185.278274,112.814732,196.700893]],["lineTo",[112.814732,200.700893]],["curveTo",[95.175595,212.845238,79.576823,230.120164,66.018415,252.52567]],["curveTo",[52.460007,274.931176,45.680803,300.467262,45.680803,329.133929]],["curveTo",[45.680803,369.456845,55.540736,403.028088,75.260602,429.847656]],["curveTo",[94.980469,456.667225,120.740699,476.667225,152.541295,489.847656]],["curveTo",[184.34189,503.028088,218.250744,509.618304,254.267857,509.618304]],["curveTo",[268.195685,509.618304,281.830543,508.600261,295.172433,506.564174]],["curveTo",[308.514323,504.528088,320.896578,501.546131,332.319197,497.618304]],["lineTo",[515.504465,497.618304]],["lineTo",[515.504465,384.01116]],["lineTo",[435.174108,384.01116]],["lineTo",[435.174108,380.01116]],["curveTo",[441.067709,371.578124,445.371094,362.675967,448.084264,353.304687]],["curveTo",[450.797434,343.933407,452.154018,333.498512,452.154018,322]],["curveTo",[452.154018,284.055059,443.33445,252.992373,425.695313,228.811942]],["curveTo",[408.056176,204.63151,384.228051,186.676153,354.210938,174.945871]],["curveTo",[324.193825,163.215588,290.879465,157.350446,254.267857,157.350446]],["curveTo",[245.618304,157.350446,236.486793,158.012835,226.873326,159.337612]],["curveTo",[217.259859,160.662388,206.786459,162.874628,195.453125,165.97433]],["curveTo",[190.136905,160.874628,186.266556,156.058408,183.842076,151.52567]],["curveTo",[181.417597,146.992932,180.205358,140.518601,180.205358,132.102679]],["curveTo",[180.205358,120.642114,185.813617,112.127419,197.030135,106.558594]],["curveTo",[208.246652,100.98977,226.837798,98.205358,252.803572,98.205358]],["lineTo",[331.159598,98.205358]],["curveTo",[392.815848,98.205358,440.287389,88.20108,473.574219,68.192523]],["curveTo",[506.86105,48.183966,523.504465,15.64881,523.504465,-29.412946]],["curveTo",[523.504465,-64.402529,511.977865,-95.401599,488.924666,-122.410156]],["curveTo",[465.871466,-149.418713,433.237538,-170.607701,391.02288,-185.97712]],["curveTo",[348.808222,-201.34654,298.828125,-209.03125,241.082589,-209.03125]],["closePath",[]],["moveTo",[254.267857,251.339286]],["curveTo",[266.305804,251.339286,276.97433,254.218193,286.273437,259.976005]],["curveTo",[295.572544,265.733818,302.970424,274.374814,308.467075,285.898996]],["curveTo",[313.963727,297.423177,316.712053,311.834822,316.712053,329.133929]],["curveTo",[316.712053,354.63244,310.845982,373.639509,299.113839,386.155134]],["curveTo",[287.381696,398.670758,272.433036,404.928571,254.267857,404.928571]],["curveTo",[236.102679,404.928571,221.154018,398.670758,209.421876,386.155134]],["curveTo",[197.689733,373.639509,191.823662,354.63244,191.823662,329.133929]],["curveTo",[191.823662,311.834822,194.571987,297.423177,200.068639,285.898996]],["curveTo",[205.565291,274.374814,212.96317,265.733818,222.262277,259.976005]],["curveTo",[231.561384,254.218193,242.229911,251.339286,254.267857,251.339286]],["closePath",[]],["moveTo",[266.267857,-111.475445]],["curveTo",[287.02753,-111.475445,305.444382,-109.154947,321.518415,-104.51395]],["curveTo",[337.592447,-99.872953,350.32366,-93.348772,359.712053,-84.941406]],["curveTo",[369.100446,-76.53404,373.794642,-67.014137,373.794642,-56.381696]],["curveTo",[373.794642,-41.460565,367.528273,-31.864211,354.995535,-27.592634]],["curveTo",[342.462797,-23.321057,324.627604,-21.185268,301.489955,-21.185268]],["lineTo",[254.803572,-21.185268]],["curveTo",[236.68006,-21.185268,222.397508,-20.667225,211.955915,-19.631139]],["curveTo",[201.514323,-18.595052,192.302084,-17.040923,184.319197,-14.96875]],["curveTo",[176.370536,-22.051339,170.481213,-28.99907,166.651228,-35.811942]],["curveTo",[162.821243,-42.624814,160.906251,-50.275297,160.906251,-58.763392]],["curveTo",[160.906251,-76.495535,170.650298,-89.713913,190.138394,-98.418526]],["curveTo",[209.626489,-107.123139,235.002977,-111.475445,266.267857,-111.475445]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[211.566964,-12]],["curveTo",[178.917411,-12.0,145.120164,-5.554129,110.175223,7.337612]],["curveTo",[75.230283,20.229353,45.082589,38.755952,19.732143,62.917411]],["lineTo",[85.886161,150.689733]],["curveTo",[108.858631,132.834078,131.435082,120.48698,153.615514,113.648438]],["curveTo",[175.795945,106.809897,196.374256,103.390626,215.350446,103.390626]],["curveTo",[235.732143,103.390626,250.460007,106.738654,259.53404,113.434711]],["curveTo",[268.608072,120.130767,273.145089,129.380953,273.145089,141.185268]],["curveTo",[273.145089,151.223214,268.910528,159.675223,260.441406,166.541295]],["curveTo",[251.972284,173.407366,240.651785,179.574963,226.47991,185.044085]],["curveTo",[212.308035,190.513207,196.186012,196.581101,178.113839,203.247768]],["curveTo",[154.63616,211.914434,133.014137,222.918713,113.247768,236.260602]],["curveTo",[93.481399,249.602492,77.584449,265.795759,65.556919,284.840402]],["curveTo",[53.52939,303.885045,47.515625,326.182292,47.515625,351.732143]],["curveTo",[47.515625,399.333333,65.253534,437.547991,100.729353,466.376116]],["curveTo",[136.205171,495.204241,182.735119,509.618304,240.319197,509.618304]],["curveTo",[277.02009,509.618304,310.568081,503.632069,340.96317,491.659598]],["curveTo",[371.35826,479.687128,397.267114,464.322917,418.689733,445.566964]],["lineTo",[352.535715,357.794642]],["curveTo",[334.157738,370.911458,315.96875,380.262834,297.96875,385.848772]],["curveTo",[279.96875,391.434709,262.463542,394.227678,245.453125,394.227678]],["curveTo",[228.043899,394.227678,215.059153,391.082403,206.498885,384.791852]],["curveTo",[197.938617,378.501302,193.658483,369.931547,193.658483,359.082589]],["curveTo",[193.658483,350.450149,196.762464,343.088356,202.970425,336.99721]],["curveTo",[209.178386,330.906064,218.998885,325.18006,232.431921,319.819197]],["curveTo",[245.864956,314.458334,263.139882,308.408483,284.256697,301.669643]],["curveTo",[309.590031,293.453125,332.509301,282.691965,353.01451,269.386161]],["curveTo",[373.519718,256.080357,389.691593,239.702381,401.530135,220.252232]],["curveTo",[413.368676,200.802084,419.287947,177.491072,419.287947,150.319197]],["curveTo",[419.287947,119.851935,411.355841,92.388951,395.49163,67.930246]],["curveTo",[379.627419,43.47154,356.313058,24.035156,325.548549,9.621094]],["curveTo",[294.78404,-4.792969,256.790179,-12.0,211.566964,-12]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[252.535715,-12]],["curveTo",[204.934524,-12.0,163.994792,-4.238839,129.716518,11.283482]],["curveTo",[95.438244,26.805804,69.204613,48.643415,51.015625,76.796317]],["curveTo",[32.826637,104.949219,23.732143,137.65625,23.732143,174.917411]],["curveTo",[23.732143,210.862351,31.047433,241.429316,45.678013,266.618304]],["curveTo",[60.308594,291.807292,79.236421,313.212798,102.461495,330.834822]],["curveTo",[125.68657,348.456845,150.263021,364.056548,176.190848,377.633929]],["curveTo",[202.118675,391.21131,226.695126,404.360863,249.920201,417.082589]],["curveTo",[273.145275,429.804315,292.073103,443.823288,306.703683,459.139509]],["curveTo",[321.334263,474.455729,328.649554,492.402529,328.649554,512.97991]],["curveTo",[328.649554,527.845982,325.21224,539.428571,318.337612,547.727678]],["curveTo",[311.462984,556.026785,300.862351,560.176338,286.535715,560.176338]],["curveTo",[271.920387,560.176338,259.067151,554.211495,247.976005,542.281807]],["curveTo",[236.884859,530.35212,231.339286,513.179315,231.339286,490.763392]],["curveTo",[231.339286,465.014137,239.267114,437.877418,255.122769,409.353236]],["curveTo",[270.978423,380.829055,292.243862,352.453497,318.919086,324.226563]],["curveTo",[345.594309,295.999628,375.251489,269.1782,407.890626,243.762277]],["curveTo",[440.529763,218.346355,473.335566,195.313617,506.308037,174.664063]],["curveTo",[534.019346,157.420016,561.937687,143.973215,590.063059,134.323662]],["curveTo",[618.188431,124.674108,640.503721,117.930061,657.00893,114.091519]],["lineTo",[620.040179,-12]],["curveTo",[586.889138,-5.783482,552.629837,4.84282,517.262277,19.878906]],["curveTo",[481.894718,34.914993,446.977307,53.541295,412.510045,75.757812]],["curveTo",[370.99814,102.640997,331.47247,133.190848,293.933036,167.407366]],["curveTo",[256.393601,201.623884,223.173735,237.543155,194.273437,275.165178]],["curveTo",[165.37314,312.787202,142.679873,349.963355,126.193638,386.693638]],["curveTo",[109.707403,423.423921,101.464285,458.041666,101.464285,490.546875]],["curveTo",[101.464285,521.519345,108.896391,550.068266,123.760602,576.193638]],["curveTo",[138.624814,602.31901,159.605841,623.331845,186.703683,639.232143]],["curveTo",[213.801525,655.13244,245.962054,663.082589,283.185268,663.082589]],["curveTo",[333.236608,663.082589,373.189175,649.555989,403.042969,622.50279]],["curveTo",[432.896764,595.449591,447.823662,559.014137,447.823662,513.196428]],["curveTo",[447.823662,484.079613,441.120909,458.354538,427.715403,436.021205]],["curveTo",[414.309897,413.687872,397.183595,393.597656,376.336496,375.750558]],["curveTo",[355.489398,357.903459,333.290924,341.430059,309.741072,326.330357]],["curveTo",[286.191221,311.230654,263.992747,296.540736,243.145648,282.260602]],["curveTo",[222.29855,267.980469,205.172248,252.943452,191.766742,237.149554]],["curveTo",[178.361236,221.355655,171.658483,203.65625,171.658483,184.051339]],["curveTo",[171.658483,166.463542,175.704056,151.984003,183.795202,140.612724]],["curveTo",[191.886348,129.241444,202.860678,120.789435,216.718193,115.256697]],["curveTo",[230.575707,109.723959,246.154018,106.95759,263.453125,106.95759]],["curveTo",[285.869048,106.95759,308.514323,112.894904,331.388951,124.769532]],["curveTo",[354.263579,136.64416,376.106399,152.239584,396.917411,171.555804]],["curveTo",[422.539435,196.421876,445.332403,225.472657,465.296317,258.708148]],["curveTo",[485.260231,291.943639,500.68378,329.219494,511.566964,370.535715]],["lineTo",[653.926341,370.535715]],["curveTo",[641.115329,322.463542,622.129093,275.963542,596.967635,231.035715]],["curveTo",[571.806177,186.107887,540.991817,143.040923,504.524555,101.834822]],["curveTo",[474.507441,67.889881,439.445685,40.431176,399.339286,19.458705]],["curveTo",[359.232888,-1.513765,310.298364,-12.0,252.535715,-12]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[432.046123,-174.211169]],["curveTo",[381.740297,-174.211169,333.614892,-166.544502,287.669908,-151.211169]],["curveTo",[241.724923,-135.877836,200.779938,-112.589608,164.834954,-81.346484]],["curveTo",[128.889969,-50.103361,100.521441,-10.630869,79.729369,37.070992]],["curveTo",[58.937297,84.772854,48.541262,140.902103,48.541262,205.458738]],["curveTo",[48.541262,276.07039,60.527508,339.938918,84.5,397.064323]],["curveTo",[108.472492,454.189728,141.431231,503.360238,183.376215,544.575854]],["curveTo",[225.3212,585.791469,273.797536,617.430628,328.805223,639.493331]],["curveTo",[383.81291,661.556033,442.351948,672.587384,504.422338,672.587384]],["curveTo",[577.339815,672.587384,640.63573,657.236454,694.310084,626.534592]],["curveTo",[747.984438,595.832731,789.429423,553.432851,818.645038,499.334954]],["curveTo",[847.860653,445.237056,862.468461,382.972492,862.468461,312.541262]],["curveTo",[862.468461,266.235436,855.52732,225.794295,841.645038,191.217839]],["curveTo",[827.762756,156.641382,809.515789,127.853154,786.904138,104.853154]],["curveTo",[764.292487,81.853154,739.347502,64.564926,712.069184,52.988469]],["curveTo",[684.790866,41.412013,657.755672,35.623785,630.9636,35.623785]],["curveTo",[601.810687,35.623785,576.338195,42.290451,554.546123,55.623785]],["curveTo",[532.754051,68.957118,519.128646,87.984626,513.669908,112.706308]],["lineTo",[511.669908,112.706308]],["curveTo",[498.6424,91.984626,480.430628,75.533574,457.034592,63.353154]],["curveTo",[433.638556,51.172733,411.669908,45.082523,391.128646,45.082523]],["curveTo",[351.364082,45.082523,319.026905,59.023664,294.117115,86.905946]],["curveTo",[269.207326,114.788228,256.752431,151.819579,256.752431,198]],["curveTo",[256.752431,229.278318,261.995554,259.691951,272.4818,289.2409]],["curveTo",[282.968046,318.789849,297.575854,345.276095,316.305223,368.699638]],["curveTo",[335.034592,392.123182,356.73261,410.834954,381.399277,424.834954]],["curveTo",[406.065943,438.834954,432.82282,445.834954,461.669908,445.834954]],["curveTo",[478.030749,445.834954,492.589608,441.91141,505.346484,434.064323]],["curveTo",[518.103361,426.217236,528.57201,413.293692,536.752431,395.293692]],["lineTo",[538.752431,395.293692]],["lineTo",[553.940538,437.293692]],["lineTo",[649.550984,437.293692]],["lineTo",[603.174769,213.623785]],["curveTo",[596.994348,186.25081,597.539453,165.99171,604.810084,152.846484]],["curveTo",[612.080715,139.701259,626.076871,133.128646,646.798553,133.128646]],["curveTo",[666.618133,133.128646,685.239695,140.354172,702.663238,154.805223]],["curveTo",[720.086782,169.256274,734.222097,189.675974,745.069184,216.064323]],["curveTo",[755.916271,242.452672,761.339815,273.278318,761.339815,308.541262]],["curveTo",[761.339815,343.082523,756.416271,376.308049,746.569184,408.217839]],["curveTo",[736.722097,440.127628,721.208343,468.568769,700.027923,493.541262]],["curveTo",[678.847502,518.513754,651.149484,538.319579,616.933869,552.958738]],["curveTo",[582.718254,567.597897,540.998795,574.917477,491.775492,574.917477]],["curveTo",[449.775492,574.917477,408.820597,566.295915,368.910807,549.052792]],["curveTo",[329.001018,531.809669,293.091228,507.188108,261.181438,475.188108]],["curveTo",[229.271648,443.188108,203.861859,404.733213,184.952069,359.823423]],["curveTo",[166.042279,314.913633,156.587384,264.792072,156.587384,209.458738]],["curveTo",[156.587384,160.847087,164.406964,118.586367,180.046123,82.676577]],["curveTo",[195.685282,46.766787,216.946002,17.00991,243.828284,-6.594054]],["curveTo",[270.710566,-30.198018,301.439936,-47.756877,336.016392,-59.270631]],["curveTo",[370.592848,-70.784385,406.728164,-76.541262,444.422338,-76.541262]],["curveTo",[469.504861,-76.541262,495.577474,-73.086367,522.640177,-66.176577]],["curveTo",[549.702879,-59.266787,573.504861,-50.721682,594.046123,-40.541262]],["lineTo",[628.692969,-127.128646]],["curveTo",[598.026302,-143.128646,566.540056,-154.975733,534.234231,-162.669908]],["curveTo",[501.928405,-170.364082,467.865702,-174.211169,432.046123,-174.211169]],["closePath",[]],["moveTo",[425.610446,144.587384]],["curveTo",[435.665461,144.587384,445.612669,147.81291,455.452069,154.263961]],["curveTo",[465.291469,160.715013,476.085764,171.724923,487.834954,187.293692]],["lineTo",[510.917477,318.706308]],["curveTo",[506.682041,327.122164,500.748587,333.586969,493.117115,338.100723]],["curveTo",[485.485643,342.614477,476.73261,344.871354,466.858015,344.871354]],["curveTo",[448.261738,344.871354,432.179215,337.434057,418.610446,322.559462]],["curveTo",[405.041677,307.684867,394.653328,289.553395,387.4454,268.165046]],["curveTo",[380.237471,246.776697,376.633507,226.451051,376.633507,207.188108]],["curveTo",[376.633507,186.536815,380.949243,170.940538,389.580715,160.399277]],["curveTo",[398.212187,149.858015,410.222097,144.587384,425.610446,144.587384]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[264.96875,-12]],["curveTo",[222.735119,-12.0,184.686198,-4.63058,150.821987,10.108259]],["curveTo",[116.957775,24.847098,90.318638,45.252604,70.904576,71.324777]],["curveTo",[51.490513,97.396949,41.783482,127.694196,41.783482,162.216518]],["curveTo",[41.783482,187.388393,46.518043,209.429687,55.987165,228.340402]],["curveTo",[65.456287,247.251116,78.258742,263.904948,94.394531,278.301897]],["curveTo",[110.53032,292.698846,128.598214,305.230654,148.598214,315.897321]],["lineTo",[148.598214,319.897321]],["curveTo",[124.148065,338.797619,103.891183,360.247768,87.827567,384.247768]],["curveTo",[71.763951,408.247768,63.732143,436.653274,63.732143,469.464285]],["curveTo",[63.732143,506.00372,72.551711,537.574963,90.190848,564.178013]],["curveTo",[107.829985,590.781064,132.234561,611.177083,163.404576,625.366071]],["curveTo",[194.574591,639.555059,230.212798,646.649554,270.319197,646.649554]],["curveTo",[329.075149,646.649554,376.258929,630.81622,411.870536,599.149554]],["curveTo",[447.482143,567.482887,465.287947,525.010417,465.287947,471.732143]],["curveTo",[465.287947,451.893601,461.458892,433.388393,453.800782,416.216518]],["curveTo",[446.142672,399.044643,436.187315,383.742188,423.934711,370.309152]],["curveTo",[411.682106,356.876116,398.069569,345.159598,383.097099,335.159598]],["lineTo",[383.097099,331.159598]],["curveTo",[403.475075,320.420759,421.884859,307.456845,438.326452,292.267857]],["curveTo",[454.768044,277.078869,467.894346,259.038505,477.705358,238.146764]],["curveTo",[487.51637,217.255022,492.421876,192.773066,492.421876,164.700893]],["curveTo",[492.421876,131.061756,482.953684,100.99907,464.0173,74.512835]],["curveTo",[445.080916,48.0266,418.604167,26.986235,384.587054,11.391741]],["curveTo",[350.569941,-4.202753,310.697173,-12.0,264.96875,-12]],["closePath",[]],["moveTo",[304.051339,372.803572]],["curveTo",[315.549851,387.219494,324.209821,401.631139,330.03125,416.038505]],["curveTo",[335.852678,430.445871,338.763392,445.443452,338.763392,461.03125]],["curveTo",[338.763392,476.780506,335.956659,490.399181,330.343192,501.887276]],["curveTo",[324.729724,513.375371,316.494233,522.331658,305.636719,528.756138]],["curveTo",[294.779204,535.180617,281.817708,538.392856,266.752232,538.392856]],["curveTo",[248.96503,538.392856,233.552456,532.783667,220.51451,521.565289]],["curveTo",[207.476563,510.346912,200.95759,492.97991,200.95759,469.464285]],["curveTo",[200.95759,453.515625,205.183595,439.963914,213.635604,428.809152]],["curveTo",[222.087612,417.65439,234.035343,407.621652,249.478795,398.710938]],["curveTo",[264.922247,389.800224,283.113095,381.164435,304.051339,372.803572]],["closePath",[]],["moveTo",[268.535715,96.256697]],["curveTo",[283.295387,96.256697,296.563616,98.8564,308.340402,104.055804]],["curveTo",[320.117187,109.255209,329.53311,117.301712,336.588169,128.195313]],["curveTo",[343.643229,139.088914,347.170758,153.040923,347.170758,170.051339]],["curveTo",[347.170758,187.477679,341.944754,201.970982,331.492745,213.53125]],["curveTo",[321.040736,225.091517,306.241629,235.511718,287.095424,244.791852]],["curveTo",[267.949219,254.071986,245.240328,264.189732,218.96875,275.145089]],["curveTo",[206.892858,263.468005,196.768044,249.277157,188.594309,232.572544]],["curveTo",[180.420574,215.867931,176.333706,198.360863,176.333706,180.051339]],["curveTo",[176.333706,162.229911,180.527903,147.057106,188.916296,134.532925]],["curveTo",[197.304688,122.008743,208.517858,112.498327,222.555804,106.001675]],["curveTo",[236.593751,99.505023,251.920387,96.256697,268.535715,96.256697]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[188.917411,249.082589]],["curveTo",[157.278274,249.082589,129.044643,257.379836,104.216518,273.97433]],["curveTo",[79.388393,290.568824,59.839472,314.366071,45.569754,345.366071]],["curveTo",[31.300037,376.366071,24.165178,413.604911,24.165178,457.082589]],["curveTo",[24.165178,522.488095,39.768229,573.172805,70.97433,609.136719]],["curveTo",[102.180432,645.100632,141.494792,663.082589,188.917411,663.082589]],["curveTo",[236.412203,663.082589,275.744606,645.100632,306.914621,609.136719]],["curveTo",[338.084636,573.172805,353.669643,522.488095,353.669643,457.082589]],["curveTo",[353.669643,413.604911,346.534785,376.366071,332.265067,345.366071]],["curveTo",[317.99535,314.366071,298.446429,290.568824,273.618304,273.97433]],["curveTo",[248.790179,257.379836,220.556548,249.082589,188.917411,249.082589]],["closePath",[]],["moveTo",[188.917411,335.720983]],["curveTo",[204.415923,335.720983,217.670387,344.605097,228.680803,362.373326]],["curveTo",[239.69122,380.141555,245.196428,411.71131,245.196428,457.082589]],["curveTo",[245.196428,502.526041,239.69122,533.780506,228.680803,550.845982]],["curveTo",[217.670387,567.911458,204.415923,576.444196,188.917411,576.444196]],["curveTo",[174.085566,576.444196,160.997768,567.911458,149.654018,550.845982]],["curveTo",[138.310268,533.780506,132.638394,502.526041,132.638394,457.082589]],["curveTo",[132.638394,411.71131,138.310268,380.141555,149.654018,362.373326]],["curveTo",[160.997768,344.605097,174.085566,335.720983,188.917411,335.720983]],["closePath",[]],["moveTo",[212.051339,-12.0]],["lineTo",[562.433036,663.082589]],["lineTo",[651.504465,663.082589]],["lineTo",[301.122769,-12.0]],["closePath",[]],["moveTo",[673.287947,-12.0]],["curveTo",[641.64881,-12.0,613.415179,-3.702753,588.587054,12.891741]],["curveTo",[563.758929,29.486235,544.210008,53.283482,529.940291,84.283482]],["curveTo",[515.670573,115.283482,508.535715,152.522321,508.535715,196.0]],["curveTo",[508.535715,261.405506,524.138765,312.090216,555.344866,348.054129]],["curveTo",[586.550968,384.018043,625.865328,402.0,673.287947,402.0]],["curveTo",[720.782739,402.0,760.115142,384.018043,791.285157,348.054129]],["curveTo",[822.455172,312.090216,838.040179,261.405506,838.040179,196.0]],["curveTo",[838.040179,152.522321,830.905321,115.283482,816.635604,84.283482]],["curveTo",[802.365886,53.283482,782.816965,29.486235,757.98884,12.891741]],["curveTo",[733.160715,-3.702753,704.927084,-12.0,673.287947,-12.0]],["closePath",[]],["moveTo",[673.287947,74.638394]],["curveTo",[688.786459,74.638394,702.040923,83.522508,713.051339,101.290737]],["curveTo",[724.061756,119.058966,729.566964,150.62872,729.566964,196.0]],["curveTo",[729.566964,241.443452,724.061756,272.697916,713.051339,289.763392]],["curveTo",[702.040923,306.828868,688.786459,315.361606,673.287947,315.361606]],["curveTo",[658.456102,315.361606,645.368304,306.828868,634.024555,289.763392]],["curveTo",[622.680805,272.697916,617.00893,241.443452,617.00893,196.0]],["curveTo",[617.00893,150.62872,622.680805,119.058966,634.024555,101.290737]],["curveTo",[645.368304,83.522508,658.456102,74.638394,673.287947,74.638394]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]],["moveTo",[174.700893,697.680803]],["curveTo",[153.549851,697.680803,136.092076,704.675595,122.327567,718.665178]],["curveTo",[108.563058,732.654762,101.680803,750.0,101.680803,770.700893]],["curveTo",[101.680803,791.401786,108.563058,808.747024,122.327567,822.736608]],["curveTo",[136.092076,836.726191,153.549851,843.720983,174.700893,843.720983]],["curveTo",[196.518601,843.720983,214.143044,836.726191,227.574219,822.736608]],["curveTo",[241.005395,808.747024,247.720983,791.401786,247.720983,770.700893]],["curveTo",[247.720983,750.0,241.005395,732.654762,227.574219,718.665178]],["curveTo",[214.143044,704.675595,196.518601,697.680803,174.700893,697.680803]],["closePath",[]],["moveTo",[398.803572,697.680803]],["curveTo",[377.65253,697.680803,360.194755,704.675595,346.430246,718.665178]],["curveTo",[332.665737,732.654762,325.783482,750.0,325.783482,770.700893]],["curveTo",[325.783482,791.401786,332.665737,808.747024,346.430246,822.736608]],["curveTo",[360.194755,836.726191,377.65253,843.720983,398.803572,843.720983]],["curveTo",[420.62128,843.720983,438.245722,836.726191,451.676898,822.736608]],["curveTo",[465.108074,808.747024,471.823662,791.401786,471.823662,770.700893]],["curveTo",[471.823662,750.0,465.108074,732.654762,451.676898,718.665178]],["curveTo",[438.245722,704.675595,420.62128,697.680803,398.803572,697.680803]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[288.376215,-12]],["curveTo",[240.431231,-12.0,197.229369,-1.711772,158.770631,18.864685]],["curveTo",[120.311892,39.441141,89.92961,69.139159,67.623785,107.958738]],["curveTo",[45.317959,146.778318,34.165046,193.764564,34.165046,248.917477]],["curveTo",[34.165046,302.737056,45.529731,349.056636,68.2591,387.876215]],["curveTo",[90.988469,426.695795,120.415856,456.682041,156.541262,477.834954]],["curveTo",[192.666667,498.987867,230.792072,509.564323,270.917477,509.564323]],["curveTo",[318.987867,509.564323,358.873992,498.956515,390.575854,477.7409]],["curveTo",[422.277715,456.525285,445.871769,427.809669,461.358015,391.594054]],["curveTo",[476.844261,355.378438,484.587384,314.847087,484.587384,270]],["curveTo",[484.587384,256.415856,483.844261,243.286608,482.358015,230.612254]],["curveTo",[480.871769,217.9379,479.371769,208.149672,477.858015,201.247569]],["lineTo",[156.633507,201.247569]],["lineTo",[155.174769,306.752431]],["lineTo",[350.495139,306.752431]],["curveTo",[350.495139,330.415856,344.804808,350.226128,333.424146,366.183246]],["curveTo",[322.043485,382.140364,302.360841,390.118923,274.376215,390.118923]],["curveTo",[259.113272,390.118923,244.107205,385.969854,229.358015,377.671716]],["curveTo",[214.608825,369.373577,202.494951,354.926369,193.016392,334.330092]],["curveTo",[183.537833,313.733816,179.2848,285.262944,180.257292,248.917477]],["curveTo",[181.229784,211.709549,188.139574,182.883902,200.986661,162.440538]],["curveTo",[213.833748,141.997174,229.920115,127.765582,249.245761,119.745761]],["curveTo",[268.571407,111.725941,288.71279,107.71603,309.669908,107.71603]],["curveTo",[328.085764,107.71603,346.064323,110.351346,363.605585,115.621977]],["curveTo",[381.146846,120.892607,398.737056,128.618133,416.376215,138.798553]],["lineTo",[466.587384,44.376215]],["curveTo",[441.003241,26.431231,412.130869,12.548949,379.970269,2.729369]],["curveTo",[347.809669,-7.09021,317.278318,-12.0,288.376215,-12]],["closePath",[]],["moveTo",[181.082589,570.0]],["lineTo",[294.330357,755.669643]],["lineTo",[458.95759,755.669643]],["lineTo",[468.091519,739.185268]],["lineTo",[300.256697,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[-7.834954,0]],["lineTo",[194.224508,651.082523]],["lineTo",[382.739092,651.082523]],["lineTo",[584.798553,0]],["lineTo",[419.0364,0]],["lineTo",[338,324.660185]],["curveTo",[329.278318,356.895621,320.691951,391.346672,312.2409,428.013339]],["curveTo",[303.789849,464.680006,295.383902,499.797723,287.023061,533.366493]],["lineTo",[283.023061,533.366493]],["curveTo",[275.815133,499.617303,267.985643,464.45448,259.534592,427.878023]],["curveTo",[251.083541,391.301567,242.497174,356.895621,233.775492,324.660185]],["lineTo",[152.468461,0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[129.834954,270.188108]],["lineTo",[445.128646,270.188108]],["lineTo",[445.128646,148.5546]],["closePath",[]],["moveTo",[286.454985,696.433036]],["curveTo",[243.935144,696.433036,210.851376,705.567894,187.203683,723.837612]],["curveTo",[163.555989,742.107329,151.732143,766.47154,151.732143,796.930246]],["curveTo",[151.732143,827.388951,163.555989,851.604539,187.203683,869.577009]],["curveTo",[210.851376,887.549479,243.935144,896.535715,286.454985,896.535715]],["curveTo",[330.037823,896.535715,363.486422,887.602559,386.800782,869.736249]],["curveTo",[410.115142,851.869938,421.772322,827.452647,421.772322,796.484375]],["curveTo",[421.772322,766.322917,410.115142,742.107329,386.800782,723.837612]],["curveTo",[363.486422,705.567894,330.037823,696.433036,286.454985,696.433036]],["closePath",[]],["moveTo",[286.47063,752.319197]],["curveTo",[299.452264,752.319197,309.619234,756.445499,316.97154,764.698103]],["curveTo",[324.323847,772.950707,328.0,783.461203,328.0,796.229592]],["curveTo",[328.0,809.409864,324.323847,820.108259,316.97154,828.324777]],["curveTo",[309.619234,836.541295,299.452264,840.649554,286.47063,840.649554]],["curveTo",[273.936639,840.649554,263.975447,836.541295,256.587054,828.324777]],["curveTo",[249.198661,820.108259,245.504465,809.409864,245.504465,796.229592]],["curveTo",[245.504465,783.461203,249.198661,772.950707,256.587054,764.698103]],["curveTo",[263.975447,756.445499,273.936639,752.319197,286.47063,752.319197]],["closePath",[]]]}]}],"chars":" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÄÖÜäöüéÅ","glyphs":[1,79,82,96,98,100,64,81,85,86,94,101,76,83,75,91,65,66,67,68,69,70,71,72,73,74,77,78,104,103,105,80,95,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,87,93,88,106,84,108,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,89,92,90,107,54,56,57,58,62,63,59,55]},{"file":"VarTestSans.otf","family":"VarTestSansCFF2","instances":["ExtraLight","Light","Regular","Medium","Semibold","Bold","Black"],"advances":[{"coordinates":[200.0],"widths":[200.0,248.0,336.0,472.0,472.0,798.0,562.0,208.0,270.0,270.0,386.0,472.0,208.0,294.0,208.0,360.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,472.0,208.0,208.0,472.0,472.0,472.0,394.0,802.0,520.0,574.0,562.0,600.0,510.0,470.0,600.0,634.0,232.0,456.0,550.0,460.0,698.0,632.0,648.0,542.0,648.0,534.0,516.0,520.0,630.0,482.0,764.0,470.0,436.0,538.0,270.0,360.0,270.0,472.0,500.0,532.0,486.0,538.0,446.0,540.0,478.0,252.0,480.0,522.0,222.0,222.0,452.0,230.0,806.0,526.0,532.0,540.0,540.0,306.0,400.0,302.0,524.0,422.0,672.0,392.0,424.0,396.0,270.0,220.0,270.0,472.0,520.0,648.0,630.0,486.0,532.0,524.0,478.0,520.0],"strikeout":[286,50]},{"coordinates":[300.0],"widths":[200.0,259.195216,360.389577,478.830692,478.797095,805.196924,574.794532,219.195216,278.996155,278.996155,394.596326,478.797095,219.195216,298.598035,219.195216,357.401111,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,478.797095,219.195216,219.195216,478.797095,478.797095,478.797095,402.396412,814.161964,526.330886,577.798377,564.332432,604.165056,514.598035,476.597181,604.598035,638.831465,240.330113,462.330886,557.796668,466.997295,705.830306,636.165056,652.331659,548.597181,652.39812,543.595899,520.79795,524.331659,634.165056,490.996155,769.997681,481.794959,446.829146,538.399829,278.996155,357.401111,278.996155,478.797095,500.0,534.798804,490.998068,542.198206,448.665636,543.998291,482.831465,262.795387,486.597181,527.997437,228.597181,228.797095,463.595045,236.830692,812.197352,531.597608,534.798804,543.998291,543.998291,317.195216,405.197779,311.829533,529.331272,434.194789,684.661771,406.793678,435.794959,403.796668,278.996155,225.797522,278.996155,478.797095,526.397266,652.39812,634.198206,490.997864,534.798804,529.397693,482.79795,526.397266],"strikeout":[287,50]},{"coordinates":[400.0],"widths":[200.0,289.214674,425.789111,497.146881,497.023195,824.495148,609.102485,249.214674,303.118934,303.118934,417.646982,497.023195,249.214674,310.927455,249.214674,350.432308,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,497.023195,249.214674,249.214674,497.023195,497.023195,497.023195,424.911005,846.773715,543.306865,587.98355,570.58674,615.333464,526.927455,494.287219,616.927455,651.786818,262.666928,479.306865,578.703077,485.760219,726.826912,647.333464,663.946802,566.287219,664.191479,569.326863,533.663432,535.946802,645.333464,515.118934,786.080188,513.422603,475.867006,539.471953,303.118934,350.432308,303.118934,497.023195,500.0,542.303668,504.400157,553.455503,455.813417,554.719526,495.786818,291.742721,504.287219,544.07929,246.287219,247.023195,494.686627,255.146881,828.815266,546.607337,542.303668,554.719526,554.719526,347.214674,419.135384,338.186975,543.626834,466.894556,718.61373,446.462248,467.422603,424.703077,303.118934,241.343313,303.118934,497.023195,543.551242,664.191479,645.455503,504.399408,542.303668,543.871361,495.663432,543.551242],"strikeout":[291,50]},{"coordinates":[500.0],"widths":[200.0,302.432957,454.586084,505.211935,505.048581,832.992615,624.209093,262.432957,313.740769,313.740769,427.796735,505.048581,262.432957,316.356393,262.432957,347.363778,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,505.048581,262.432957,262.432957,505.048581,505.048581,505.048581,434.824718,861.133445,550.781794,592.468325,573.340661,620.25118,532.356393,502.076564,622.356393,657.491369,272.50236,486.781794,587.908666,494.021982,736.072218,652.25118,669.061227,574.076564,669.384376,580.65682,539.32841,541.061227,650.25118,525.740769,793.161699,527.349008,488.653068,539.944034,313.740769,347.363778,313.740769,505.048581,500.0,545.608239,510.301416,558.412359,458.960755,559.440342,501.491369,304.488923,512.076564,551.160513,254.076564,255.048581,508.376991,263.211935,836.13253,553.216478,545.608239,559.440342,559.440342,360.432957,425.272444,349.792785,549.92151,481.293042,733.563587,463.929264,481.349008,433.908666,313.740769,248.188495,313.740769,505.048581,551.104547,669.384376,650.412359,510.300427,545.608239,550.244461,501.32841,551.104547],"strikeout":[292,50]},{"coordinates":[600.0],"widths":[200.0,315.200328,482.400714,513.001845,512.800199,841.200211,638.800374,275.200328,324.000263,324.000263,437.600252,512.800199,275.200328,321.600135,275.200328,344.399924,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,512.800199,275.200328,275.200328,512.800199,512.800199,512.800199,444.400246,875.003349,558.001777,596.800111,576.000683,625.001162,537.600135,509.600193,627.600135,663.001299,282.002324,494.001777,596.800228,502.001914,745.002119,657.001025,674.00123,581.600193,674.400129,591.600281,544.80014,546.00123,655.001162,536.000263,800.00164,540.800345,501.002939,540.400012,324.000263,344.399924,324.000263,512.800199,500.0,548.800082,516.001367,563.200123,462.000683,564.000117,507.001299,316.800316,519.600193,558.000175,261.600193,262.800199,521.600339,271.001845,843.200181,559.600164,548.800082,564.000117,564.000117,373.200328,431.200152,361.002665,556.001504,495.200357,748.003417,480.800433,494.800345,442.800228,324.000263,254.80017,324.000263,512.800199,558.400187,674.400129,655.200123,516.000146,548.800082,556.400158,506.80014,558.400187],"strikeout":[294,50]},{"coordinates":[700.0],"widths":[200.0,340.283625,537.046469,528.118089,528.029344,857.325188,667.467,300.283625,344.156485,344.156485,456.860641,528.029344,300.283625,331.902203,300.283625,338.577016,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,528.029344,300.283625,300.283625,528.029344,528.029344,528.029344,463.212719,902.436531,572.558159,605.310516,581.599292,634.518797,547.902203,524.381422,637.902203,673.638655,301.037593,508.558159,614.268953,517.678018,762.357806,665.398938,684.078726,596.381422,684.254281,613.10025,555.550125,556.078726,664.518797,556.156485,813.438301,567.227391,525.076956,541.295844,344.156485,338.577016,344.156485,528.029344,500.0,555.070906,527.198584,572.606359,467.599292,572.958438,517.638655,340.987782,534.381422,571.437656,276.381422,278.029344,547.579469,286.118089,857.085578,572.141813,555.070906,572.958438,572.958438,398.283625,442.845969,382.837239,568.318443,522.523235,775.99646,513.946219,521.227391,460.268953,344.156485,267.789735,344.156485,528.029344,572.7335,684.254281,664.606359,527.198047,555.070906,568.493891,517.550125,572.7335],"strikeout":[297,50]},{"coordinates":[900.0],"widths":[200.0,360.0,580.0,540.0,540.0,870.0,690.0,320.0,360.0,360.0,472.0,540.0,320.0,340.0,320.0,334.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,540.0,320.0,320.0,540.0,540.0,540.0,478.0,924.0,584.0,612.0,586.0,642.0,556.0,536.0,646.0,682.0,316.0,520.0,628.0,530.0,776.0,672.0,692.0,608.0,692.0,630.0,564.0,564.0,672.0,572.0,824.0,588.0,544.0,542.0,360.0,334.0,360.0,540.0,500.0,560.0,536.0,580.0,472.0,580.0,526.0,360.0,546.0,582.0,288.0,290.0,568.0,298.0,868.0,582.0,560.0,580.0,580.0,418.0,452.0,400.0,578.0,544.0,798.0,540.0,542.0,474.0,360.0,278.0,360.0,540.0,584.0,692.0,672.0,536.0,560.0,578.0,526.0,584.0],"strikeout":[300,50]}],"locations":[{"coordinates":[300.0],"glyphs":[{"char":"A","commands":[["moveTo",[8.000773,0]],["lineTo",[54.328567,0]],["lineTo",[187.831079,388.003092]],["curveTo",[214.664863,466.170467,237.831852,535.170081,260.832238,615.67066]],["lineTo",[264.832238,615.67066]],["curveTo",[287.999227,535.336683,311.166216,466.170467,338.0,388.003092]],["lineTo",[469.669114,0.0]],["lineTo",[518.330113,0.0]],["lineTo",[286.161964,659.000386]],["lineTo",[240.168921,659.000386]],["lineTo",[8.000773,0.0]],["closePath",[]],["moveTo",[113.999227,226.170467]],["lineTo",[409.498261,226.170467]],["lineTo",[409.498261,264.666409]],["lineTo",[113.999227,264.666409]],["lineTo",[113.999227,226.170467]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[97.201196,0]],["lineTo",[467.798377,0]],["lineTo",[467.798377,39.595045]],["lineTo",[143.195216,39.595045]],["lineTo",[143.195216,330.203332]],["lineTo",[407.397693,330.203332]],["lineTo",[407.397693,369.798377]],["lineTo",[143.195216,369.798377]],["lineTo",[143.195216,619.405382]],["lineTo",[457.798377,619.405382]],["lineTo",[457.798377,659.000427]],["lineTo",[97.201196,659.000427]],["lineTo",[97.201196,0.0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[326.165829,-12]],["curveTo",[484.664863,-12,597.664477,123.000386,597.664477,331.666795]],["curveTo",[597.664477,540.166602,484.664863,671.000386,326.165829,671.000386]],["curveTo",[167.833398,671.000386,54.833784,540.166602,54.833784,331.666795]],["curveTo",[54.833784,123.000386,167.833398,-12.0,326.165829,-12.0]],["closePath",[]],["moveTo",[326.165829,29.82876]],["curveTo",[192.330113,29.82876,102.994976,148.830692,102.994976,331.666795]],["curveTo",[102.994976,514.336296,192.330113,629.171627,326.165829,629.171627]],["curveTo",[460.001546,629.171627,549.336683,514.336296,549.336683,331.666795]],["curveTo",[549.336683,148.830692,460.001546,29.82876,326.165829,29.82876]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.999573,-12]],["curveTo",[398.598035,-12,479.797522,64.398975,479.797522,165.398548]],["curveTo",[479.797522,266.19906,417.596754,305.998291,341.096967,339.798377]],["lineTo",[241.395985,384.299018]],["curveTo",[193.196497,405.498932,127.39513,434.499786,127.39513,511.401965]],["curveTo",[127.39513,582.703973,185.796668,629.205468,272.798804,629.205468]],["curveTo",[338.700128,629.205468,390.800513,602.404955,431.200342,563.404528]],["lineTo",[456.397266,592.600598]],["curveTo",[416.298163,634.900043,352.598889,671.000427,272.798804,671.000427]],["curveTo",[161.400256,671.000427,81.201196,604.301581,81.201196,508.602307]],["curveTo",[81.201196,412.802221,155.701837,370.903033,214.901324,344.603161]],["lineTo",[314.802221,299.902606]],["curveTo",[379.702691,270.702264,433.403674,244.501495,433.403674,162.19906]],["curveTo",[433.403674,83.696711,370.001709,29.794959,269.399402,29.794959]],["curveTo",[192.198206,29.794959,122.097821,63.2956,75.197779,116.596326]],["lineTo",[46.400683,87.400256]],["curveTo",[98.399829,28.600598,172.19906,-12.0,268.999573,-12.0]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.000386,-12]],["curveTo",[260.000773,-12,318.001546,22.667182,367.335137,62.333977]],["lineTo",[369.668341,62.333977]],["lineTo",[374.33475,0.0]],["lineTo",[411.830306,0.0]],["lineTo",[411.830306,306.667955]],["curveTo",[411.830306,409.66525,373.332045,492.165829,259.666023,492.165829]],["curveTo",[181.666023,492.165829,114.999614,455.66525,81.001159,429.999227]],["lineTo",[101.332818,398.669501]],["curveTo",[133.66525,421.33591,189.666023,453.003478,254.333977,453.003478]],["curveTo",[348.669501,453.003478,369.002705,374.333977,367.669114,300.998841]],["curveTo",[154.001546,277.332045,57.667568,226.998841,57.667568,119.499034]],["curveTo",[57.667568,29.332045,121.001159,-12.0,197.000386,-12.0]],["closePath",[]],["moveTo",[204.664863,26.995749]],["curveTo",[149.663704,26.995749,102.995749,52.996522,102.995749,121.66525]],["curveTo",[102.995749,198.333977,170.330886,243.335137,367.669114,266.002319]],["lineTo",[367.669114,103.664477]],["curveTo",[308.333977,52.996522,259.666023,26.995749,204.664863,26.995749]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[273.999227,-12]],["curveTo",[344.333205,-12,387.66525,9.666795,425.331272,34.999614]],["lineTo",[406.999614,65.662931]],["curveTo",[372.999614,41.996135,329.999227,26.662544,277.998454,26.662544]],["curveTo",[176.163124,26.662544,97.328953,108.331659,97.328953,240.999614]],["curveTo",[97.328953,385.667568,182.330886,453.669887,261.666023,453.669887]],["curveTo",[347.667568,453.669887,403.002705,393.001932,403.002705,271.331272]],["lineTo",[79.329726,271.331272]],["lineTo",[79.329726,233.335137]],["lineTo",[442.331659,233.335137]],["curveTo",[444.664863,246.33475,444.998068,258.000773,444.998068,270.0]],["curveTo",[444.998068,411.000386,375.331272,492.165829,261.332818,492.165829]],["curveTo",[153.666795,492.165829,51.667568,395.999227,51.667568,239.332818]],["curveTo",[51.667568,82.0,151.000386,-12.0,273.999227,-12.0]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.000427,-231.201196]],["curveTo",[387.398548,-231.201196,475.997437,-151.80094,475.997437,-69.002136]],["curveTo",[475.997437,6.797095,425.598462,41.196924,317.699274,41.196924]],["lineTo",[214.79795,41.196924]],["curveTo",[143.196924,41.196924,123.196924,68.997864,123.196924,103.598462]],["curveTo",[123.196924,135.599317,141.397693,156.0,162.198206,173.100384]],["curveTo",[184.198206,160.0,214.798804,152.599744,241.599317,152.599744]],["curveTo",[335.598462,152.599744,409.397693,224.000854,409.397693,322.0]],["curveTo",[409.397693,376.901751,385.696711,415.402819,355.995728,438.603161]],["lineTo",[355.995728,442.603161]],["lineTo",[467.997437,442.603161]],["lineTo",[467.997437,480.19906]],["lineTo",[305.398548,480.19906]],["curveTo",[290.398975,486.398975,267.799231,492.19906,241.599317,492.19906]],["curveTo",[147.800085,492.19906,72.601452,421.598462,72.601452,322.799658]],["curveTo",[72.601452,264.799658,102.801367,216.999573,133.401111,191.199487]],["lineTo",[133.401111,187.199487]],["curveTo",[112.001709,172.199915,81.001282,141.600171,81.001282,98.599744]],["curveTo",[81.001282,62.0,99.201196,36.0,123.401111,21.600171]],["lineTo",[123.401111,17.600171]],["curveTo",[79.201196,-13.600171,51.601025,-56.300726,51.601025,-98.200769]],["curveTo",[51.601025,-179.200342,127.800085,-231.201196,249.000427,-231.201196]],["closePath",[]],["moveTo",[241.599317,187.996583]],["curveTo",[175.397693,187.996583,117.395985,242.39812,117.395985,322.799658]],["curveTo",[117.395985,404.801367,173.597608,455.602734,241.599317,455.602734]],["curveTo",[309.601025,455.602734,365.802648,404.801367,365.802648,322.799658]],["curveTo",[365.802648,242.39812,307.80094,187.996583,241.599317,187.996583]],["closePath",[]],["moveTo",[253.599317,-195.404528]],["curveTo",[152.397266,-195.404528,94.396412,-154.403246,94.396412,-93.60188]],["curveTo",[94.396412,-59.80094,112.99701,-22.799658,157.398548,7.201196]],["curveTo",[182.19906,1.401111,207.399402,-0.598889,216.79795,-0.598889]],["lineTo",[319.700982,-0.598889]],["curveTo",[392.402392,-0.598889,430.803076,-20.799658,430.803076,-73.80094]],["curveTo",[430.803076,-134.202478,361.801794,-195.404528,253.599317,-195.404528]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.399829,-12]],["curveTo",[312.39812,-12,370.197352,50.999573,370.197352,123.398548]],["curveTo",[370.197352,215.999146,288.996155,241.498078,212.996155,269.998291]],["curveTo",[154.396412,291.798377,103.39513,312.598889,103.39513,367.000427]],["curveTo",[103.39513,411.101666,137.19607,454.403246,212.198206,454.403246]],["curveTo",[260.398975,454.403246,294.698847,438.202905,327.198633,414.803076]],["lineTo",[350.596326,442.399829]],["curveTo",[315.596754,471.399402,267.797522,492.19906,213.398548,492.19906]],["curveTo",[113.399402,492.19906,58.600598,433.599317,58.600598,364.400683]],["curveTo",[58.600598,283.000427,134.601452,253.001282,208.201623,227.001282]],["curveTo",[263.901751,207.001282,325.402819,180.800513,325.402819,120.598889]],["curveTo",[325.402819,69.997437,286.201623,25.795814,210.599744,25.795814]],["curveTo",[138.79795,25.795814,96.39812,48.396412,55.798377,82.596326]],["lineTo",[32.400683,54.999573]],["curveTo",[73.600171,14.0,133.599317,-12.0,208.399829,-12.0]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[227.198633,-12]],["curveTo",[309.396839,-12,369.395985,28.599744,420.595472,85.999146]],["curveTo",[480.194789,156.498505,523.194361,247.398548,550.994447,345.198633]],["lineTo",[508.399829,345.198633]],["curveTo",[482.799658,252.39812,444.399829,172.197352,388.999573,109.796668]],["curveTo",[345.199487,60.996155,290.19906,26.195643,230.198206,26.195643]],["curveTo",[149.796668,26.195643,81.39513,82.197352,81.39513,169.799231]],["curveTo",[81.39513,328.401538,373.395985,386.002563,373.395985,551.20205]],["curveTo",[373.395985,620.000854,334.197352,671.000427,262.598889,671.000427]],["curveTo",[182.600598,671.000427,126.801367,608.801367,126.801367,523.801794]],["curveTo",[126.801367,379.700982,262.000854,194.600598,394.299018,81.300299]],["curveTo",[449.898334,33.700128,502.397266,3.600171,547.19607,-12.0]],["lineTo",[561.994874,26.995301]],["curveTo",[520.994447,41.995728,471.39513,70.796241,420.795387,113.696711]],["curveTo",[299.395985,215.197779,167.996583,391.400256,167.996583,525.60188]],["curveTo",[167.996583,585.602734,202.797095,634.604015,261.198633,634.604015]],["curveTo",[314.099957,634.604015,333.400256,591.802648,333.400256,549.401965]],["curveTo",[333.400256,400.400683,36.400683,350.997864,36.400683,166.999573]],["curveTo",[36.400683,56.799658,122.400683,-12.0,227.198633,-12.0]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[387.664477,-145.66525]],["curveTo",[455.997681,-145.66525,517.997681,-127.66525,574.830692,-92.331659]],["lineTo",[557.998454,-60.33475]],["curveTo",[510.664863,-89.001932,456.664863,-108.002319,390.66409,-108.002319]],["curveTo",[212.995749,-108.002319,91.996135,11.666023,91.996135,204.666409]],["curveTo",[91.996135,441.500966,264.330113,595.335137,445.831079,595.335137]],["curveTo",[617.663704,595.335137,722.998841,484.002319,722.998841,313.333591]],["curveTo",[722.998841,172.331659,644.997295,91.664477,581.996908,91.664477]],["curveTo",[520.995749,91.664477,499.995362,134.665636,522.32934,226.333977]],["lineTo",[557.662158,414.998841]],["lineTo",[523.831852,414.998841]],["lineTo",[514.998841,374.665636]],["lineTo",[512.998841,374.665636]],["curveTo",[493.332045,407.332045,465.66525,423.332045,429.998454,423.332045]],["curveTo",[311.998454,423.332045,242.999614,295.66525,242.999614,198.0]],["curveTo",[242.999614,107.500193,295.49942,61.334364,357.998454,61.334364]],["curveTo",[403.66525,61.334364,445.331272,92.33475,479.998454,131.667568]],["lineTo",[481.998454,131.667568]],["curveTo",[486.664863,79.334364,525.664477,55.001159,578.66409,55.001159]],["curveTo",[662.32934,55.001159,762.661771,145.334364,762.661771,315.666795]],["curveTo",[762.661771,506.165829,642.162737,632.998068,448.997295,632.998068]],["curveTo",[238.165829,632.998068,51.666795,462.998841,51.666795,202.333205]],["curveTo",[51.666795,-18.331659,194.664863,-145.66525,387.664477,-145.66525]],["closePath",[]],["moveTo",[363.830306,98.330886]],["curveTo",[319.662931,98.330886,284.328567,125.497874,284.328567,200.833011]],["curveTo",[284.328567,285.334364,339.329726,386.335523,428.831465,386.335523]],["curveTo",[459.332045,386.335523,478.998841,374.83533,502.999614,337.667568]],["lineTo",[471.999227,164.998841]],["curveTo",[432.165056,118.998068,396.164283,98.330886,363.830306,98.330886]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[242.798804,-12]],["curveTo",[359.297736,-12,436.99701,62.199915,436.99701,155.199487]],["curveTo",[436.99701,240.999573,380.09654,283.799231,325.296882,317.699274]],["lineTo",[325.296882,321.699274]],["curveTo",[362.196497,351.599317,416.197352,413.800085,416.197352,484.400683]],["curveTo",[416.197352,579.800085,352.39812,651.400256,243.398548,651.400256]],["curveTo",[150.0,651.400256,76.400683,587.000427,76.400683,494.801367]],["curveTo",[76.400683,427.001282,121.201196,379.201196,167.601025,348.401538]],["lineTo",[167.601025,344.401538]],["curveTo",[107.601025,312.401538,40.199915,250.400683,40.199915,163.800085]],["curveTo",[40.199915,63.400256,125.499359,-12.0,242.798804,-12.0]],["closePath",[]],["moveTo",[289.799231,334.79795]],["curveTo",[206.39812,367.798377,120.195643,404.19906,120.195643,494.801367]],["curveTo",[120.195643,564.002563,171.496796,614.404101,242.998718,614.404101]],["curveTo",[326.200769,614.404101,373.60188,554.202478,373.60188,483.201196]],["curveTo",[373.60188,428.199915,343.80094,378.798804,289.799231,334.79795]],["closePath",[]],["moveTo",[243.198633,24.996155]],["curveTo",[150.596326,24.996155,83.695002,86.997864,83.695002,165.799231]],["curveTo",[83.695002,235.601025,131.896625,289.20205,196.798804,327.402819]],["curveTo",[296.099957,288.202478,392.302435,254.001709,392.302435,155.799231]],["curveTo",[392.302435,82.598035,333.401111,24.996155,243.198633,24.996155]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.999573,257.000427]],["curveTo",[267.999146,257.000427,321.998291,331.400256,321.998291,465.000427]],["curveTo",[321.998291,596.800513,267.999146,671.000427,180.999573,671.000427]],["curveTo",[94.0,671.000427,40.000854,596.800513,40.000854,465.000427]],["curveTo",[40.000854,331.400256,94.0,257.000427,180.999573,257.000427]],["closePath",[]],["moveTo",[180.999573,289.797522]],["curveTo",[118.997864,289.797522,78.797095,353.599317,78.797095,465.000427]],["curveTo",[78.797095,576.401538,118.997864,638.203332,180.999573,638.203332]],["curveTo",[243.001282,638.203332,283.20205,576.401538,283.20205,465.000427]],["curveTo",[283.20205,353.599317,243.001282,289.797522,180.999573,289.797522]],["closePath",[]],["moveTo",[197.799231,-12.0]],["lineTo",[236.196497,-12.0]],["lineTo",[603.997437,671.000427]],["lineTo",[565.600171,671.000427]],["lineTo",[197.799231,-12.0]],["closePath",[]],["moveTo",[624.197352,-12.0]],["curveTo",[711.196924,-12.0,765.19607,62.399829,765.19607,196.0]],["curveTo",[765.19607,327.800085,711.196924,402.0,624.197352,402.0]],["curveTo",[537.197779,402.0,483.198633,327.800085,483.198633,196.0]],["curveTo",[483.198633,62.399829,537.197779,-12.0,624.197352,-12.0]],["closePath",[]],["moveTo",[624.197352,20.797095]],["curveTo",[562.195643,20.797095,521.994874,84.598889,521.994874,196.0]],["curveTo",[521.994874,307.401111,562.195643,369.202905,624.197352,369.202905]],["curveTo",[686.19906,369.202905,726.399829,307.401111,726.399829,196.0]],["curveTo",[726.399829,84.598889,686.19906,20.797095,624.197352,20.797095]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[8.000854,0]],["lineTo",[54.394703,0]],["lineTo",[187.797522,388.003417]],["curveTo",[214.598035,466.204186,237.798377,535.203759,260.798804,615.604443]],["lineTo",[264.798804,615.604443]],["curveTo",[287.999146,535.303716,311.199487,466.204186,338.0,388.003417]],["lineTo",[469.602734,-0.0]],["lineTo",[518.396412,-0.0]],["lineTo",[286.194789,659.000427]],["lineTo",[240.202478,659.000427]],["lineTo",[8.000854,0.0]],["closePath",[]],["moveTo",[113.999146,226.204186]],["lineTo",[409.498078,226.204186]],["lineTo",[409.498078,264.699701]],["lineTo",[113.999146,264.699701]],["lineTo",[113.999146,226.204186]],["closePath",[]],["moveTo",[165.199487,724.601452]],["curveTo",[185.998291,724.601452,201.797522,738.800513,201.797522,761.199487]],["curveTo",[201.797522,783.598462,185.998291,797.797522,165.199487,797.797522]],["curveTo",[144.400683,797.797522,128.601452,783.598462,128.601452,761.199487]],["curveTo",[128.601452,738.800513,144.400683,724.601452,165.199487,724.601452]],["closePath",[]],["moveTo",[360.79795,724.601452]],["curveTo",[381.596754,724.601452,397.395985,738.800513,397.395985,761.199487]],["curveTo",[397.395985,783.598462,381.596754,797.797522,360.79795,797.797522]],["curveTo",[339.999146,797.797522,324.199915,783.598462,324.199915,761.199487]],["curveTo",[324.199915,738.800513,339.999146,724.601452,360.79795,724.601452]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[273.799231,-12]],["curveTo",[344.199915,-12,387.398548,9.800085,425.197779,35.199487]],["lineTo",[407.199487,65.19607]],["curveTo",[372.999573,41.595899,329.999146,26.195643,277.998291,26.195643]],["curveTo",[175.896198,26.195643,96.795387,107.598462,96.395557,240.999573]],["curveTo",[95.995728,384.401538,181.797522,454.003417,261.599317,454.003417]],["curveTo",[347.80094,454.003417,403.402819,393.002136,403.402819,270.39812]],["lineTo",[79.595899,270.39812]],["lineTo",[79.795814,234.001709]],["lineTo",[442.498078,234.001709]],["curveTo",[444.598035,246.001709,444.997864,258.000854,444.997864,270.0]],["curveTo",[444.997864,411.200342,374.997864,492.19906,261.199487,492.19906]],["curveTo",[153.600171,492.19906,51.80094,395.799231,51.80094,239.199487]],["curveTo",[51.80094,82.199915,151.200342,-12.0,273.799231,-12.0]],["closePath",[]],["moveTo",[189.000427,570.0]],["lineTo",[228.996155,570.0]],["lineTo",[380.995301,718.598889]],["lineTo",[378.195643,723.998291]],["lineTo",[326.001709,723.998291]],["lineTo",[189.000427,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[8.000854,0]],["lineTo",[54.394703,0]],["lineTo",[187.797522,388.003417]],["curveTo",[214.598035,466.204186,237.798377,535.203759,260.798804,615.604443]],["lineTo",[264.798804,615.604443]],["curveTo",[287.999146,535.303716,311.199487,466.204186,338.0,388.003417]],["lineTo",[469.602734,-0.0]],["lineTo",[518.396412,-0.0]],["lineTo",[286.194789,659.000427]],["lineTo",[240.202478,659.000427]],["lineTo",[8.000854,0.0]],["closePath",[]],["moveTo",[113.999146,226.204186]],["lineTo",[409.498078,226.204186]],["lineTo",[409.498078,264.699701]],["lineTo",[113.999146,264.699701]],["lineTo",[113.999146,226.204186]],["closePath",[]],["moveTo",[262.998718,699.600171]],["curveTo",[320.597181,699.600171,361.596754,732.399829,361.596754,785.399402]],["curveTo",[361.596754,838.398975,320.597181,871.198633,262.998718,871.198633]],["curveTo",[205.400256,871.198633,164.400683,838.398975,164.400683,785.399402]],["curveTo",[164.400683,732.399829,205.400256,699.600171,262.998718,699.600171]],["closePath",[]],["moveTo",[262.998718,725.398548]],["curveTo",[226.39812,725.398548,197.997437,745.198633,197.997437,785.399402]],["curveTo",[197.997437,825.600171,226.39812,845.400256,262.998718,845.400256]],["curveTo",[299.599317,845.400256,328.0,825.600171,328.0,785.399402]],["curveTo",[328.0,745.198633,299.599317,725.398548,262.998718,725.398548]],["closePath",[]]]}]},{"coordinates":[250.0],"glyphs":[{"char":"A","commands":[["moveTo",[9.000386,0]],["lineTo",[48.164283,0]],["lineTo",[184.915539,392.001546]],["curveTo",[212.332432,471.085234,235.915926,539.58504,259.416119,620.83533]],["lineTo",[263.416119,620.83533]],["curveTo",[286.999614,539.668341,310.583108,471.085234,338.0,392.001546]],["lineTo",[472.834557,0.0]],["lineTo",[514.165056,0.0]],["lineTo",[280.080982,659.500193]],["lineTo",[243.084461,659.500193]],["lineTo",[9.000386,0.0]],["closePath",[]],["moveTo",[112.999614,231.085234]],["lineTo",[407.24913,231.085234]],["lineTo",[407.24913,264.333205]],["lineTo",[112.999614,264.333205]],["lineTo",[112.999614,231.085234]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[98.600598,0]],["lineTo",[465.899188,0]],["lineTo",[465.899188,33.797522]],["lineTo",[137.597608,33.797522]],["lineTo",[137.597608,334.101666]],["lineTo",[404.698847,334.101666]],["lineTo",[404.698847,367.899188]],["lineTo",[137.597608,367.899188]],["lineTo",[137.597608,625.702691]],["lineTo",[455.899188,625.702691]],["lineTo",[455.899188,659.500214]],["lineTo",[98.600598,659.500214]],["lineTo",[98.600598,0.0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[325.082915,-12]],["curveTo",[482.332432,-12,594.832238,123.500193,594.832238,331.833398]],["curveTo",[594.832238,540.083301,482.332432,671.500193,325.082915,671.500193]],["curveTo",[167.916699,671.500193,55.416892,540.083301,55.416892,331.833398]],["curveTo",[55.416892,123.500193,167.916699,-12.0,325.082915,-12.0]],["closePath",[]],["moveTo",[325.082915,23.91438]],["curveTo",[188.165056,23.91438,96.497488,145.415346,96.497488,331.833398]],["curveTo",[96.497488,518.168148,188.165056,635.585813,325.082915,635.585813]],["curveTo",[462.000773,635.585813,553.668341,518.168148,553.668341,331.833398]],["curveTo",[553.668341,145.415346,462.000773,23.91438,325.082915,23.91438]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[268.499786,-12]],["curveTo",[396.299018,-12,476.898761,63.199487,476.898761,163.699274]],["curveTo",[476.898761,265.09953,413.798377,303.999146,337.548484,337.899188]],["lineTo",[236.697992,383.149509]],["curveTo",[189.098249,404.249466,121.697565,434.249893,121.697565,513.700982]],["curveTo",[121.697565,587.351986,181.898334,635.602734,271.399402,635.602734]],["curveTo",[338.850064,635.602734,391.400256,608.202478,431.600171,568.702264]],["lineTo",[453.198633,593.300299]],["curveTo",[414.149082,634.950021,351.299445,671.500214,271.399402,671.500214]],["curveTo",[161.700128,671.500214,82.600598,606.15079,82.600598,511.301153]],["curveTo",[82.600598,415.401111,157.850918,374.451516,216.450662,348.301581]],["lineTo",[317.401111,302.951303]],["curveTo",[382.851346,273.351132,437.701837,246.250748,437.701837,161.09953]],["curveTo",[437.701837,79.848355,372.000854,23.89748,268.699701,23.89748]],["curveTo",[190.099103,23.89748,119.548911,58.1478,72.598889,112.298163]],["lineTo",[47.200342,87.700128]],["curveTo",[98.199915,29.300299,171.09953,-12.0,268.499786,-12.0]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[197.500193,-12]],["curveTo",[261.000386,-12,320.000773,23.333591,369.667568,63.166989]],["lineTo",[371.834171,63.166989]],["lineTo",[376.167375,0.0]],["lineTo",[407.915153,0.0]],["lineTo",[407.915153,308.333977]],["curveTo",[407.915153,407.832625,371.666023,491.082915,258.833011,491.082915]],["curveTo",[180.833011,491.082915,114.499807,453.832625,82.50058,428.999614]],["lineTo",[100.666409,402.33475]],["curveTo",[131.832625,424.667955,188.833011,457.501739,255.166989,457.501739]],["curveTo",[352.33475,457.501739,372.501353,375.166989,370.834557,299.49942]],["curveTo",[156.000773,275.666023,58.833784,225.49942,58.833784,118.249517]],["curveTo",[58.833784,27.666023,122.50058,-12.0,197.500193,-12.0]],["closePath",[]],["moveTo",[202.332432,21.497874]],["curveTo",[145.831852,21.497874,97.497874,48.498261,97.497874,119.832625]],["curveTo",[97.497874,199.166989,167.165443,245.667568,370.834557,269.001159]],["lineTo",[370.834557,100.832238]],["curveTo",[309.166989,48.498261,258.833011,21.497874,202.332432,21.497874]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[272.999614,-12]],["curveTo",[344.166602,-12,385.832625,9.833398,422.665636,34.499807]],["lineTo",[406.499807,60.831465]],["curveTo",[372.499807,36.998068,328.999614,21.331272,275.999227,21.331272]],["curveTo",[171.581562,21.331272,91.664477,106.165829,91.664477,240.499807]],["curveTo",[91.664477,386.833784,179.165443,457.834944,260.833011,457.834944]],["curveTo",[348.833784,457.834944,406.501353,395.500966,406.501353,268.665636]],["lineTo",[74.664863,268.665636]],["lineTo",[74.664863,235.667568]],["lineTo",[440.165829,235.667568]],["curveTo",[442.332432,248.167375,442.499034,259.000386,442.499034,270.0]],["curveTo",[442.499034,411.500193,372.665636,491.082915,260.666409,491.082915]],["curveTo",[153.833398,491.082915,52.833784,394.999614,52.833784,238.666409]],["curveTo",[52.833784,82.0,151.500193,-12.0,272.999614,-12.0]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[249.500214,-232.600598]],["curveTo",[385.699274,-232.600598,472.998718,-152.90047,472.998718,-71.501068]],["curveTo",[472.998718,3.398548,423.799231,37.598462,316.849637,37.598462]],["lineTo",[212.398975,37.598462]],["curveTo",[139.598462,37.598462,119.598462,66.498932,119.598462,101.799231]],["curveTo",[119.598462,134.799658,138.698847,156.0,160.099103,173.550192]],["curveTo",[182.099103,160.0,213.399402,152.299872,240.799658,152.299872]],["curveTo",[333.799231,152.299872,406.698847,225.000427,406.698847,322.0]],["curveTo",[406.698847,378.950876,381.848355,418.70141,350.997864,442.301581]],["lineTo",[350.997864,446.301581]],["lineTo",[464.998718,446.301581]],["lineTo",[464.998718,479.09953]],["lineTo",[303.699274,479.09953]],["curveTo",[289.199487,485.199487,266.899616,491.09953,240.799658,491.09953]],["curveTo",[147.900043,491.09953,74.300726,419.799231,74.300726,322.399829]],["curveTo",[74.300726,264.399829,104.400683,216.499786,134.700555,190.599744]],["lineTo",[134.700555,186.599744]],["curveTo",[114.000854,172.099957,82.500641,141.800085,82.500641,98.299872]],["curveTo",[82.500641,62.0,100.600598,36.0,124.700555,21.800085]],["lineTo",[124.700555,17.800085]],["curveTo",[80.600598,-13.800085,52.800513,-57.150363,52.800513,-99.100384]],["curveTo",[52.800513,-179.600171,127.900043,-232.600598,249.500214,-232.600598]],["closePath",[]],["moveTo",[240.799658,183.998291]],["curveTo",[172.698847,183.998291,112.697992,240.19906,112.697992,322.399829]],["curveTo",[112.697992,406.400683,170.798804,458.801367,240.799658,458.801367]],["curveTo",[310.800513,458.801367,368.901324,406.400683,368.901324,322.399829]],["curveTo",[368.901324,240.19906,308.90047,183.998291,240.799658,183.998291]],["closePath",[]],["moveTo",[252.799658,-200.702264]],["curveTo",[149.198633,-200.702264,90.198206,-158.201623,90.198206,-95.80094]],["curveTo",[90.198206,-60.90047,109.498505,-22.399829,155.699274,8.600598]],["curveTo",[181.09953,2.700555,206.699701,0.700555,214.398975,0.700555]],["lineTo",[320.850491,0.700555]],["curveTo",[395.201196,0.700555,434.401538,-20.399829,434.401538,-74.90047]],["curveTo",[434.401538,-137.101239,363.900897,-200.702264,252.799658,-200.702264]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[208.199915,-12]],["curveTo",[310.19906,-12,367.098676,50.499786,367.098676,121.699274]],["curveTo",[367.098676,214.999573,284.498078,239.249039,208.498078,267.999146]],["curveTo",[150.198206,289.899188,97.697565,311.299445,97.697565,367.500214]],["curveTo",[97.697565,413.050833,132.598035,458.201623,210.099103,458.201623]],["curveTo",[259.199487,458.201623,293.349423,441.601452,325.599317,418.401538]],["lineTo",[346.298163,442.199915]],["curveTo",[311.798377,470.699701,264.898761,491.09953,211.699274,491.09953]],["curveTo",[112.699701,491.09953,59.300299,432.799658,59.300299,365.200342]],["curveTo",[59.300299,283.500214,136.300726,254.500641,210.100812,228.500641]],["curveTo",[265.950876,208.500641,328.70141,181.400256,328.70141,119.299445]],["curveTo",[328.70141,66.998718,288.100812,20.897907,210.299872,20.897907]],["curveTo",[136.398975,20.897907,94.19906,44.198206,53.899188,78.298163]],["lineTo",[33.200342,54.499786]],["curveTo",[73.800085,14.0,132.799658,-12.0,208.199915,-12.0]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[225.599317,-12]],["curveTo",[305.698419,-12,364.697992,28.299872,415.297736,84.999573]],["curveTo",[474.097394,154.749252,516.597181,245.699274,544.497223,343.599317]],["lineTo",[508.199915,343.599317]],["curveTo",[482.399829,250.19906,444.199915,169.098676,388.499786,105.898334]],["curveTo",[344.599744,56.498078,289.09953,21.097821,228.099103,21.097821]],["curveTo",[145.898334,21.097821,75.697565,79.098676,75.697565,168.899616]],["curveTo",[75.697565,330.200769,368.697992,389.001282,368.697992,553.601025]],["curveTo",[368.697992,621.000427,331.098676,671.500214,261.299445,671.500214]],["curveTo",[183.300299,671.500214,128.400683,610.400683,128.400683,525.900897]],["curveTo",[128.400683,380.850491,263.000427,195.300299,393.149509,81.65015]],["curveTo",[447.949167,33.850064,499.198633,3.800085,542.598035,-12.0]],["lineTo",[555.997437,21.497651]],["curveTo",[514.497223,36.997864,465.697565,66.39812,415.397693,109.848355]],["curveTo",[294.697992,212.598889,163.998291,391.700128,163.998291,527.80094]],["curveTo",[163.998291,588.801367,199.398548,639.302008,259.599317,639.302008]],["curveTo",[314.049979,639.302008,333.700128,594.901324,333.700128,551.700982]],["curveTo",[333.700128,401.200342,37.200342,348.498932,37.200342,166.499786]],["curveTo",[37.200342,56.399829,123.200342,-12.0,225.599317,-12.0]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[384.832238,-143.832625]],["curveTo",[452.998841,-143.832625,514.998841,-125.832625,571.415346,-90.165829]],["lineTo",[555.999227,-62.167375]],["curveTo",[508.332432,-91.500966,454.332432,-111.001159,387.332045,-111.001159]],["curveTo",[207.497874,-111.001159,86.998068,10.833011,86.998068,204.333205]],["curveTo",[86.998068,442.750483,260.165056,597.667568,442.915539,597.667568]],["curveTo",[613.831852,597.667568,721.49942,487.001159,721.49942,313.666795]],["curveTo",[721.49942,170.165829,641.498647,88.832238,577.998454,88.832238]],["curveTo",[515.497874,88.832238,493.997681,133.332818,517.16467,227.166989]],["lineTo",[551.831079,413.49942]],["lineTo",[521.915926,413.49942]],["lineTo",[513.49942,373.332818]],["lineTo",[511.49942,373.332818]],["curveTo",[491.666023,405.666023,463.832625,421.666023,427.999227,421.666023]],["curveTo",[309.999227,421.666023,242.499807,293.832625,242.499807,198.0]],["curveTo",[242.499807,107.750097,294.74971,62.667182,355.999227,62.667182]],["curveTo",[401.832625,62.667182,442.665636,94.167375,477.999227,132.833784]],["lineTo",[479.999227,132.833784]],["curveTo",[484.332432,80.667182,522.832238,56.50058,575.332045,56.50058]],["curveTo",[657.16467,56.50058,756.330886,146.667182,756.330886,315.833398]],["curveTo",[756.330886,505.082915,637.081369,630.499034,445.498647,630.499034]],["curveTo",[237.082915,630.499034,51.833398,461.49942,51.833398,202.166602]],["curveTo",[51.833398,-16.165829,192.332432,-143.832625,384.832238,-143.832625]],["closePath",[]],["moveTo",[359.915153,95.165443]],["curveTo",[314.831465,95.165443,278.164283,122.748937,278.164283,200.416506]],["curveTo",[278.164283,286.667182,334.664863,389.167762,426.415733,389.167762]],["curveTo",[457.666023,389.167762,477.49942,377.417665,502.499807,338.833784]],["lineTo",[470.999614,163.49942]],["curveTo",[430.082528,116.499034,393.082142,95.165443,359.915153,95.165443]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[241.399402,-12]],["curveTo",[356.648868,-12,433.498505,62.099957,433.498505,154.599744]],["curveTo",[433.498505,240.499786,376.04827,282.899616,321.648441,316.849637]],["lineTo",[321.648441,320.849637]],["curveTo",[358.098249,350.799658,413.098676,413.900043,413.098676,485.200342]],["curveTo",[413.098676,579.900043,350.19906,651.700128,241.699274,651.700128]],["curveTo",[150.0,651.700128,77.200342,587.500214,77.200342,496.400683]],["curveTo",[77.200342,428.500641,122.600598,380.600598,168.800513,350.200769]],["lineTo",[168.800513,346.200769]],["curveTo",[108.800513,314.200769,40.099957,251.200342,40.099957,163.900043]],["curveTo",[40.099957,63.700128,124.74968,-12.0,241.399402,-12.0]],["closePath",[]],["moveTo",[288.899616,332.398975]],["curveTo",[204.19906,365.899188,115.097821,403.09953,115.097821,496.400683]],["curveTo",[115.097821,567.001282,167.748398,619.20205,241.499359,619.20205]],["curveTo",[327.100384,619.20205,375.80094,557.101239,375.80094,484.600598]],["curveTo",[375.80094,428.099957,344.90047,377.399402,288.899616,332.398975]],["closePath",[]],["moveTo",[241.599317,20.498078]],["curveTo",[146.298163,20.498078,77.847501,84.498932,77.847501,164.899616]],["curveTo",[77.847501,236.800513,127.948313,291.601025,195.399402,330.70141]],["curveTo",[296.049979,291.101239,395.151217,256.000854,395.151217,154.899616]],["curveTo",[395.151217,80.299018,334.700555,20.498078,241.599317,20.498078]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[180.499786,257.500214]],["curveTo",[266.999573,257.500214,319.999146,331.700128,319.999146,465.500214]],["curveTo",[319.999146,597.400256,266.999573,671.500214,180.499786,671.500214]],["curveTo",[94.0,671.500214,41.000427,597.400256,41.000427,465.500214]],["curveTo",[41.000427,331.700128,94.0,257.500214,180.499786,257.500214]],["closePath",[]],["moveTo",[180.499786,286.898761]],["curveTo",[116.498932,286.898761,75.398548,352.799658,75.398548,465.500214]],["curveTo",[75.398548,578.200769,116.498932,642.101666,180.499786,642.101666]],["curveTo",[244.500641,642.101666,285.601025,578.200769,285.601025,465.500214]],["curveTo",[285.601025,352.799658,244.500641,286.898761,180.499786,286.898761]],["closePath",[]],["moveTo",[196.899616,-12.0]],["lineTo",[232.098249,-12.0]],["lineTo",[600.998718,671.500214]],["lineTo",[565.800085,671.500214]],["lineTo",[196.899616,-12.0]],["closePath",[]],["moveTo",[621.098676,-12.0]],["curveTo",[707.598462,-12.0,760.598035,62.199915,760.598035,196.0]],["curveTo",[760.598035,327.900043,707.598462,402.0,621.098676,402.0]],["curveTo",[534.598889,402.0,481.599317,327.900043,481.599317,196.0]],["curveTo",[481.599317,62.199915,534.598889,-12.0,621.098676,-12.0]],["closePath",[]],["moveTo",[621.098676,17.398548]],["curveTo",[557.097821,17.398548,515.997437,83.299445,515.997437,196.0]],["curveTo",[515.997437,308.700555,557.097821,372.601452,621.098676,372.601452]],["curveTo",[685.09953,372.601452,726.199915,308.700555,726.199915,196.0]],["curveTo",[726.199915,83.299445,685.09953,17.398548,621.098676,17.398548]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[9.000427,0]],["lineTo",[48.197352,0]],["lineTo",[184.898761,392.001709]],["curveTo",[212.299018,471.102093,235.899188,539.60188,259.399402,620.802221]],["lineTo",[263.399402,620.802221]],["curveTo",[286.999573,539.651858,310.599744,471.102093,338.0,392.001709]],["lineTo",[472.801367,0.0]],["lineTo",[514.198206,0.0]],["lineTo",[280.097394,659.500214]],["lineTo",[243.101239,659.500214]],["lineTo",[9.000427,0.0]],["closePath",[]],["moveTo",[112.999573,231.102093]],["lineTo",[407.249039,231.102093]],["lineTo",[407.249039,264.34985]],["lineTo",[112.999573,264.34985]],["lineTo",[112.999573,231.102093]],["closePath",[]],["moveTo",[164.599744,726.300726]],["curveTo",[183.999146,726.300726,198.898761,739.400256,198.898761,760.599744]],["curveTo",[198.898761,781.799231,183.999146,794.898761,164.599744,794.898761]],["curveTo",[145.200342,794.898761,130.300726,781.799231,130.300726,760.599744]],["curveTo",[130.300726,739.400256,145.200342,726.300726,164.599744,726.300726]],["closePath",[]],["moveTo",[358.398975,726.300726]],["curveTo",[377.798377,726.300726,392.697992,739.400256,392.697992,760.599744]],["curveTo",[392.697992,781.799231,377.798377,794.898761,358.398975,794.898761]],["curveTo",[338.999573,794.898761,324.099957,781.799231,324.099957,760.599744]],["curveTo",[324.099957,739.400256,338.999573,726.300726,358.398975,726.300726]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[272.899616,-12]],["curveTo",[344.099957,-12,385.699274,9.900043,422.598889,34.599744]],["lineTo",[406.599744,60.598035]],["curveTo",[372.499786,36.79795,328.999573,21.097821,275.999146,21.097821]],["curveTo",[171.448099,21.097821,91.397693,105.799231,91.197779,240.499786]],["curveTo",[90.997864,386.200769,178.898761,458.001709,260.799658,458.001709]],["curveTo",[348.90047,458.001709,406.70141,395.501068,406.70141,268.19906]],["lineTo",[74.79795,268.19906]],["lineTo",[74.897907,236.000854]],["lineTo",[440.249039,236.000854]],["curveTo",[442.299018,248.000854,442.498932,259.000427,442.498932,270.0]],["curveTo",[442.498932,411.600171,372.498932,491.09953,260.599744,491.09953]],["curveTo",[153.800085,491.09953,52.90047,394.899616,52.90047,238.599744]],["curveTo",[52.90047,82.099957,151.600171,-12.0,272.899616,-12.0]],["closePath",[]],["moveTo",[189.500214,570.0]],["lineTo",[224.498078,570.0]],["lineTo",[375.497651,717.299445]],["lineTo",[373.097821,721.999146]],["lineTo",[328.000854,721.999146]],["lineTo",[189.500214,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[9.000427,0]],["lineTo",[48.197352,0]],["lineTo",[184.898761,392.001709]],["curveTo",[212.299018,471.102093,235.899188,539.60188,259.399402,620.802221]],["lineTo",[263.399402,620.802221]],["curveTo",[286.999573,539.651858,310.599744,471.102093,338.0,392.001709]],["lineTo",[472.801367,0.0]],["lineTo",[514.198206,0.0]],["lineTo",[280.097394,659.500214]],["lineTo",[243.101239,659.500214]],["lineTo",[9.000427,0.0]],["closePath",[]],["moveTo",[112.999573,231.102093]],["lineTo",[407.249039,231.102093]],["lineTo",[407.249039,264.34985]],["lineTo",[112.999573,264.34985]],["lineTo",[112.999573,231.102093]],["closePath",[]],["moveTo",[261.499359,699.800085]],["curveTo",[317.29859,699.800085,357.798377,732.199915,357.798377,784.699701]],["curveTo",[357.798377,837.199487,317.29859,869.599317,261.499359,869.599317]],["curveTo",[205.700128,869.599317,165.200342,837.199487,165.200342,784.699701]],["curveTo",[165.200342,732.199915,205.700128,699.800085,261.499359,699.800085]],["closePath",[]],["moveTo",[261.499359,723.699274]],["curveTo",[224.19906,723.699274,194.998718,743.599317,194.998718,784.699701]],["curveTo",[194.998718,825.800085,224.19906,845.700128,261.499359,845.700128]],["curveTo",[298.799658,845.700128,328.0,825.800085,328.0,784.699701]],["curveTo",[328.0,743.599317,298.799658,723.699274,261.499359,723.699274]],["closePath",[]]]}]},{"coordinates":[777.0],"glyphs":[{"char":"A","commands":[["moveTo",[-7.834954,0]],["lineTo",[152.468461,0]],["lineTo",[233.775492,324.660185]],["curveTo",[251.128646,388.5546,267.940538,465.472077,283.023061,533.366493]],["lineTo",[287.023061,533.366493]],["curveTo",[303.834954,466.472077,320.646846,388.5546,338.0,324.660185]],["lineTo",[419.0364,0.0]],["lineTo",[584.798553,0.0]],["lineTo",[382.739092,651.082523]],["lineTo",[194.224508,651.082523]],["lineTo",[-7.834954,0.0]],["closePath",[]],["moveTo",[129.834954,148.5546]],["lineTo",[445.128646,148.5546]],["lineTo",[445.128646,270.188108]],["lineTo",[129.834954,270.188108]],["lineTo",[129.834954,148.5546]],["closePath",[]]]},{"char":"E","commands":[["moveTo",[75.03125,0]],["lineTo",[497.886161,0]],["lineTo",[497.886161,131.441965]],["lineTo",[231.875001,131.441965]],["lineTo",[231.875001,268.444196]],["lineTo",[450.154018,268.444196]],["lineTo",[450.154018,399.886161]],["lineTo",[231.875001,399.886161]],["lineTo",[231.875001,519.640624]],["lineTo",[487.886161,519.640624]],["lineTo",[487.886161,651.082589]],["lineTo",[75.03125,651.082589]],["lineTo",[75.03125,0.0]],["closePath",[]]]},{"char":"O","commands":[["moveTo",[343.564323,-12]],["curveTo",[521.128646,-12,642.046123,115.082523,642.046123,328.541262]],["curveTo",[642.046123,541.0,521.128646,663.082523,343.564323,663.082523]],["curveTo",[166.270631,663.082523,45.353154,541.729369,45.353154,328.541262]],["curveTo",[45.353154,115.082523,166.270631,-12.0,343.564323,-12.0]],["closePath",[]],["moveTo",[343.564323,123.280353]],["curveTo",[258.798553,123.280353,205.927199,202.692969,205.927199,328.541262]],["curveTo",[205.927199,454.118923,258.798553,527.80217,343.564323,527.80217]],["curveTo",[428.330092,527.80217,481.201447,454.118923,481.201447,328.541262]],["curveTo",[481.201447,202.692969,428.330092,123.280353,343.564323,123.280353]],["closePath",[]]]},{"char":"S","commands":[["moveTo",[276.917411,-12]],["curveTo",[435.02009,-12,525.720983,83.401786,525.720983,192.319197]],["curveTo",[525.720983,283.618304,477.772322,337.669643,397.313617,369.886161]],["lineTo",[315.823662,402.510045]],["curveTo",[258.122769,425.293527,217.658483,438.458705,217.658483,474.97991]],["curveTo",[217.658483,509.068079,247.555804,527.857142,294.96875,527.857142]],["curveTo",[336.324777,527.857142,381.299107,510.558035,424.866071,479.475445]],["lineTo",[507.071429,581.515625]],["curveTo",[450.344866,634.108259,373.185268,663.082589,294.96875,663.082589]],["curveTo",[156.649554,663.082589,59.03125,575.00558,59.03125,465.845982]],["curveTo",[59.03125,371.629464,121.655134,314.686383,190.356026,286.01116]],["lineTo",[273.629464,251.603794]],["curveTo",[329.820312,228.737723,365.310267,216.789062,365.310267,179.618304]],["curveTo",[365.310267,144.664063,338.330357,123.225448,280.484375,123.225448]],["curveTo",[225.453125,123.225448,162.478795,144.849331,116.370536,184.689733]],["lineTo",[33.732143,82.649554]],["curveTo",[101.566964,17.515625,189.618304,-12.0,276.917411,-12.0]],["closePath",[]]]},{"char":"a","commands":[["moveTo",[189.082523,-12]],["curveTo",[244.165046,-12,289.247569,13.082523,330.871354,49.623785]],["lineTo",[334.871354,49.623785]],["lineTo",[345.788831,0.0]],["lineTo",[473.610446,0.0]],["lineTo",[473.610446,281.247569]],["curveTo",[473.610446,438.211169,400.211169,509.564323,272.376215,509.564323]],["curveTo",[194.376215,509.564323,122.917477,484.211169,57.247569,444.376215]],["lineTo",[112.376215,343.0364]],["curveTo",[162.211169,370.495139,202.376215,384.660185,241.623785,384.660185]],["curveTo",[291.577662,384.660185,313.577662,361.623785,317.0364,321.834954]],["curveTo",[122.330092,301.293692,40.165046,244.917477,40.165046,139.293692]],["curveTo",[40.165046,56.211169,97.247569,-12.0,189.082523,-12.0]],["closePath",[]],["moveTo",[242.587384,109.71603]],["curveTo",[209.881077,109.71603,190.092246,123.527923,190.092246,151.669908]],["curveTo",[190.092246,185.623785,220.9636,212.706308,317.0364,225.788831]],["lineTo",[317.0364,148.046123]],["curveTo",[294.165046,124.257292,273.834954,109.71603,242.587384,109.71603]],["closePath",[]]]},{"char":"e","commands":[["moveTo",[288.376215,-12]],["curveTo",[346.0,-12,414.752431,8.0,466.587384,44.376215]],["lineTo",[416.376215,138.798553]],["curveTo",[380.917477,118.257292,345.834954,107.71603,309.669908,107.71603]],["curveTo",[246.798553,107.71603,183.174769,137.293692,180.257292,248.917477]],["curveTo",[177.339815,357.953877,228.587384,390.118923,274.376215,390.118923]],["curveTo",[330.165046,390.118923,350.495139,353.412616,350.495139,306.752431]],["lineTo",[155.174769,306.752431]],["lineTo",[156.633507,201.247569]],["lineTo",[477.858015,201.247569]],["curveTo",[481.128646,214.871354,484.587384,242.165046,484.587384,270.0]],["curveTo",[484.587384,404.541262,415.128646,509.564323,270.917477,509.564323]],["curveTo",[150.541262,509.564323,34.165046,410.376215,34.165046,248.917477]],["curveTo",[34.165046,83.458738,144.541262,-12.0,288.376215,-12.0]],["closePath",[]]]},{"char":"g","commands":[["moveTo",[241.082589,-209.03125]],["curveTo",[414.319197,-209.03125,523.504465,-134.381696,523.504465,-29.412946]],["curveTo",[523.504465,60.638394,454.102679,98.205358,331.159598,98.205358]],["lineTo",[252.803572,98.205358]],["curveTo",[200.205358,98.205358,180.205358,108.587054,180.205358,132.102679]],["curveTo",[180.205358,148.267857,184.154018,156.0,195.453125,165.97433]],["curveTo",[217.453125,160.0,236.96875,157.350446,254.267857,157.350446]],["curveTo",[364.102679,157.350446,452.154018,208.165178,452.154018,322.0]],["curveTo",[452.154018,344.438616,446.664063,363.145089,435.174108,380.01116]],["lineTo",[435.174108,384.01116]],["lineTo",[515.504465,384.01116]],["lineTo",[515.504465,497.618304]],["lineTo",[332.319197,497.618304]],["curveTo",[309.401786,505.401786,282.051339,509.618304,254.267857,509.618304]],["curveTo",[146.216518,509.618304,45.680803,450.102679,45.680803,329.133929]],["curveTo",[45.680803,271.133929,77.464285,224.917411,112.814732,200.700893]],["lineTo",[112.814732,196.700893]],["curveTo",[80.330357,173.783482,57.247768,138.433036,57.247768,103.350446]],["curveTo",[57.247768,62.0,77.03125,36.0,102.814732,18.433036]],["lineTo",[102.814732,14.433036]],["curveTo",[57.03125,-10.433036,32.598214,-42.840402,32.598214,-83.948661]],["curveTo",[32.598214,-172.866071,126.216518,-209.03125,241.082589,-209.03125]],["closePath",[]],["moveTo",[254.267857,251.339286]],["curveTo",[218.154018,251.339286,191.823662,277.236608,191.823662,329.133929]],["curveTo",[191.823662,379.464285,217.937501,404.928571,254.267857,404.928571]],["curveTo",[290.598214,404.928571,316.712053,379.464285,316.712053,329.133929]],["curveTo",[316.712053,277.236608,290.381696,251.339286,254.267857,251.339286]],["closePath",[]],["moveTo",[266.267857,-111.475445]],["curveTo",[203.071429,-111.475445,160.906251,-94.227678,160.906251,-58.763392]],["curveTo",[160.906251,-42.381696,168.421876,-29.133929,184.319197,-14.96875]],["curveTo",[199.618304,-19.185268,218.484375,-21.185268,254.803572,-21.185268]],["lineTo",[301.489955,-21.185268]],["curveTo",[348.062499,-21.185268,373.794642,-27.133929,373.794642,-56.381696]],["curveTo",[373.794642,-88.279017,328.546875,-111.475445,266.267857,-111.475445]],["closePath",[]]]},{"char":"s","commands":[["moveTo",[211.566964,-12]],["curveTo",[347.236608,-12,419.287947,58.917411,419.287947,150.319197]],["curveTo",[419.287947,231.834822,360.256697,277.128349,284.256697,301.669643]],["curveTo",[220.906251,321.886161,193.658483,333.185268,193.658483,359.082589]],["curveTo",[193.658483,380.222098,210.040179,394.227678,245.453125,394.227678]],["curveTo",[279.401786,394.227678,316.077009,384.361606,352.535715,357.794642]],["lineTo",[418.689733,445.566964]],["curveTo",[375.772322,482.484375,313.720983,509.618304,240.319197,509.618304]],["curveTo",[124.484375,509.618304,47.515625,446.267857,47.515625,351.732143]],["curveTo",[47.515625,275.082589,107.680803,229.247768,178.113839,203.247768]],["curveTo",[231.438616,183.247768,273.145089,171.299107,273.145089,141.185268]],["curveTo",[273.145089,117.504465,256.113839,103.390626,215.350446,103.390626]],["curveTo",[176.803572,103.390626,131.236608,114.906251,85.886161,150.689733]],["lineTo",[19.732143,62.917411]],["curveTo",[70.433036,14.0,146.267857,-12.0,211.566964,-12.0]],["closePath",[]]]},{"char":"&","commands":[["moveTo",[252.535715,-12]],["curveTo",[367.98884,-12,443.823662,33.350446,504.524555,101.834822]],["curveTo",[576.792412,184.210938,627.709823,274.319197,653.926341,370.535715]],["lineTo",[511.566964,370.535715]],["curveTo",[489.133929,287.236608,447.566964,221.287947,396.917411,171.555804]],["curveTo",[354.700893,132.256697,307.618304,106.95759,263.453125,106.95759]],["curveTo",[211.555804,106.95759,171.658483,131.287947,171.658483,184.051339]],["curveTo",[171.658483,299.897321,447.823662,338.495535,447.823662,513.196428]],["curveTo",[447.823662,604.165178,383.287947,663.082589,283.185268,663.082589]],["curveTo",[171.515625,663.082589,101.464285,583.464285,101.464285,490.546875]],["curveTo",[101.464285,361.489955,246.165178,183.515625,412.510045,75.757812]],["curveTo",[480.777902,31.324777,553.071429,0.433036,620.040179,-12.0]],["lineTo",[657.00893,114.091519]],["curveTo",[623.926341,121.174108,561.658483,140.473215,506.308037,174.664063]],["curveTo",[373.823662,256.370536,231.339286,386.649554,231.339286,490.763392]],["curveTo",[231.339286,534.928571,256.638394,560.176338,286.535715,560.176338]],["curveTo",[314.891741,560.176338,328.649554,542.712053,328.649554,512.97991]],["curveTo",[328.649554,387.732143,23.732143,390.587054,23.732143,174.917411]],["curveTo",[23.732143,63.133929,109.732143,-12.0,252.535715,-12.0]],["closePath",[]]]},{"char":"@","commands":[["moveTo",[432.046123,-174.211169]],["curveTo",[503.504861,-174.211169,566.9636,-159.128646,628.692969,-127.128646]],["lineTo",[594.046123,-40.541262]],["curveTo",[552.9636,-61.082523,494.587384,-76.541262,444.422338,-76.541262]],["curveTo",[292.798553,-76.541262,156.587384,14.165046,156.587384,209.458738]],["curveTo",[156.587384,431.188108,323.504861,574.917477,491.775492,574.917477]],["curveTo",[688.092246,574.917477,761.339815,446.706308,761.339815,308.541262]],["curveTo",[761.339815,202.752431,706.257292,133.128646,646.798553,133.128646]],["curveTo",[605.174769,133.128646,590.633507,158.211169,603.174769,213.623785]],["lineTo",[649.550984,437.293692]],["lineTo",[553.940538,437.293692]],["lineTo",[538.752431,395.293692]],["lineTo",[536.752431,395.293692]],["curveTo",[520.211169,431.293692,494.211169,445.834954,461.669908,445.834954]],["curveTo",[346.587384,445.834954,256.752431,322.752431,256.752431,198.0]],["curveTo",[256.752431,105.729369,311.023061,45.082523,391.128646,45.082523]],["curveTo",[432.211169,45.082523,485.128646,71.082523,511.669908,112.706308]],["lineTo",[513.669908,112.706308]],["curveTo",[524.587384,63.082523,572.234231,35.623785,630.9636,35.623785]],["curveTo",[738.798553,35.623785,862.468461,127.623785,862.468461,312.541262]],["curveTo",[862.468461,523.564323,722.904138,672.587384,504.422338,672.587384]],["curveTo",[255.564323,672.587384,48.541262,486.752431,48.541262,205.458738]],["curveTo",[48.541262,-53.128646,231.128646,-174.211169,432.046123,-174.211169]],["closePath",[]],["moveTo",[425.610446,144.587384]],["curveTo",[394.257292,144.587384,376.633507,166.128646,376.633507,207.188108]],["curveTo",[376.633507,264.706308,410.798553,344.871354,466.858015,344.871354]],["curveTo",[486.211169,344.871354,502.023061,335.141985,510.917477,318.706308]],["lineTo",[487.834954,187.293692]],["curveTo",[463.940538,155.669908,445.234231,144.587384,425.610446,144.587384]],["closePath",[]]]},{"char":"8","commands":[["moveTo",[264.96875,-12]],["curveTo",[401.262277,-12,492.421876,63.783482,492.421876,164.700893]],["curveTo",[492.421876,248.917411,444.231028,298.051339,383.097099,331.159598]],["lineTo",[383.097099,335.159598]],["curveTo",[427.122769,364.267857,465.287947,412.216518,465.287947,471.732143]],["curveTo",[465.287947,578.216518,387.236608,646.649554,270.319197,646.649554]],["curveTo",[150.0,646.649554,63.732143,579.082589,63.732143,469.464285]],["curveTo",[63.732143,403.247768,99.03125,357.03125,148.598214,319.897321]],["lineTo",[148.598214,315.897321]],["curveTo",[88.598214,283.897321,41.783482,237.732143,41.783482,162.216518]],["curveTo",[41.783482,58.649554,137.376116,-12.0,264.96875,-12.0]],["closePath",[]],["moveTo",[304.051339,372.803572]],["curveTo",[241.236608,397.886161,200.95759,421.618304,200.95759,469.464285]],["curveTo",[200.95759,516.495535,230.880581,538.392856,266.752232,538.392856]],["curveTo",[311.948661,538.392856,338.763392,508.279017,338.763392,461.03125]],["curveTo",[338.763392,429.783482,326.381696,400.96875,304.051339,372.803572]],["closePath",[]],["moveTo",[268.535715,96.256697]],["curveTo",[218.689733,96.256697,176.333706,126.587054,176.333706,180.051339]],["curveTo",[176.333706,216.598214,194.447545,251.196428,218.96875,275.145089]],["curveTo",[296.891741,242.279017,347.170758,222.330357,347.170758,170.051339]],["curveTo",[347.170758,119.02009,312.814732,96.256697,268.535715,96.256697]],["closePath",[]]]},{"char":"%","commands":[["moveTo",[188.917411,249.082589]],["curveTo",[283.834822,249.082589,353.669643,326.649554,353.669643,457.082589]],["curveTo",[353.669643,587.299107,283.834822,663.082589,188.917411,663.082589]],["curveTo",[94.0,663.082589,24.165178,587.299107,24.165178,457.082589]],["curveTo",[24.165178,326.649554,94.0,249.082589,188.917411,249.082589]],["closePath",[]],["moveTo",[188.917411,335.720983]],["curveTo",[158.587054,335.720983,132.638394,366.267857,132.638394,457.082589]],["curveTo",[132.638394,547.897321,158.587054,576.444196,188.917411,576.444196]],["curveTo",[219.247768,576.444196,245.196428,547.897321,245.196428,457.082589]],["curveTo",[245.196428,366.267857,219.247768,335.720983,188.917411,335.720983]],["closePath",[]],["moveTo",[212.051339,-12.0]],["lineTo",[301.122769,-12.0]],["lineTo",[651.504465,663.082589]],["lineTo",[562.433036,663.082589]],["lineTo",[212.051339,-12.0]],["closePath",[]],["moveTo",[673.287947,-12.0]],["curveTo",[768.205358,-12.0,838.040179,65.566964,838.040179,196.0]],["curveTo",[838.040179,326.216518,768.205358,402.0,673.287947,402.0]],["curveTo",[578.370536,402.0,508.535715,326.216518,508.535715,196.0]],["curveTo",[508.535715,65.566964,578.370536,-12.0,673.287947,-12.0]],["closePath",[]],["moveTo",[673.287947,74.638394]],["curveTo",[642.95759,74.638394,617.00893,105.185268,617.00893,196.0]],["curveTo",[617.00893,286.814732,642.95759,315.361606,673.287947,315.361606]],["curveTo",[703.618304,315.361606,729.566964,286.814732,729.566964,196.0]],["curveTo",[729.566964,105.185268,703.618304,74.638394,673.287947,74.638394]],["closePath",[]]]},{"char":"Ä","commands":[["moveTo",[-7.834822,0]],["lineTo",[152.575894,0]],["lineTo",[233.720983,324.660714]],["curveTo",[251.02009,388.609374,267.886161,465.526785,282.96875,533.258928]],["lineTo",[286.96875,533.258928]],["curveTo",[303.834822,466.418526,320.700893,388.609374,338.0,324.660714]],["lineTo",[418.928571,0.0]],["lineTo",[584.906251,0.0]],["lineTo",[382.792412,651.082589]],["lineTo",[194.279017,651.082589]],["lineTo",[-7.834822,0.0]],["closePath",[]],["moveTo",[129.834822,148.609374]],["lineTo",[445.128349,148.609374]],["lineTo",[445.128349,270.242188]],["lineTo",[129.834822,270.242188]],["lineTo",[129.834822,148.609374]],["closePath",[]],["moveTo",[174.700893,697.680803]],["curveTo",[217.669643,697.680803,247.720983,729.299107,247.720983,770.700893]],["curveTo",[247.720983,812.102679,217.669643,843.720983,174.700893,843.720983]],["curveTo",[131.732143,843.720983,101.680803,812.102679,101.680803,770.700893]],["curveTo",[101.680803,729.299107,131.732143,697.680803,174.700893,697.680803]],["closePath",[]],["moveTo",[398.803572,697.680803]],["curveTo",[441.772322,697.680803,471.823662,729.299107,471.823662,770.700893]],["curveTo",[471.823662,812.102679,441.772322,843.720983,398.803572,843.720983]],["curveTo",[355.834822,843.720983,325.783482,812.102679,325.783482,770.700893]],["curveTo",[325.783482,729.299107,355.834822,697.680803,398.803572,697.680803]],["closePath",[]]]},{"char":"é","commands":[["moveTo",[288.051339,-12]],["curveTo",[345.783482,-12,414.319197,8.216518,466.370536,44.700893]],["lineTo",[416.700893,138.040179]],["curveTo",[380.917411,117.607144,345.834822,106.95759,309.669643,106.95759]],["curveTo",[246.364956,106.95759,182.308037,136.102679,178.741072,248.917411]],["curveTo",[175.174108,355.897321,227.720983,390.660714,274.267857,390.660714]],["curveTo",[330.381696,390.660714,351.145089,353.412946,351.145089,305.236608]],["lineTo",[155.607144,305.236608]],["lineTo",[157.390626,202.330357]],["lineTo",[478.128349,202.330357]],["curveTo",[481.02009,214.330357,484.587054,242.165178,484.587054,270.0]],["curveTo",[484.587054,404.866071,414.587054,509.618304,270.700893,509.618304]],["curveTo",[150.433036,509.618304,34.381696,410.051339,34.381696,248.700893]],["curveTo",[34.381696,83.783482,144.866071,-12.0,288.051339,-12.0]],["closePath",[]],["moveTo",[181.082589,570.0]],["lineTo",[300.256697,570.0]],["lineTo",[468.091519,739.185268]],["lineTo",[458.95759,755.669643]],["lineTo",[294.330357,755.669643]],["lineTo",[181.082589,570.0]],["closePath",[]]]},{"char":"Å","commands":[["moveTo",[-7.834822,0]],["lineTo",[152.575894,0]],["lineTo",[233.720983,324.660714]],["curveTo",[251.02009,388.609374,267.886161,465.526785,282.96875,533.258928]],["lineTo",[286.96875,533.258928]],["curveTo",[303.834822,466.418526,320.700893,388.609374,338.0,324.660714]],["lineTo",[418.928571,0.0]],["lineTo",[584.906251,0.0]],["lineTo",[382.792412,651.082589]],["lineTo",[194.279017,651.082589]],["lineTo",[-7.834822,0.0]],["closePath",[]],["moveTo",[129.834822,148.609374]],["lineTo",[445.128349,148.609374]],["lineTo",[445.128349,270.242188]],["lineTo",[129.834822,270.242188]],["lineTo",[129.834822,148.609374]],["closePath",[]],["moveTo",[286.752232,696.433036]],["curveTo",[372.854911,696.433036,421.772322,735.566964,421.772322,796.484375]],["curveTo",[421.772322,857.401786,372.854911,896.535715,286.752232,896.535715]],["curveTo",[200.649554,896.535715,151.732143,857.401786,151.732143,796.484375]],["curveTo",[151.732143,735.566964,200.649554,696.433036,286.752232,696.433036]],["closePath",[]],["moveTo",[286.752232,752.319197]],["curveTo",[261.236608,752.319197,245.504465,770.535715,245.504465,796.484375]],["curveTo",[245.504465,822.433036,261.236608,840.649554,286.752232,840.649554]],["curveTo",[312.267857,840.649554,328.0,822.433036,328.0,796.484375]],["curveTo",[328.0,770.535715,312.267857,752.319197,286.752232,752.319197]],["closePath",[]]]}]}],"chars":" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÄÖÜäöüéÅ","glyphs":[1,77,80,94,95,96,62,79,83,84,92,97,74,81,73,89,63,64,65,66,67,68,69,70,71,72,75,76,99,98,100,78,93,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,85,91,86,101,82,103,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,87,90,88,102,54,56,57,58,60,61,59,55]}] \ No newline at end of file diff --git a/tests/CADability.Tests/Files/Fonts/VarTestSans.ttf b/tests/CADability.Tests/Files/Fonts/VarTestSans.ttf new file mode 100644 index 000000000..0e85b6a27 Binary files /dev/null and b/tests/CADability.Tests/Files/Fonts/VarTestSans.ttf 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 000000000..f26256056 --- /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/Files/Fonts/make_reference.py b/tests/CADability.Tests/Files/Fonts/make_reference.py new file mode 100644 index 000000000..2eb4ab1a1 --- /dev/null +++ b/tests/CADability.Tests/Files/Fonts/make_reference.py @@ -0,0 +1,30 @@ +# Writes LiberationSans-Regular.reference.json, the reference data of TrueTypeFontTests, with fontTools +# (pip install fonttools), an implementation independent of CADability's TrueType reader. +# Run from this directory: python make_reference.py +import json +from fontTools.ttLib import TTFont + +font = TTFont('LiberationSans-Regular.ttf') +glyf, cmap, hmtx = font['glyf'], font.getBestCmap(), font['hmtx'] +# printable ASCII, composite glyphs (accents, also with negative byte offsets: ÌÎïμ) and some curves +chars = ''.join(chr(c) for c in range(32, 127)) + 'ÄÖÜäöüßéÅ€°±ÌÎïμ' +glyphs = [] +for ch in chars: + name = cmap[ord(ch)] + coords, endPts, flags = glyf[name].getCoordinates(glyf) + contours, start = [], 0 + for end in endPts: + contours.append([[int(round(coords[i][0])), int(round(coords[i][1])), flags[i] & 1] for i in range(start, end + 1)]) + start = end + 1 + glyphs.append({'char': ch, 'glyph': font.getGlyphID(name), 'advance': hmtx[name][0], + 'composite': glyf[name].isComposite(), 'contours': contours}) +kern = font['kern'].kernTables[0].kernTable +pairs = [{'left': a, 'right': b, 'value': kern.get((cmap[ord(a)], cmap[ord(b)]), 0)} + for a, b in ['AV', 'To', 'LT', 'AT', 'Ya', 'F.']] +names = font['name'] +reference = {'family': names.getDebugName(1), 'subfamily': names.getDebugName(2), 'full': names.getDebugName(4), + 'unitsPerEm': font['head'].unitsPerEm, 'ascender': font['hhea'].ascent, 'descender': font['hhea'].descent, + 'lineGap': font['hhea'].lineGap, 'winAscent': font['OS/2'].usWinAscent, 'winDescent': font['OS/2'].usWinDescent, + 'glyphCount': font['maxp'].numGlyphs, 'kerningPairCount': len(kern), 'kerning': pairs, 'glyphs': glyphs} +with open('LiberationSans-Regular.reference.json', 'w', encoding='utf-8') as f: + json.dump(reference, f, ensure_ascii=False, separators=(',', ':')) diff --git a/tests/CADability.Tests/Files/Fonts/make_variable_test_fonts.py b/tests/CADability.Tests/Files/Fonts/make_variable_test_fonts.py new file mode 100644 index 000000000..01f5cd208 --- /dev/null +++ b/tests/CADability.Tests/Files/Fonts/make_variable_test_fonts.py @@ -0,0 +1,125 @@ +# Writes the variable test fonts of VariableFontTests and their reference data with fontTools (pip install fonttools): +# +# VarTestSans.ttf TrueType outlines with "gvar" (sparse deltas with IUP, intermediate regions, composite glyphs), +# "avar" and "HVAR" +# VarTestSans-NoHVAR.ttf the same without "HVAR": the advance widths come from the phantom points of "gvar" +# VarTestSans.otf CFF2 outlines (blend, vsindex) with "avar" and "HVAR" +# VarTestSans.reference.json the outlines and advance widths as fontTools computes them at a named instance and two +# other locations, for a selection of characters (all advance widths and the strikeout line, +# which "MVAR" varies, at all named instances) +# +# They are subsets of Source Sans 3 VF (SIL Open Font License 1.1, https://github.com/adobe-fonts/source-sans), taken from +# the npm package "source-sans". "Source" is a Reserved Font Name of these fonts, so the subsets, which are modified versions, +# are renamed to "VarTest Sans" (the three files get different family names, VarTestSans, VarTestSansNoHVAR and +# VarTestSansCFF2, so that a font provider can tell them apart). Run from this directory with the directory of SourceSans3VF-Upright.ttf and .otf: +# python make_variable_test_fonts.py /VF +import json, os, sys +from fontTools.ttLib import TTFont +from fontTools.subset import Subsetter, Options +from fontTools.pens.basePen import BasePen +from fontTools.varLib import instancer + +UNICODES = list(range(0x20, 0x7F)) + [0xC4, 0xD6, 0xDC, 0xE4, 0xF6, 0xFC, 0xE9, 0xC5] +OUTLINES = 'AEOSaegs&@8%ÄéÅ' # the characters whose outlines are compared, composite glyphs included + + +class CubicPen(BasePen): + """Records the outline as moveTo, lineTo, curveTo and closePath; quadratic segments become cubic ones without loss.""" + + def __init__(self, glyphSet): + super().__init__(glyphSet) + self.value = [] + + def _moveTo(self, p): + self.value.append(['moveTo', list(p)]) + + def _lineTo(self, p): + self.value.append(['lineTo', list(p)]) + + def _curveToOne(self, p1, p2, p3): + self.value.append(['curveTo', list(p1) + list(p2) + list(p3)]) + + def _qCurveToOne(self, p1, p2): + p0 = self._getCurrentPoint() + c1 = (p0[0] + 2 / 3 * (p1[0] - p0[0]), p0[1] + 2 / 3 * (p1[1] - p0[1])) + c2 = (p2[0] + 2 / 3 * (p1[0] - p2[0]), p2[1] + 2 / 3 * (p1[1] - p2[1])) + self.value.append(['curveTo', list(c1) + list(c2) + list(p2)]) + + def _closePath(self): + self.value.append(['closePath', []]) + + def _endPath(self): + self.value.append(['closePath', []]) + + +def make(source, target, family, drop_hvar=False): + options = Options() + options.layout_features = [] + options.name_IDs = ['*'] + options.drop_tables += ['DSIG', 'BASE', 'GDEF', 'GPOS', 'GSUB'] + font = TTFont(source) + subsetter = Subsetter(options) + subsetter.populate(unicodes=UNICODES) + subsetter.subset(font) + if drop_hvar: + del font['HVAR'] + names = font['name'] + for record in list(names.names): + if record.nameID == 7: # the trademark notice of the original name + names.removeNames(nameID=7) + continue + if record.nameID == 0: # the copyright stays + continue + text = record.toUnicode() + for old, new in (('Source Sans 3', family), ('SourceSans3VF', family), ('SourceSans3', family), ('Source Sans', family)): + text = text.replace(old, new) + record.string = text + remaining = [r.toUnicode() for r in names.names if r.nameID != 0 and 'Source' in r.toUnicode()] + assert not remaining, remaining + font.save(target) + + +def reference(path): + font = TTFont(path) + axes = font['fvar'].axes + instances = [dict(i.coordinates) for i in font['fvar'].instances] + order = font.getGlyphOrder() + cmap = font.getBestCmap() + r = lambda v: round(v, 6) + result = {'file': os.path.basename(path), 'family': font['name'].getDebugName(1), + 'instances': [font['name'].getDebugName(i.subfamilyNameID) for i in font['fvar'].instances], + 'advances': [], 'locations': []} + for loc in instances: # all advance widths and the strikeout line ("MVAR") at all named instances + glyphSet = font.getGlyphSet(location=loc, normalized=False) + static = instancer.instantiateVariableFont(TTFont(path), loc) + result['advances'].append({'coordinates': [loc[a.axisTag] for a in axes], + 'widths': [r(glyphSet[cmap[c]].width) for c in UNICODES], + 'strikeout': [static['OS/2'].yStrikeoutPosition, static['OS/2'].yStrikeoutSize]}) + result['chars'] = ''.join(chr(c) for c in UNICODES) + result['glyphs'] = [order.index(cmap[c]) for c in UNICODES] + for loc in ({'wght': 300.0}, {'wght': 250.0}, {'wght': 777.0}): # the outlines at a named instance and between them + glyphSet = font.getGlyphSet(location=loc, normalized=False) + glyphs = [] + for ch in OUTLINES: + pen = CubicPen(glyphSet) + glyphSet[cmap[ord(ch)]].draw(pen) + glyphs.append({'char': ch, 'commands': [[op, [r(v) for v in args]] for op, args in pen.value]}) + result['locations'].append({'coordinates': [loc[a.axisTag] for a in axes], 'glyphs': glyphs}) + return result + + +def write_reference(): + data = [reference(f) for f in ('VarTestSans.ttf', 'VarTestSans-NoHVAR.ttf', 'VarTestSans.otf')] + with open('VarTestSans.reference.json', 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, separators=(',', ':')) + + +def main(directory): + make(os.path.join(directory, 'SourceSans3VF-Upright.ttf'), 'VarTestSans.ttf', 'VarTestSans') + make(os.path.join(directory, 'SourceSans3VF-Upright.ttf'), 'VarTestSans-NoHVAR.ttf', 'VarTestSansNoHVAR', drop_hvar=True) + make(os.path.join(directory, 'SourceSans3VF-Upright.otf'), 'VarTestSans.otf', 'VarTestSansCFF2') + write_reference() + + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/tests/CADability.Tests/FontCacheGlyphTests.cs b/tests/CADability.Tests/FontCacheGlyphTests.cs index d0eaf1156..04f64367b 100644 --- a/tests/CADability.Tests/FontCacheGlyphTests.cs +++ b/tests/CADability.Tests/FontCacheGlyphTests.cs @@ -7,7 +7,8 @@ namespace CADability.Tests { /// /// Sends all printable ASCII characters of installed fonts through - /// and checks that every glyph with a closed outline is painted with triangles. Needs Windows (GDI) and the font. + /// and checks that every glyph with a closed outline is painted with triangles. Needs the font: Liberation Sans comes with + /// the tests (see ), the others are Windows fonts. /// [TestClass] public class FontCacheGlyphTests @@ -105,7 +106,6 @@ public void SetClip(Rectangle clipRectangle) { } private static string CheckFontAvailable(string fontName) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "glyph outlines need GDI, which is only available on Windows"; if (!GeoObject.Text.FontFamilyNames.Contains(fontName.ToUpper())) { // name the installed families that start with the same word, which shows a misspelled or a wrong instance name @@ -145,6 +145,7 @@ private static (List withoutTriangles, List withoutList) CheckFont(s } [DataTestMethod] + [DataRow("Liberation Sans")] [DataRow("Arial")] [DataRow("Calibri")] [DataRow("Bahnschrift")] @@ -179,18 +180,17 @@ public void ReportGlyphsWithoutTrianglesForAllInstalledFonts() { if (!System.Diagnostics.Debugger.IsAttached && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CADABILITY_FONT_SURVEY"))) Assert.Inconclusive("survey over all installed fonts, takes a few minutes: start it with Debug Test or set CADABILITY_FONT_SURVEY=1"); - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Assert.Inconclusive("glyph outlines need GDI, which is only available on Windows"); int fontsWithProblems = 0; - foreach (FontFamily ff in FontFamily.Families) + foreach (string family in FontProvider.Current.GetFamilyNames()) { try { - var (withoutTriangles, withoutList) = CheckFont(ff.Name, (int)FontStyle.Regular, TestContext); + var (withoutTriangles, withoutList) = CheckFont(family, (int)FontStyle.Regular, TestContext); if (withoutTriangles.Count + withoutList.Count > 0) ++fontsWithProblems; } catch (Exception ex) { - TestContext.WriteLine("{0}: exception {1}", ff.Name, ex.Message); + TestContext.WriteLine("{0}: exception {1}", family, ex.Message); ++fontsWithProblems; } } diff --git a/tests/CADability.Tests/FontCodePointTests.cs b/tests/CADability.Tests/FontCodePointTests.cs new file mode 100644 index 000000000..045025aa0 --- /dev/null +++ b/tests/CADability.Tests/FontCodePointTests.cs @@ -0,0 +1,115 @@ +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Drawing; + +namespace CADability.Tests +{ + /// + /// takes Unicode code points, so that characters outside the Basic Multilingual Plane, e.g. most + /// emoji, which are a surrogate pair of two chars in a string, reach the font as one character (issue #412). + /// + [TestClass] + public class FontCodePointTests + { + private const int Emoji = 0x1F600; // GRINNING FACE, "😀" in a string + + /// + /// A face that records the characters it is asked for. Every character is a square from 0.1 to 0.4 with an advance + /// width of 0.5 (in units of the em height). + /// + private sealed class RecordingFace : FontFace + { + public readonly List Glyphs = new List(), Widths = new List(); + public readonly List<(int, int)> KerningPairs = new List<(int, int)>(); + public override string FamilyName => "Recording"; + public override bool IsBold => false; + public override bool IsItalic => false; + public override int EmHeight => 1000; + public override int CellAscent => 800; + public override int CellDescent => 200; + public override int LineSpacing => 1000; + public override double GetAdvanceWidth(int codePoint) + { + lock (Widths) Widths.Add(codePoint); + return 0.5; + } + public override double GetKerning(int left, int right) + { + lock (KerningPairs) KerningPairs.Add((left, right)); + return 0.0; + } + public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types) + { + lock (Glyphs) Glyphs.Add(codePoint); + points = new[] { new PointF(0.1f, 0.2f), new PointF(0.4f, 0.2f), new PointF(0.4f, 0.6f), new PointF(0.1f, 0.6f) }; + types = new byte[] { 0, 1, 1, 0x81 }; + } + } + private sealed class RecordingProvider : FontProvider + { + public readonly RecordingFace Face = new RecordingFace(); + public override string[] GetFamilyNames() => new[] { "Recording" }; + public override FontFace GetFace(string familyName, bool bold, bool italic) => string.Equals(familyName, "Recording", StringComparison.OrdinalIgnoreCase) ? Face : null; + } + + private static bool IsSurrogate(int c) => c >= 0xD800 && c <= 0xDFFF; + + [TestMethod] + public void ASurrogatePairIsOneCodePoint() + { + CollectionAssert.AreEqual(new[] { 'a', Emoji, 'b' }, Text.CodePoints("a😀b")); + // a lone surrogate is no code point, it is kept as the char it is + CollectionAssert.AreEqual(new[] { 0xD83D, 'x' }, Text.CodePoints("\uD83Dx")); + CollectionAssert.AreEqual(new[] { 'x', 0xDE00 }, Text.CodePoints("x\uDE00")); + CollectionAssert.AreEqual(new int[0], Text.CodePoints("")); + } + + [TestMethod] + [DoNotParallelize] + public void AnEmojiReachesTheFontAsOneCharacter() + { + RecordingProvider provider = new RecordingProvider(); + try + { + FontProvider.Current = provider; + Text text = Text.Construct(); + text.Font = "Recording"; + text.TextString = "a😀b"; + text.Location = GeoPoint.Origin; + text.LineDirection = GeoVector.XAxis; + text.GlyphDirection = GeoVector.YAxis; + text.TextSize = 1.0; + + // three characters, not four as with the two halves of the surrogate pair as separate characters: the extent + // is the sum of the advance widths, the outline goes from the first square at 0.1 to the third at 2 * 0.5 + 0.4 + BoundingCube extent = text.GetBoundingCube(); + Assert.AreEqual(1.5, extent.Xmax - extent.Xmin, 1e-6, "the advance widths of three characters"); + BoundingCube outline = text.GetOutline().GetExtent(); + Assert.AreEqual(0.1, outline.Xmin, 1e-6); + Assert.AreEqual(1.4, outline.Xmax, 1e-6, "the outlines of three characters"); + + RecordingFace face = provider.Face; + CollectionAssert.AreEquivalent(new[] { 'a', Emoji, 'b' }, face.Glyphs.Distinct().ToArray(), "the outlines asked for"); + Assert.IsTrue(face.Widths.Contains(Emoji), "the advance width of the emoji"); + Assert.IsTrue(face.KerningPairs.Contains(('a', Emoji)) && face.KerningPairs.Contains((Emoji, 'b')), "the kerning around the emoji"); + Assert.IsFalse(face.Glyphs.Concat(face.Widths).Any(IsSurrogate), "no half of a surrogate pair reaches the font"); + } + finally + { + TestFonts.UseTestFonts(null); + } + } + + [TestMethod] + public void TheManagedReaderMapsCodePointsAboveTheBmp() + { + // the face of the managed reader passes the code point on to the cmap, which supports format 12 (beyond U+FFFF); + // a font without the character gives the missing glyph, but no exception + FontFace face = new ManagedFontProvider(new[] { TestFonts.Directory }, false).GetFace("Liberation Sans", false, false); + Assert.IsNotNull(face); + Assert.AreEqual(face.GetAdvanceWidth(0xFFFF + 1), face.GetAdvanceWidth(0x10FFFF), 1e-12, "both are the missing glyph"); + face.GetGlyphPath(Emoji, false, false, out PointF[] points, out byte[] types); + Assert.AreEqual(points.Length, types.Length); + } + } +} diff --git a/tests/CADability.Tests/FontProviderDefaultTests.cs b/tests/CADability.Tests/FontProviderDefaultTests.cs new file mode 100644 index 000000000..89b45b19e --- /dev/null +++ b/tests/CADability.Tests/FontProviderDefaultTests.cs @@ -0,0 +1,144 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Reflection; + +namespace CADability.Tests +{ + /// + /// CADability.Forms makes its GDI font provider the default, so that Windows Forms applications show their texts as before + /// issue #412, but a provider the application has set itself is kept. The tests change the static state of + /// , so they do not run in parallel with other tests and restore it afterwards. + /// + [TestClass] + public class FontProviderDefaultTests + { + private sealed class NamedProvider : FontProvider + { + public readonly string Name; + public NamedProvider(string name) { Name = name; } + public override string[] GetFamilyNames() => new string[0]; + public override FontFace GetFace(string familyName, bool bold, bool italic) => null; + } + + private static readonly FieldInfo current = typeof(FontProvider).GetField("current", BindingFlags.NonPublic | BindingFlags.Static); + private static readonly FieldInfo chosenByApplication = typeof(FontProvider).GetField("chosenByApplication", BindingFlags.NonPublic | BindingFlags.Static); + + /// + /// Runs as in a fresh process, where the application has not chosen a provider yet, and restores + /// the provider of the other tests afterwards. + /// + private static void AsInAFreshProcess(Action test) + { + object savedCurrent = current.GetValue(null), savedChosen = chosenByApplication.GetValue(null); + try + { + current.SetValue(null, null); + chosenByApplication.SetValue(null, false); + test(); + } + finally + { + current.SetValue(null, savedCurrent); + chosenByApplication.SetValue(null, savedChosen); + CADability.GeoObject.FontCache.GlobalFontCache.Clear(); + CADability.GeoObject.Text.ResetFontFamilyNames(); + } + } + + [TestMethod] + [DoNotParallelize] + public void TheDefaultIsTheManagedProvider() + { + AsInAFreshProcess(() => Assert.IsInstanceOfType(FontProvider.Current, typeof(ManagedFontProvider))); + } + + [TestMethod] + [DoNotParallelize] + public void AFrontEndCanReplaceTheDefault() + { + AsInAFreshProcess(() => + { + FontProvider.SetDefault(() => new NamedProvider("front end")); + Assert.AreEqual("front end", ((NamedProvider)FontProvider.Current).Name); + }); + // also after the managed default was already used + AsInAFreshProcess(() => + { + Assert.IsInstanceOfType(FontProvider.Current, typeof(ManagedFontProvider)); + FontProvider.SetDefault(() => new NamedProvider("front end")); + Assert.AreEqual("front end", ((NamedProvider)FontProvider.Current).Name); + }); + } + + [TestMethod] + [DoNotParallelize] + public void TheProviderOfTheApplicationIsKept() + { + // set before the front end is loaded + AsInAFreshProcess(() => + { + FontProvider.Current = new NamedProvider("application"); + FontProvider.SetDefault(() => new NamedProvider("front end")); + Assert.AreEqual("application", ((NamedProvider)FontProvider.Current).Name); + }); + // set after the front end is loaded + AsInAFreshProcess(() => + { + FontProvider.SetDefault(() => new NamedProvider("front end")); + FontProvider.Current = new NamedProvider("application"); + FontProvider.SetDefault(() => new NamedProvider("front end")); + Assert.AreEqual("application", ((NamedProvider)FontProvider.Current).Name); + }); + } + +#if WINDOWS + private static readonly MethodInfo makeDefault = typeof(CADability.Forms.GdiFontProvider).GetMethod("MakeDefault", BindingFlags.NonPublic | BindingFlags.Static); + + [TestMethod] + [DoNotParallelize] + public void CADabilityFormsMakesGdiTheDefault() + { + FieldInfo madeDefault = typeof(CADability.Forms.GdiFontProvider).GetField("madeDefault", BindingFlags.NonPublic | BindingFlags.Static); + object savedMadeDefault = madeDefault.GetValue(null); + try + { + AsInAFreshProcess(() => + { + madeDefault.SetValue(null, 0); + makeDefault.Invoke(null, null); + Assert.IsInstanceOfType(FontProvider.Current, typeof(CADability.Forms.GdiFontProvider)); + }); + AsInAFreshProcess(() => + { + madeDefault.SetValue(null, 0); + FontProvider.Current = new ManagedFontProvider(); + makeDefault.Invoke(null, null); + Assert.IsInstanceOfType(FontProvider.Current, typeof(ManagedFontProvider), "the choice of the application is kept"); + }); + } + finally + { + madeDefault.SetValue(null, savedMadeDefault); + } + } + + [TestMethod] + [DataRow(typeof(CADability.Forms.CadControl))] + [DataRow(typeof(CADability.Forms.CadCanvas))] + [DataRow(typeof(CADability.Forms.CadFrame))] + [DataRow(typeof(CADability.Forms.PaintToOpenGL))] + [DataRow(typeof(CADability.Forms.PaintToGDI))] + public void TheMainClassesOfFormsMakeGdiTheDefault(Type formsClass) + { + // the static constructor calls GdiFontProvider.MakeDefault: look for "call MakeDefault" in its IL + ConstructorInfo cctor = formsClass.TypeInitializer; + Assert.IsNotNull(cctor, formsClass.Name + " has no static constructor"); + byte[] il = cctor.GetMethodBody().GetILAsByteArray(); + byte[] token = BitConverter.GetBytes(makeDefault.MetadataToken); + bool found = false; + for (int i = 0; i + 4 < il.Length && !found; ++i) + found = il[i] == 0x28 && il[i + 1] == token[0] && il[i + 2] == token[1] && il[i + 3] == token[2] && il[i + 4] == token[3]; + Assert.IsTrue(found, formsClass.Name + " does not call GdiFontProvider.MakeDefault"); + } +#endif + } +} diff --git a/tests/CADability.Tests/FontProviderTests.cs b/tests/CADability.Tests/FontProviderTests.cs new file mode 100644 index 000000000..308678909 --- /dev/null +++ b/tests/CADability.Tests/FontProviderTests.cs @@ -0,0 +1,175 @@ +using CADability.Curve2D; +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.CompilerServices; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// Texts get their fonts from , a static service of the kernel, and no longer from GDI or + /// the frame (issue #412, step 6): they work without a frame and on every platform. The tests that replace the provider + /// do not run in parallel with other tests and restore the provider of . + /// + [TestClass] + public class FontProviderTests + { + private static string FontDirectory([CallerFilePath] string thisFile = "") + => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "Fonts"); + + private static Text MakeText(string font, string textString, double size) + { + Text text = Text.Construct(); + text.Font = font; + text.TextString = textString; + text.Location = GeoPoint.Origin; + text.LineDirection = GeoVector.XAxis; + text.GlyphDirection = GeoVector.YAxis; + text.TextSize = size; + return text; + } + private static void WithProvider(FontProvider provider, Action action) + { + try + { + FontProvider.Current = provider; + action(); + } + finally + { + TestFonts.UseTestFonts(null); + } + } + + [TestMethod] + [DoNotParallelize] + public void TextWorksWithoutFrameAndWithoutGdi() + { + Assert.IsNull(FrameImpl.MainFrame, "the tests run without a frame"); + WithProvider(new ManagedFontProvider(new[] { FontDirectory() }, false), () => + { + Text text = MakeText("Liberation Sans", "Hello", 10.0); + GeoObjectList outline = text.GetOutline(); + Assert.IsTrue(outline.Count > 0, "the text has an outline"); + BoundingCube ext = text.GetBoundingCube(); + Assert.IsFalse(ext.IsEmpty); + + // the width of the text is the sum of the advance widths and the kerning of the font + TrueTypeFont font = TrueTypeFont.Load(Path.Combine(FontDirectory(), "LiberationSans-Regular.ttf"))[0]; + double advance = 0.0; + string s = "Hello"; + for (int i = 0; i < s.Length; ++i) + { + advance += font.GetAdvanceWidth(font.GetGlyphIndex(s[i])); + if (i > 0) advance += font.GetKerning(font.GetGlyphIndex(s[i - 1]), font.GetGlyphIndex(s[i])); + } + double width = 10.0 * advance / font.UnitsPerEm; + Assert.AreEqual(width, text.GetBoundingCube().Xmax - text.GetBoundingCube().Xmin, 0.15 * 10.0, "the extent is about the advance width (the glyphs do not fill their whole advance)"); + }); + } + + [TestMethod] + [DoNotParallelize] + public void UnknownFontUsesTheDefaultFace() + { + WithProvider(new ManagedFontProvider(new[] { FontDirectory() }, false), () => + { + FontFace face = FontCache.GlobalFontCache.GetFace("A Font That Does Not Exist", 0); + Assert.AreEqual("Liberation Sans", face.FamilyName); + Text known = MakeText("Liberation Sans", "Ag", 1.0), unknown = MakeText("A Font That Does Not Exist", "Ag", 1.0); + Assert.AreEqual(known.GetOutline().GetExtent().Size, unknown.GetOutline().GetExtent().Size, 1e-9); + }); + } + + [TestMethod] + [DoNotParallelize] + public void WithoutAnyFontTheExceptionSaysWhatToDo() + { + string empty = Path.Combine(Path.GetTempPath(), "CADability-no-fonts-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(empty); + try + { + WithProvider(new ManagedFontProvider(new[] { empty }, false), () => + { + InvalidOperationException ex = Assert.ThrowsException(() => FontCache.GlobalFontCache.GetFace("Arial", 0)); + StringAssert.Contains(ex.Message, "FontProvider.Current"); + // the extent and the glyphs for painting are empty instead of failing, an explicit request for the outline throws + FontCache.GlobalFontCache.Get("Arial", 0, 'A', out double width, null); + Text text = MakeText("Arial", "no font", 1.0); + text.GetBoundingCube(); + Assert.ThrowsException(() => text.GetOutline()); + }); + } + finally + { + Directory.Delete(empty, true); + } + } + + [TestMethod] + [DoNotParallelize] + public void UnderlineAndStrikeoutAddBars() + { + WithProvider(new ManagedFontProvider(new[] { FontDirectory() }, false), () => + { + Path2D[] plain = FontCache.GlobalFontCache.GetOutline2D("Liberation Sans", 0, 'o', out double w1); + Path2D[] underlined = FontCache.GlobalFontCache.GetOutline2D("Liberation Sans", 4, 'o', out double w2); + Path2D[] both = FontCache.GlobalFontCache.GetOutline2D("Liberation Sans", 4 | 8, 'o', out double w3); + Assert.AreEqual(plain.Length + 1, underlined.Length); + Assert.AreEqual(plain.Length + 2, both.Length); + Assert.AreEqual(w1, w2); + // the underline is centered on the underline position of "post", as GDI+ does it (the baseline is at + // y = 1 - CellAscent/em after PathDataToPath2D) + FontFace face = FontCache.GlobalFontCache.GetFace("Liberation Sans", 0); + TrueTypeFont font = ((ManagedFontFace)face).Font; + double em = face.EmHeight, baseline = 1.0 - face.CellAscent / em; + BoundingRect bar = underlined.Last().GetExtent(); + Assert.AreEqual(baseline + font.UnderlinePosition / em, (bar.Top + bar.Bottom) / 2.0, 1e-5); + Assert.AreEqual(font.UnderlineThickness / em, bar.Top - bar.Bottom, 1e-5); + }); + } + + [TestMethod] + [DoNotParallelize] + public void MissingStyleIsMadeFromAnExistingFace() + { + ManagedFontProvider provider = new ManagedFontProvider(new[] { FontDirectory() }, false); + CollectionAssert.Contains(provider.GetFamilyNames(), "Liberation Sans"); + FontFace bold = provider.GetFace("liberation sans", true, false); + Assert.IsNotNull(bold, "the family name is not case sensitive"); + // only the regular face is there: the bold face is synthesized from it (see SyntheticFontFaceTests) + Assert.IsTrue(bold.IsBold); + Assert.AreSame(provider.GetFace("Liberation Sans", false, false), ((SyntheticFontFace)bold).BaseFace, "faces are loaded once"); + Assert.IsNull(provider.GetFace("Arial", false, false)); + } + + [TestMethod] + public void HeadersAreReadWithoutLoadingTheGlyphs() + { + string file = Path.Combine(FontDirectory(), "LiberationSans-Regular.ttf"); + TrueTypeFont header = TrueTypeFont.ReadHeaders(file).Single(), full = TrueTypeFont.Load(file).Single(); + Assert.IsTrue(header.IsHeaderOnly); + Assert.IsFalse(full.IsHeaderOnly); + Assert.AreEqual(full.FamilyName, header.FamilyName); + Assert.AreEqual(full.SubfamilyName, header.SubfamilyName); + Assert.AreEqual(full.IsBold, header.IsBold); + Assert.AreEqual(full.IsItalic, header.IsItalic); + Assert.AreEqual(full.UnitsPerEm, header.UnitsPerEm); + Assert.AreEqual(full.CellAscent, header.CellAscent); + Assert.AreEqual(full.UnderlinePosition, header.UnderlinePosition); + Assert.IsTrue(header.HasTrueTypeOutlines); + Assert.ThrowsException(() => header.GetContours(5)); + } + + [TestMethod] + public void FontCacheHasNoFinalizerThatCanCrashTheProcess() + { + // the FontCache used to create a GDI device context in its constructor and delete it in its finalizer; without GDI + // the finalizer threw and ended the process (issue #412) + for (int i = 0; i < 10; ++i) new FontCache(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } +} diff --git a/tests/CADability.Tests/GdiFontComparisonTests.cs b/tests/CADability.Tests/GdiFontComparisonTests.cs new file mode 100644 index 000000000..c20adc627 --- /dev/null +++ b/tests/CADability.Tests/GdiFontComparisonTests.cs @@ -0,0 +1,306 @@ +#if WINDOWS +using CADability.Forms; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; + +namespace CADability.Tests +{ + /// + /// Compares the managed font reader (, the default since issue #412) with GDI and GDI+ + /// (, how CADability got its fonts before) on the fonts installed on Windows: the families, + /// the metrics, the advance widths, the underline and strikeout lines and the glyph outlines must be the same, so that + /// texts in existing drawings keep their layout. The outlines are compared with the unhinted outlines of GDI. + /// + [TestClass] + public class GdiFontComparisonTests + { + public TestContext TestContext { get; set; } + + // GDI gives the advance widths as whole pixels at the em height as pixel size, which is one font unit. The outlines + // are floats, and GDI rounds the varied coordinates of a variable font to whole font units. + private const double WidthTolerance = 1.0; // in font units + private const double OutlineTolerance = 2.0; // in font units + + private static readonly string Characters = new string(Enumerable.Range(0x20, 0x7F - 0x20).Select(c => (char)c).ToArray()) + "ÄÖÜäöüßéÅ€"; + + private static readonly ManagedFontProvider managed = new ManagedFontProvider(); + private static readonly GdiFontProvider gdi = new GdiFontProvider(); + + [TestMethod] + public void TheManagedProviderKnowsAllFamiliesOfGdi() + { + // GDI cuts the names to 31 characters, the managed provider also finds the family by such a name + string[] missing = gdi.GetFamilyNames().Where(name => managed.GetFace(name, false, false) == null).OrderBy(name => name).ToArray(); + Assert.AreEqual(0, missing.Length, "families of GDI the managed provider does not know: " + string.Join(", ", missing)); + } + + [TestMethod] + [DataRow("Arial")] + [DataRow("Times New Roman")] + [DataRow("Courier New")] + [DataRow("Segoe UI")] + [DataRow("Calibri")] + [DataRow("Cambria")] // in a font collection (cambria.ttc) + [DataRow("Consolas")] + [DataRow("Georgia")] + [DataRow("Bahnschrift")] // variable: the default instance + [DataRow("Bahnschrift Light Condensed")] // variable: a named instance + [DataRow("Segoe UI Variable Display")] // variable, Windows 11 only + [DataRow("Segoe UI Variable Text Light")] // named from "STAT": "Light Text" in "fvar" + public void FacesMatchGdi(string family) + { + if (gdi.GetFace(family, false, false) == null) Assert.Inconclusive(family + " is not installed"); + StringBuilder report = new StringBuilder(); + int compared = 0; + foreach (bool bold in new[] { false, true }) + { + foreach (bool italic in new[] { false, true }) + { + FontFace g = gdi.GetFace(family, bold, italic), m = managed.GetFace(family, bold, italic); + Assert.IsNotNull(m, family + " is not known to the managed provider"); + // only real faces: a missing bold or italic face is synthesized by both, see SyntheticStylesMatchGdiPlus + if (g.IsBold != bold || g.IsItalic != italic || !(m is ManagedFontFace real) || real.IsBold != bold || real.IsItalic != italic) continue; + Compare(g, real, Characters, true, report); + ++compared; + } + } + Assert.IsTrue(compared > 0, "no face of " + family + " was compared"); + Assert.AreEqual(0, report.Length, report.ToString()); + } + + /// + /// All fonts GDI knows, with fewer outlines: the metrics, the advance widths of all characters of the test and + /// the outlines of a few characters with curves, corners, holes and composite glyphs. + /// + [TestMethod] + public void AllInstalledFontsMatchGdi() + { + StringBuilder report = new StringBuilder(); + int compared = 0; + foreach (string family in gdi.GetFamilyNames().OrderBy(name => name)) + { + FontFace g = gdi.GetFace(family, false, false), m = managed.GetFace(family, false, false); + if (g == null || m == null) continue; // a missing family is reported by TheManagedProviderKnowsAllFamiliesOfGdi + if (g.IsBold != m.IsBold || g.IsItalic != m.IsItalic) continue; // a family without a regular face + Compare(g, (ManagedFontFace)m, "AOgs8&@Äé", false, report); + ++compared; + } + TestContext.WriteLine($"{compared} families compared"); + Assert.AreEqual(0, report.Length, report.ToString()); + } + + private static void Compare(FontFace g, ManagedFontFace m, string outlineCharacters, bool decorations, StringBuilder report) + { + string face = $"{g.FamilyName}{(g.IsBold ? " bold" : "")}{(g.IsItalic ? " italic" : "")}"; + void Fail(string message) => report.AppendLine(face + ": " + message); + if (g.EmHeight != m.EmHeight || g.CellAscent != m.CellAscent || g.CellDescent != m.CellDescent || g.LineSpacing != m.LineSpacing) + { + Fail($"metrics (em, ascent, descent, line spacing) GDI {g.EmHeight} {g.CellAscent} {g.CellDescent} {g.LineSpacing}, managed {m.EmHeight} {m.CellAscent} {m.CellDescent} {m.LineSpacing}"); + return; // all the rest would differ as well + } + double em = m.EmHeight; + // characters the font does not have are left out: GDI takes them from another font (font linking) + foreach (char c in Characters.Where(c => m.Font.GetGlyphIndex(c) != 0)) + { + double gw = g.GetAdvanceWidth(c) * em, mw = m.GetAdvanceWidth(c) * em; + if (Math.Abs(gw - mw) > WidthTolerance) Fail($"advance width of '{c}' GDI {gw}, managed {mw}"); + } + FontStyle style = (g.IsBold ? FontStyle.Bold : FontStyle.Regular) | (g.IsItalic ? FontStyle.Italic : FontStyle.Regular); + using (GdiGlyphOutlines outlines = new GdiGlyphOutlines(g.FamilyName, style, m.EmHeight)) + { + foreach (char c in outlineCharacters.Where(c => m.Font.GetGlyphIndex(c) != 0)) + { + m.GetGlyphPath(c, false, false, out PointF[] mp, out byte[] mt); + CompareOutlines($"outline of '{c}'", outlines.Get(c, m.CellAscent), Flatten(mp, mt, em), Fail); + } + } + if (decorations) + { + CompareBar(g, m, true, Fail); + CompareBar(g, m, false, Fail); + } + } + + /// + /// The underline or the strikeout line, which is the last figure of the path data of GDI+ and of the managed reader. + /// + private static void CompareBar(FontFace g, FontFace m, bool underline, Action fail) + { + g.GetGlyphPath('H', underline, !underline, out PointF[] gp, out byte[] gt); + m.GetGlyphPath('H', underline, !underline, out PointF[] mp, out byte[] mt); + List> a = Flatten(gp, gt, m.EmHeight), b = Flatten(mp, mt, m.EmHeight); + if (a.Count == 0 || b.Count == 0) fail((underline ? "underline" : "strikeout") + " is missing"); + else CompareOutlines(underline ? "underline" : "strikeout", a.GetRange(a.Count - 1, 1), b.GetRange(b.Count - 1, 1), fail); + } + + private static void CompareOutlines(string what, List> gdi, List> managed, Action fail) + { + if (gdi.Count != managed.Count) + { + fail($"{what}: GDI {gdi.Count} contours, managed {managed.Count}"); + return; + } + // the contours may start at different points, so the outlines are compared as point sets (Hausdorff distance) + double distance = Math.Max(Directed(gdi, managed), Directed(managed, gdi)); + if (distance > OutlineTolerance) fail($"{what}: distance {distance:0.###} font units"); + } + + /// + /// The unhinted glyph outlines of GDI (GetGlyphOutline with GGO_NATIVE | GGO_UNHINTED) at the em height as pixel size, + /// so in font units. GDI+ () is not the reference for the outlines: AddString gives the + /// hinted outlines, which differ from the design by a few font units in strongly hinted fonts (e.g. 17 units for '2' + /// of Calibri) and are wrong for Segoe Script. + /// + private sealed class GdiGlyphOutlines : IDisposable + { + [StructLayout(LayoutKind.Sequential)] private struct GLYPHMETRICS { public uint bx, by; public int ox, oy; public short cx, cy; } + [StructLayout(LayoutKind.Sequential)] private struct MAT2 { public int m11, m12, m21, m22; } + [DllImport("gdi32.dll")] private static extern IntPtr CreateCompatibleDC(IntPtr hdc); + [DllImport("gdi32.dll")] private static extern bool DeleteDC(IntPtr hdc); + [DllImport("gdi32.dll")] private static extern IntPtr SelectObject(IntPtr hdc, IntPtr obj); + [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr obj); + [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr CreateFontW(int height, int width, int escapement, int orientation, int weight, uint italic, uint underline, uint strikeOut, uint charSet, uint outPrecision, uint clipPrecision, uint quality, uint pitchAndFamily, string faceName); + [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] private static extern uint GetGlyphOutlineW(IntPtr hdc, uint c, uint format, out GLYPHMETRICS metrics, uint size, byte[] buffer, ref MAT2 matrix); + private const uint GGO_NATIVE = 2, GGO_UNHINTED = 0x100; + + private readonly IntPtr hdc, hfont, oldFont; + public GdiGlyphOutlines(string family, FontStyle style, int em) + { + // the weight of a regular face is FW_DONTCARE: with FW_NORMAL GDI would embolden a lighter face, e.g. the + // instance "Cascadia Code ExtraLight" + int weight = (style & FontStyle.Bold) != 0 ? 700 : 0; + hfont = CreateFontW(-em, 0, 0, 0, weight, (style & FontStyle.Italic) != 0 ? 1u : 0u, 0, 0, 1 /* DEFAULT_CHARSET */, 0, 0, 0, 0, family); + hdc = CreateCompatibleDC(IntPtr.Zero); + oldFont = SelectObject(hdc, hfont); + } + public void Dispose() + { + SelectObject(hdc, oldFont); + DeleteObject(hfont); + DeleteDC(hdc); + } + private static double Fixed(byte[] b, int offset) => BitConverter.ToInt16(b, offset + 2) + BitConverter.ToUInt16(b, offset) / 65536.0; + /// + /// The contours of a character as polylines in the layout of : font units, y downwards, the + /// top of the cell at 0. + /// + public List> Get(char c, int cellAscent) + { + const int n = 16; + MAT2 identity = new MAT2 { m11 = 0x10000, m22 = 0x10000 }; + List> res = new List>(); + uint size = GetGlyphOutlineW(hdc, c, GGO_NATIVE | GGO_UNHINTED, out GLYPHMETRICS metrics, 0, null, ref identity); + if (size == 0 || size == uint.MaxValue) return res; + byte[] b = new byte[size]; + GetGlyphOutlineW(hdc, c, GGO_NATIVE | GGO_UNHINTED, out metrics, size, b, ref identity); + (double x, double y) Point(int offset) => (Fixed(b, offset), cellAscent - Fixed(b, offset + 4)); + int pos = 0; + while (pos < size) + { // TTPOLYGONHEADER: cb, dwType, pfxStart, then TTPOLYCURVEs: wType, cpfx, apfx[cpfx] + int cb = BitConverter.ToInt32(b, pos); + List<(double x, double y)> contour = new List<(double, double)> { Point(pos + 8) }; + for (int p = pos + 16; p < pos + cb;) + { + int type = BitConverter.ToUInt16(b, p), count = BitConverter.ToUInt16(b, p + 2); + if (type == 1) for (int k = 0; k < count; ++k) contour.Add(Point(p + 4 + 8 * k)); // lines + else if (type == 2) + { // quadratic B-spline: the on-curve points between the control points are implied + for (int k = 0; k < count - 1; ++k) + { + (double x, double y) p0 = contour[contour.Count - 1], c1 = Point(p + 4 + 8 * k), next = Point(p + 12 + 8 * k); + (double x, double y) e = k == count - 2 ? next : ((c1.x + next.x) / 2, (c1.y + next.y) / 2); + for (int s = 1; s <= n; ++s) + { + double t = (double)s / n, u = 1 - t; + contour.Add((u * u * p0.x + 2 * u * t * c1.x + t * t * e.x, u * u * p0.y + 2 * u * t * c1.y + t * t * e.y)); + } + } + } + else + { // cubic Bezier segments (fonts with CFF outlines) + for (int k = 0; k + 2 < count; k += 3) + { + (double x, double y) p0 = contour[contour.Count - 1], p1 = Point(p + 4 + 8 * k), p2 = Point(p + 12 + 8 * k), p3 = Point(p + 20 + 8 * k); + for (int s = 1; s <= n; ++s) + { + double t = (double)s / n, u = 1 - t; + contour.Add((u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x, + u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y)); + } + } + } + p += 4 + 8 * count; + } + contour.Add(contour[0]); + res.Add(contour); + pos += cb; + } + return res; + } + } + + /// + /// The contours of GDI+ path data as polylines in font units, with the Bezier segments finely subdivided. + /// + private static List> Flatten(PointF[] points, byte[] types, double em) + { + const int n = 16; + List> res = new List>(); + List<(double x, double y)> contour = null; + for (int i = 0; i < points.Length; ++i) + { + (double x, double y) p = (points[i].X * em, points[i].Y * em); + switch (types[i] & 0x07) + { + case 0: + contour = new List<(double, double)> { p }; + res.Add(contour); + break; + case 1: + contour.Add(p); + break; + case 3: + (double x, double y) p0 = contour[contour.Count - 1], p1 = p, p2 = (points[i + 1].X * em, points[i + 1].Y * em), p3 = (points[i + 2].X * em, points[i + 2].Y * em); + for (int k = 1; k <= n; ++k) + { + double t = (double)k / n, u = 1 - t; + contour.Add((u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x, + u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y)); + } + i += 2; + break; + } + if ((types[i] & 0x80) != 0) contour.Add(contour[0]); + } + return res; + } + + /// + /// The largest distance of a vertex of from the polylines of . + /// + private static double Directed(List> a, List> b) + { + double max = 0.0; + foreach (var p in a.SelectMany(contour => contour)) + { + double best = double.MaxValue; + foreach (var contour in b) + { + for (int i = 1; i < contour.Count; ++i) best = Math.Min(best, SegmentDistance(p, contour[i - 1], contour[i])); + if (contour.Count == 1) best = Math.Min(best, SegmentDistance(p, contour[0], contour[0])); + } + max = Math.Max(max, best); + } + return max; + } + private static double SegmentDistance((double x, double y) p, (double x, double y) s, (double x, double y) e) + { + double dx = e.x - s.x, dy = e.y - s.y, l = dx * dx + dy * dy; + double t = l == 0.0 ? 0.0 : Math.Max(0.0, Math.Min(1.0, ((p.x - s.x) * dx + (p.y - s.y) * dy) / l)); + double x = s.x + t * dx - p.x, y = s.y + t * dy - p.y; + return Math.Sqrt(x * x + y * y); + } + } +} +#endif diff --git a/tests/CADability.Tests/HeadlessPictureExportTests.cs b/tests/CADability.Tests/HeadlessPictureExportTests.cs new file mode 100644 index 000000000..4d89b1de8 --- /dev/null +++ b/tests/CADability.Tests/HeadlessPictureExportTests.cs @@ -0,0 +1,102 @@ +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.Serialization.Formatters.Binary; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// A picture is created, measured and exported without a frame and without GDI (issue #412, step 9): this runs in the + /// Linux CI, where there is no GDI+. Afterwards the finalizers run, and the process must survive them. + /// + [TestClass] + public class HeadlessPictureExportTests + { + // a PNG of 3×2 pixels + private static readonly byte[] Png3x2 = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAIAAAASFvFNAAAAG0lEQVR4nAXBAQEAAACCIDe5qbHOCgCHIeSyPCtwBd1HI03nAAAAAElFTkSuQmCC"); + + private static Project MakeProject() + { + Project project = Project.CreateSimpleProject(); + Picture picture = Picture.Construct(); + picture.Set(Png3x2, new GeoPoint(1, 2, 3), new GeoVector(30, 0, 0), new GeoVector(0, 20, 0)); + project.GetActiveModel().Add(picture); + project.GetActiveModel().Add(Line.TwoPoints(GeoPoint.Origin, new GeoPoint(100, 0, 0))); + return project; + } + + [TestMethod] + public void APictureIsMeasuredWithoutFrameOrGdi() + { + Assert.IsNull(FrameImpl.MainFrame, "the tests run without a frame"); + Picture picture = MakeProject().GetActiveModel().AllObjects.OfType().Single(); + Assert.AreEqual((3, 2), (picture.PixelWidth, picture.PixelHeight)); + BoundingCube extent = picture.GetBoundingCube(); + Assert.AreEqual((1.0, 31.0, 2.0, 22.0), (extent.Xmin, extent.Xmax, extent.Ymin, extent.Ymax)); + picture.SetHeight(40, true); + Assert.AreEqual(60, picture.DirectionWidth.Length, 1e-12, "the aspect ratio of the pixels"); + } + + [TestMethod] + public void AProjectWithAPictureIsExportedWithoutFrameOrGdi() + { + // DXF and STL have no pictures: the other objects are written, and the picture does not stop the export + Project project = MakeProject(); + string dxf = Path.Combine(Path.GetTempPath(), "HeadlessPictureExportTests-" + Guid.NewGuid().ToString("N") + ".dxf"); + string stl = Path.ChangeExtension(dxf, ".stl"); + try + { + Assert.IsTrue(project.Export(dxf, "dxf"), "the DXF file was not written"); + Assert.AreEqual(1, Project.ReadFromFile(dxf, "dxf").GetActiveModel().AllObjects.OfType().Count()); + project.ExportToSTL(stl); + Assert.IsTrue(File.Exists(stl)); + } + finally + { + File.Delete(dxf); + File.Delete(stl); + } + } + + [TestMethod] + [DoNotParallelize] // enables the BinaryFormatter for the process + public void AProjectWithAPictureIsWrittenInTheBinaryFormatWithoutGdi() + { + // the binary format used to contain the GDI+ bitmap, which could only be written and read with GDI+ + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); + try + { +#pragma warning disable SYSLIB0011 // the binary format is only tested, for old applications + new BinaryFormatter().Serialize(new MemoryStream(), 1); +#pragma warning restore SYSLIB0011 + } + catch (Exception e) when (e is NotSupportedException || e is PlatformNotSupportedException) + { + Assert.Inconclusive("this runtime has no BinaryFormatter: " + e.Message); + } + MemoryStream written = new MemoryStream(); + MakeProject().WriteToStream(written); +#pragma warning disable CS0618 // ReadFromStream is kept for binary files + Project read = Project.ReadFromStream(new MemoryStream(written.ToArray())); +#pragma warning restore CS0618 + CollectionAssert.AreEqual(Png3x2, read.GetActiveModel().AllObjects.OfType().Single().ImageData); + } + + [TestMethod] + public void TheProcessSurvivesTheFinalizersAfterPictures() + { + for (int i = 0; i < 20; ++i) + { + Project project = MakeProject(); + string stl = Path.Combine(Path.GetTempPath(), "HeadlessPictureExportTests-" + Guid.NewGuid().ToString("N") + ".stl"); + project.ExportToSTL(stl); // paints the picture, which decodes it with GDI+ where there is GDI+ + File.Delete(stl); + Picture picture = project.GetActiveModel().AllObjects.OfType().Single(); + ((Picture)picture.Clone()).ImageData = Png3x2; + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } +} diff --git a/tests/CADability.Tests/HeadlessTextExportTests.cs b/tests/CADability.Tests/HeadlessTextExportTests.cs new file mode 100644 index 000000000..32cd16aee --- /dev/null +++ b/tests/CADability.Tests/HeadlessTextExportTests.cs @@ -0,0 +1,86 @@ +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// A text is created, measured and exported without a frame and without GDI (issue #412, step 9): this runs in the Linux + /// CI, where there is no GDI+. The font is Liberation Sans, which the tests register (see ). + /// Afterwards the finalizers run, and the process must survive them. + /// + [TestClass] + public class HeadlessTextExportTests + { + private static Text MakeText(string textString) + { + Text text = Text.Construct(); + text.Font = "Liberation Sans"; + text.TextString = textString; + text.Location = new GeoPoint(10, 20, 0); + text.LineDirection = GeoVector.XAxis; + text.GlyphDirection = GeoVector.YAxis; + text.TextSize = 5.0; + return text; + } + + [TestMethod] + public void ATextIsSavedAndReadWithoutFrameOrGdi() + { + Assert.IsNull(FrameImpl.MainFrame, "the tests run without a frame"); + Project project = Project.CreateSimpleProject(); + Text text = MakeText("Headless 412"); + project.GetActiveModel().Add(text); + BoundingCube extent = text.GetBoundingCube(); + Assert.IsFalse(extent.IsEmpty); + Assert.IsTrue(extent.Xmax - extent.Xmin > 20 && extent.Ymax - extent.Ymin > 2, "the text has the size of its glyphs"); + + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); + Text read = Project.ReadFromJson(new MemoryStream(written.ToArray())).GetActiveModel().AllObjects.OfType().Single(); + Assert.AreEqual("Headless 412", read.TextString); + Assert.AreEqual("Liberation Sans", read.Font); + BoundingCube readExtent = read.GetBoundingCube(); + Assert.AreEqual(extent.Xmin, readExtent.Xmin, 1e-9); + Assert.AreEqual(extent.Xmax, readExtent.Xmax, 1e-9); + Assert.AreEqual(extent.Ymax, readExtent.Ymax, 1e-9); + } + + [TestMethod] + public void ATextIsExportedToDxfAndReadBackWithoutFrameOrGdi() + { + Project project = Project.CreateSimpleProject(); + project.GetActiveModel().Add(MakeText("Headless 412")); + string file = Path.Combine(Path.GetTempPath(), "HeadlessTextExportTests-" + Guid.NewGuid().ToString("N") + ".dxf"); + try + { + Assert.IsTrue(project.Export(file, "dxf"), "the DXF file was not written"); + Project imported = Project.ReadFromFile(file, "dxf"); + Text text = imported.GetActiveModel().AllObjects.OfType().Single(); + Assert.AreEqual("Headless 412", text.TextString); + Assert.AreEqual(5.0, text.TextSize, 1e-6); + Assert.AreEqual(new GeoPoint(10, 20, 0), text.Location); + Assert.IsFalse(text.GetBoundingCube().IsEmpty, "the imported text is measured with the font of the kernel"); + } + finally + { + File.Delete(file); + } + } + + [TestMethod] + public void TheProcessSurvivesTheFinalizersAfterTexts() + { + // the GDI version of the FontCache had a finalizer that threw without GDI and ended the process (issue #412) + for (int i = 0; i < 20; ++i) + { + Text text = MakeText("finalize " + i); + text.GetOutline(); + text.GetBoundingCube(); + } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + } +} diff --git a/tests/CADability.Tests/PageSetupTests.cs b/tests/CADability.Tests/PageSetupTests.cs new file mode 100644 index 000000000..946ff2ea9 --- /dev/null +++ b/tests/CADability.Tests/PageSetupTests.cs @@ -0,0 +1,131 @@ +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization.Formatters.Binary; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// Project files keep the page settings of the project and of the layouts as a neutral instead + /// of the GDI+ PageSettings, which can only be serialized on .NET Framework (issue #412, step 7). + /// pagesettings-1x-net48.cdb and .cdb.json were written by CADability 1.x on .NET Framework 4.8 with the printer + /// "Microsoft Print to PDF": the project has A3 landscape with the margins 50, 60, 70, 80, the layout "A4 portrait" has A4 + /// portrait with the margins 10, 20, 30, 40 (in hundredths of an inch). + /// + [TestClass] + public class PageSetupTests + { + private static string CdbFile(string name, [CallerFilePath] string thisFile = "") + => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "CDB", name); + + private const double Inch100 = 0.254; // mm + + private static void AssertPage(PageSetup page, string paper, int kind, int width, int height, bool landscape, int[] margins, string printer, string what) + { + Assert.IsNotNull(page, what); + Assert.AreEqual(paper, page.PaperName, what); + Assert.AreEqual(kind, page.PaperKind, what); + Assert.AreEqual(width * Inch100, page.PaperWidth, 1e-9, what); + Assert.AreEqual(height * Inch100, page.PaperHeight, 1e-9, what); + Assert.AreEqual(landscape, page.Landscape, what); + CollectionAssert.AreEqual(margins.Select(m => m * Inch100).ToArray(), new[] { page.MarginLeft, page.MarginRight, page.MarginTop, page.MarginBottom }, what); + Assert.AreEqual(printer, page.PrinterName, what); + } + + private static void AssertLegacyPages(Project project, string printer, string file) + { + Assert.IsNotNull(project, "could not read " + file); + AssertPage(project.DefaultPageSetup, "A3", 8, 1169, 1654, true, new[] { 50, 60, 70, 80 }, printer, file + ": the project"); + Layout layout = Enumerable.Range(0, project.LayoutCount).Select(i => project.GetLayout(i)).Single(l => l.Name == "A4 portrait"); + AssertPage(layout.PageSetup, "A4", 9, 827, 1169, false, new[] { 10, 20, 30, 40 }, printer, file + ": the layout"); + } + + [TestMethod] + public void A1xJsonFileWithPageSettingsOpens() + { + // the JSON format has the public properties, so the printer name is the resolved default printer + AssertLegacyPages(Project.ReadFromFile(CdbFile("pagesettings-1x-net48.cdb.json"), "cdb"), "Microsoft Print to PDF", "pagesettings-1x-net48.cdb.json"); + } + + [TestMethod] + [DoNotParallelize] // enables the BinaryFormatter for the process + public void A1xBinaryFileWithPageSettingsOpens() + { + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); + try + { +#pragma warning disable SYSLIB0011 // the binary format is only read, for old files + new BinaryFormatter().Serialize(new MemoryStream(), 1); +#pragma warning restore SYSLIB0011 + } + catch (Exception e) when (e is NotSupportedException || e is PlatformNotSupportedException) + { + Assert.Inconclusive("this runtime has no BinaryFormatter: " + e.Message); + } + using (FileStream stream = File.OpenRead(CdbFile("pagesettings-1x-net48.cdb"))) + { + // the binary format has the private fields, where an unchanged printer name is empty: the default printer +#pragma warning disable CS0618 // ReadFromStream is kept for old binary files + AssertLegacyPages(Project.ReadFromStream(stream), null, "pagesettings-1x-net48.cdb"); +#pragma warning restore CS0618 + } + } + + [TestMethod] + public void PageSetupsSurviveAJsonRoundTrip() + { + Project project = Project.CreateSimpleProject(); + project.DefaultPageSetup = new PageSetup { PaperName = "A3", PaperKind = 8, PaperWidth = 297, PaperHeight = 420, Landscape = true, MarginLeft = 1, MarginRight = 2, MarginTop = 3, MarginBottom = 4, PrinterName = "Plotter" }; + Layout layout = new Layout(project) { Name = "custom", PageSetup = new PageSetup { PaperName = "Strip", PaperWidth = 100, PaperHeight = 1000 } }; + project.AddLayout(layout); + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); + string json = System.Text.Encoding.UTF8.GetString(written.ToArray()); + Assert.IsFalse(json.Contains("System.Drawing"), "no GDI+ types in the file"); + + Project read = Project.ReadFromJson(new MemoryStream(written.ToArray())); + PageSetup page = read.DefaultPageSetup; + Assert.AreEqual(("A3", 8, 297.0, 420.0, true, "Plotter"), (page.PaperName, page.PaperKind, page.PaperWidth, page.PaperHeight, page.Landscape, page.PrinterName)); + Assert.AreEqual((1.0, 2.0, 3.0, 4.0), (page.MarginLeft, page.MarginRight, page.MarginTop, page.MarginBottom)); + Layout readLayout = Enumerable.Range(0, read.LayoutCount).Select(i => read.GetLayout(i)).Single(l => l.Name == "custom"); + Assert.AreEqual(("Strip", 100.0, 1000.0, false), (readLayout.PageSetup.PaperName, readLayout.PageSetup.PaperWidth, readLayout.PageSetup.PaperHeight, readLayout.PageSetup.Landscape)); + } + + [TestMethod] + public void TheProjectHandsOutCopies() + { + Project project = Project.CreateSimpleProject(); + Assert.IsNull(project.DefaultPageSetup, "no page settings: the printer's defaults"); + PageSetup page = new PageSetup { PaperName = "A4" }; + project.DefaultPageSetup = page; + page.PaperName = "changed"; + Assert.AreEqual("A4", project.DefaultPageSetup.PaperName); + project.DefaultPageSetup.PaperName = "changed"; + Assert.AreEqual("A4", project.DefaultPageSetup.PaperName); + } + +#if WINDOWS + [TestMethod] + public void APageSetupIsAppliedToAPrinterAndSavedOnNet8() + { + Project project = Project.CreateSimpleProject(); + project.DefaultPageSetup = new PageSetup { PaperName = "A3", PaperKind = 8, PaperWidth = 1169 * Inch100, PaperHeight = 1654 * Inch100, Landscape = true, MarginLeft = 50 * Inch100, MarginRight = 60 * Inch100, MarginTop = 70 * Inch100, MarginBottom = 80 * Inch100 }; + System.Drawing.Printing.PrintDocument document = project.PrintDocument; // what the page setup dialog works on + if (!document.PrinterSettings.IsValid) Assert.Inconclusive("no printer installed"); + System.Drawing.Printing.PageSettings settings = document.DefaultPageSettings; + bool hasA3 = document.PrinterSettings.PaperSizes.Cast().Any(p => p.RawKind == 8); + if (hasA3) Assert.AreEqual(8, settings.PaperSize.RawKind, "the A3 paper of the printer"); + Assert.AreEqual((1169, 1654), (settings.PaperSize.Width, settings.PaperSize.Height)); + Assert.IsTrue(settings.Landscape); + Assert.AreEqual((50, 60, 70, 80), (settings.Margins.Left, settings.Margins.Right, settings.Margins.Top, settings.Margins.Bottom)); + // with a PrintDocument, CADability 1.x on .NET 8 could not save the project (PageSettings is not serializable there) + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); + PageSetup read = Project.ReadFromJson(new MemoryStream(written.ToArray())).DefaultPageSetup; + Assert.AreEqual(("A3", true), (read.PaperName, read.Landscape)); + Assert.AreEqual(document.PrinterSettings.PrinterName, read.PrinterName); + } +#endif + } +} diff --git a/tests/CADability.Tests/PictureImageTests.cs b/tests/CADability.Tests/PictureImageTests.cs new file mode 100644 index 000000000..bf1ed8990 --- /dev/null +++ b/tests/CADability.Tests/PictureImageTests.cs @@ -0,0 +1,298 @@ +using CADability.Attribute; +using CADability.GeoObject; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization.Formatters.Binary; +using Icon = CADability.GeoObject.Icon; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// and keep their image as the bytes of a PNG, JPEG, GIF or BMP file and write + /// those into project files, so that projects with pictures open on every platform (issue #412, step 7). The kernel + /// reads only the size from the header; GDI+ decodes the image for painting where it is available. + /// pictures-1x.cdb and pictures-1x.cdb.json were written by CADability 1.x before this change (on Windows and .NET 8, + /// with GDI+ bitmaps), pictures-1x-net48.cdb and pictures-1x-net48.cdb.json by the same code on .NET Framework 4.8: + /// a 7×5 picture, a 9×4 picture loaded from a JPEG and a 4×3 icon, whose pixel (x, y) is RGB (28x, 50y, 128). + /// + [TestClass] + public class PictureImageTests + { + private static string CdbFile(string name, [CallerFilePath] string thisFile = "") + => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "CDB", name); + + // a PNG of 3×2 pixels + private static readonly byte[] Png3x2 = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAIAAAASFvFNAAAAG0lEQVR4nAXBAQEAAACCIDe5qbHOCgCHIeSyPCtwBd1HI03nAAAAAElFTkSuQmCC"); + + private static Project RoundTrip(Project project) + { + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); // closes the stream + return Project.ReadFromJson(new MemoryStream(written.ToArray())); + } + + [TestMethod] + public void TheSizeIsReadFromTheHeader() + { + Assert.IsTrue(EncodedImage.TryGetSize(Png3x2, out int w, out int h)); + Assert.AreEqual((3, 2), (w, h)); + Assert.AreEqual("png", EncodedImage.FormatOf(Png3x2)); + + byte[] gif = { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a', 0x2C, 0x01, 0x10, 0x00, 0, 0, 0 }; + Assert.IsTrue(EncodedImage.TryGetSize(gif, out w, out h)); + Assert.AreEqual((300, 16), (w, h)); + + byte[] bmp = new byte[54]; + bmp[0] = (byte)'B'; bmp[1] = (byte)'M'; + BitConverter.GetBytes(40).CopyTo(bmp, 14); + BitConverter.GetBytes(640).CopyTo(bmp, 18); + BitConverter.GetBytes(-480).CopyTo(bmp, 22); // top-down + Assert.IsTrue(EncodedImage.TryGetSize(bmp, out w, out h)); + Assert.AreEqual((640, 480), (w, h)); + byte[] os2 = new byte[26]; + os2[0] = (byte)'B'; os2[1] = (byte)'M'; + BitConverter.GetBytes(12).CopyTo(os2, 14); + BitConverter.GetBytes((short)33).CopyTo(os2, 18); + BitConverter.GetBytes((short)44).CopyTo(os2, 20); + Assert.IsTrue(EncodedImage.TryGetSize(os2, out w, out h)); + Assert.AreEqual((33, 44), (w, h)); + + // SOI, an APP0 segment, a DHT segment (which must not be taken for a frame header), then SOF2 (progressive) + byte[] jpeg = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x04, 0x4A, 0x46, 0xFF, 0xC4, 0x00, 0x03, 0x00, 0xFF, 0xC2, 0x00, 0x0B, 0x08, 0x01, 0xE0, 0x02, 0x80, 0x01, 0x01, 0x11, 0x00 }; + Assert.IsTrue(EncodedImage.TryGetSize(jpeg, out w, out h)); + Assert.AreEqual((640, 480), (w, h)); + + Assert.IsFalse(EncodedImage.TryGetSize(Png3x2.Take(20).ToArray(), out _, out _), "a truncated header"); + Assert.IsFalse(EncodedImage.TryGetSize(new byte[] { 0x49, 0x49, 0x2A, 0x00, 0, 0, 0, 0 }, out _, out _), "TIFF is not supported"); + Assert.IsFalse(EncodedImage.TryGetSize(null, out _, out _)); + Assert.IsFalse(EncodedImage.TryGetSize(jpeg.Take(14).ToArray(), out _, out _), "a JPEG without a frame header"); + } + + [TestMethod] + public void APictureKeepsTheBytesOfTheImageFile() + { + Picture picture = Picture.Construct(); + picture.Set(Png3x2, new GeoPoint(1, 2, 3), new GeoVector(30, 0, 0), new GeoVector(0, 20, 0)); + Assert.AreSame(Png3x2, picture.ImageData); + Assert.AreEqual(3, picture.PixelWidth); + Assert.AreEqual(2, picture.PixelHeight); + picture.SetWidth(60, true); + Assert.AreEqual(40, picture.DirectionHeight.Length, 1e-12, "the aspect ratio of the pixels"); + Assert.ThrowsException(() => picture.ImageData = new byte[] { 1, 2, 3 }); + Assert.AreSame(Png3x2, picture.ImageData, "invalid data does not replace the image"); + picture.ImageData = null; + Assert.AreEqual(0, picture.PixelWidth); + Picture clone = (Picture)picture.Clone(); + Assert.IsNull(clone.ImageData); + } + + [TestMethod] + public void PicturesAndIconsSurviveAJsonRoundTrip() + { + Project project = Project.CreateSimpleProject(); + Picture picture = Picture.Construct(); + picture.Set(Png3x2, new GeoPoint(1, 2, 3), new GeoVector(30, 0, 0), new GeoVector(0, 20, 0)); + picture.Path = "image.png"; + project.GetActiveModel().Add(picture); + Icon icon = Icon.Construct(); + icon.Set(Png3x2, new GeoPoint(4, 5, 6), 1, 1); + project.GetActiveModel().Add(icon); + + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); + string json = System.Text.Encoding.UTF8.GetString(written.ToArray()); + StringAssert.Contains(json, Convert.ToBase64String(Png3x2), "the image file as Base64"); + Assert.IsFalse(json.Contains("System.Drawing"), "no GDI+ types in the file"); + + Project read = Project.ReadFromJson(new MemoryStream(written.ToArray())); + Picture readPicture = read.GetActiveModel().AllObjects.OfType().Single(); + CollectionAssert.AreEqual(Png3x2, readPicture.ImageData); + Assert.AreEqual(new GeoPoint(1, 2, 3), readPicture.Location); + Assert.AreEqual("image.png", readPicture.Path); + Icon readIcon = read.GetActiveModel().AllObjects.OfType().Single(); + CollectionAssert.AreEqual(Png3x2, readIcon.ImageData); + Assert.AreEqual(new GeoPoint(4, 5, 6), readIcon.Location); + } + + private static void AssertLegacyPictures(Project project, string file, bool jpegKept) + { + Assert.IsNotNull(project, "could not read " + file); + Picture[] pictures = project.GetActiveModel().AllObjects.OfType().ToArray(); + Assert.AreEqual(2, pictures.Length, file); + Picture png = pictures.Single(p => p.Path.EndsWith("pattern.png")); + Assert.AreEqual("png", EncodedImage.FormatOf(png.ImageData), file); + Assert.AreEqual((7, 5), (png.PixelWidth, png.PixelHeight), file); + Assert.AreEqual(new GeoPoint(1, 2, 3), png.Location, file); + Assert.AreEqual(new GeoVector(70, 0, 0), png.DirectionWidth, file); + // GDI+ on .NET Framework serializes a bitmap in its own format, System.Drawing.Common on .NET 8 every bitmap as PNG + Picture jpeg = pictures.Single(p => p.Path == "pattern.jpg"); + Assert.AreEqual(jpegKept ? "jpeg" : "png", EncodedImage.FormatOf(jpeg.ImageData), file); + Assert.AreEqual((9, 4), (jpeg.PixelWidth, jpeg.PixelHeight), file); + Icon icon = project.GetActiveModel().AllObjects.OfType().Single(); + Assert.AreEqual((4, 3), (icon.PixelWidth, icon.PixelHeight), file); + Assert.AreEqual(new GeoPoint(5, 5, 0), icon.Location, file); +#if WINDOWS + // GDI+ decodes the bytes to the pixels the 1.x bitmap had +#pragma warning disable CS0618 // the GDI+ bitmap is obsolete, but it is what the old files contained + Bitmap bitmap = png.Bitmap; +#pragma warning restore CS0618 + for (int x = 0; x < 7; ++x) + for (int y = 0; y < 5; ++y) + Assert.AreEqual(Color.FromArgb(255, 28 * x, 50 * y, 128).ToArgb(), bitmap.GetPixel(x, y).ToArgb(), $"{file}: pixel {x}, {y}"); +#endif + } + + [TestMethod] + [DataRow("pictures-1x.cdb.json", false)] + [DataRow("pictures-1x-net48.cdb.json", true)] + public void A1xJsonFileWithPicturesOpens(string file, bool jpegKept) + { + AssertLegacyPictures(Project.ReadFromFile(CdbFile(file), "cdb"), file, jpegKept); + } + + [TestMethod] + [DoNotParallelize] // enables the BinaryFormatter for the process + [DataRow("pictures-1x.cdb", false)] + [DataRow("pictures-1x-net48.cdb", true)] + public void A1xBinaryFileWithPicturesOpens(string file, bool jpegKept) + { + AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); + try + { +#pragma warning disable SYSLIB0011 // the binary format is only read, for old files + new BinaryFormatter().Serialize(new MemoryStream(), 1); +#pragma warning restore SYSLIB0011 + } + catch (Exception e) when (e is NotSupportedException || e is PlatformNotSupportedException) + { + Assert.Inconclusive("this runtime has no BinaryFormatter: " + e.Message); + } + using (FileStream stream = File.OpenRead(CdbFile(file))) + { +#pragma warning disable CS0618 // ReadFromStream is kept for old binary files + AssertLegacyPictures(Project.ReadFromStream(stream), file, jpegKept); +#pragma warning restore CS0618 + } + } + + [TestMethod] + public void AnUnreadableImageIsLoadedFromThePath() + { + // a relative path: JsonSerialize does not escape backslashes but reads "\r" and "\n" as control characters, so an + // absolute Windows path like C:\Users\runneradmin\... does not survive a round trip (a bug of its own) + string file = "PictureImageTests-" + Guid.NewGuid().ToString("N") + ".png"; + File.WriteAllBytes(file, Png3x2); + try + { + Project project = Project.CreateSimpleProject(); + Picture picture = Picture.Construct(); + picture.Set(new byte[] { 0x89, (byte)'P', (byte)'N', (byte)'G', 0x0D, 0x0A, 0x1A, 0x0A, 0, 0, 0, 13, (byte)'I', (byte)'H', (byte)'D', (byte)'R', 0, 0, 0, 1, 0, 0, 0, 1 }, GeoPoint.Origin, GeoVector.XAxis, GeoVector.YAxis); + picture.Path = file; + project.GetActiveModel().Add(picture); + MemoryStream written = new MemoryStream(); + project.WriteToJson(written); + // damage the image in the file: a header that ends before its size + string json = System.Text.Encoding.UTF8.GetString(written.ToArray()).Replace(Convert.ToBase64String(picture.ImageData), Convert.ToBase64String(new byte[] { 0x89, (byte)'P', (byte)'N', (byte)'G' })); + Project read = Project.ReadFromJson(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json))); + CollectionAssert.AreEqual(Png3x2, read.GetActiveModel().AllObjects.OfType().Single().ImageData); + } + finally + { + File.Delete(file); + } + } + + [TestMethod] + public void APictureIsPaintedWithGdiOrAsItsOutline() + { + Picture picture = Picture.Construct(); + picture.Set(Png3x2, GeoPoint.Origin, new GeoVector(30, 0, 0), new GeoVector(0, 20, 0)); + RecordingPaintTo3D paint = new RecordingPaintTo3D(); + picture.PrePaintTo3D(paint); + picture.PaintTo3D(paint); +#if WINDOWS + Assert.AreEqual(1, paint.Bitmaps.Count, "GDI+ decodes the image"); + Assert.AreEqual((3, 2), (paint.Bitmaps[0].Width, paint.Bitmaps[0].Height)); +#else + // without GDI+ the image is not decoded, and the outline shows where the picture is + Assert.AreEqual(0, paint.Bitmaps.Count); + Assert.AreEqual(1, paint.Polylines.Count); + CollectionAssert.AreEqual(new[] { GeoPoint.Origin, new GeoPoint(30, 0, 0), new GeoPoint(30, 20, 0), new GeoPoint(0, 20, 0), GeoPoint.Origin }, paint.Polylines[0]); +#endif + } + + private class RecordingPaintTo3D : IPaintTo3D + { + public List Polylines { get; } = new List(); + public List Bitmaps { get; } = new List(); + + public void Polyline(GeoPoint[] points) { Polylines.Add(points); } + public void RectangularBitmap(Bitmap bitmap, GeoPoint location, GeoVector directionWidth, GeoVector directionHeight) { Bitmaps.Add(bitmap); } + + #region unused IPaintTo3D members + public bool PaintSurfaces => true; + public bool PaintEdges => true; + public bool PaintSurfaceEdges { get; set; } + public bool UseLineWidth { get; set; } + public double Precision { get; set; } = 0.01; + public double PixelToWorld => 1.0; + public bool SelectMode { get; set; } + public Color SelectColor { get; set; } + public bool DelayText { get; set; } + public bool DelayAll { get; set; } + public bool TriangulateText { get; set; } + public bool DontRecalcTriangulation { get; set; } + public PaintCapabilities Capabilities => PaintCapabilities.Standard; + public IDisposable FacesBehindEdgesOffset => null; + public bool IsBitmap => false; + + public void MakeCurrent() { } + public void SetColor(Color color, int lockColor = 0) { } + public void AvoidColor(Color color) { } + public void SetLineWidth(LineWidth lineWidth) { } + public void SetLinePattern(LinePattern pattern) { } + public void FilledPolyline(GeoPoint[] points) { } + public void Points(GeoPoint[] points, float size, PointSymbol pointSymbol) { } + public void Triangle(GeoPoint[] vertex, GeoVector[] normals, int[] indextriples) { } + public void PrepareText(string fontName, string textString, FontStyle fontStyle) { } + public void PreparePointSymbol(PointSymbol pointSymbol) { } + public void PrepareIcon(Bitmap icon) { } + public void PrepareBitmap(Bitmap bitmap, int xoffset, int yoffset) { } + public void PrepareBitmap(Bitmap bitmap) { } + public void Text(GeoVector lineDirection, GeoVector glyphDirection, GeoPoint location, string fontName, string textString, FontStyle fontStyle, CADability.GeoObject.Text.AlignMode alignment, CADability.GeoObject.Text.LineAlignMode lineAlignment) { } + public void List(IPaintTo3DList paintThisList) { } + public void SelectedList(IPaintTo3DList paintThisList, int wobbleRadius) { } + public void Nurbs(GeoPoint[] poles, double[] weights, double[] knots, int degree) { } + public void Line2D(int sx, int sy, int ex, int ey) { } + public void Line2D(PointF p1, PointF p2) { } + public void FillRect2D(PointF p1, PointF p2) { } + public void Point2D(int x, int y) { } + public void DisplayIcon(GeoPoint p, Bitmap icon) { } + public void DisplayBitmap(GeoPoint p, Bitmap bitmap) { } + public void SetProjection(Projection projection, BoundingCube boundingCube) { } + public void Clear(Color background) { } + public void Resize(int width, int height) { } + public void OpenList(string name = null) { } + public IPaintTo3DList CloseList() => null; + public IPaintTo3DList MakeList(List sublists) => null; + public void OpenPath() { } + public void ClosePath(Color color) { } + public void CloseFigure() { } + public void Arc(GeoPoint center, GeoVector majorAxis, GeoVector minorAxis, double startParameter, double sweepParameter) { } + public void FreeUnusedLists() { } + public void UseZBuffer(bool use) { } + public void Blending(bool on) { } + public void FinishPaint() { } + public void PaintFaces(PaintTo3D.PaintMode paintMode) { } + public void Dispose() { } + public void PushState() { } + public void PopState() { } + public void PushMultModOp(ModOp insertion) { } + public void PopModOp() { } + public void SetClip(Rectangle clipRectangle) { } + #endregion + } + } +} diff --git a/tests/CADability.Tests/ProjectTest.cs b/tests/CADability.Tests/ProjectTest.cs index 9923a8a34..03274956f 100644 --- a/tests/CADability.Tests/ProjectTest.cs +++ b/tests/CADability.Tests/ProjectTest.cs @@ -15,16 +15,6 @@ public class ProjectTest public TestContext TestContext { get; set; } - /// - /// Text entities are measured and triangulated with GDI (System.Drawing and gdi32 via FontCache), - /// which only exists on Windows. Tests that import or create text are inconclusive elsewhere. - /// - private static void RequiresGdiText() - { - if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - Assert.Inconclusive("text needs GDI, which is only available on Windows"); - } - /// /// Renders with OpenGL and compares it with the reference bitmap. /// Rendering needs CADability.Forms (WinForms, OpenGL), so the check only runs on Windows. @@ -103,7 +93,6 @@ public void import_step_issue101_succeds() [TestMethod] public void export_dxf_issue_129_succeds() { - RequiresGdiText(); // in master@82ffb34 exporting a text object to txt does not set the location, // so all texts are all not in the correct location / orientation @@ -172,7 +161,6 @@ public void import_dxf_issue171_succeds() [DeploymentItem(@"Files/Dxf/Z273.dxf", nameof(import_dxf_Z273_succeeds))] public void import_dxf_Z273_succeeds() { - RequiresGdiText(); // AC1009 (R12) DXF with no *Paper_Space block — importing this used to throw // KeyNotFoundException when FillPaperSpace accessed the missing block record. var file = Path.Combine(this.TestContext.DeploymentDirectory, this.TestContext.TestName, "Z273.dxf"); @@ -323,7 +311,6 @@ public void import_dxf_arc_crossing_zero_sweep_is_correct() [TestMethod] public void import_dxf_text_rotation_correct() { - RequiresGdiText(); // Regression: TextEntity.Rotation from ACadSharp is in RADIANS. // Old code called Angle.Deg() on it, making 90° text appear at ~1.57° (nearly horizontal). // A text at DXF rotation=90° must import with LineDirection ≈ (0, 1, 0). @@ -509,7 +496,6 @@ public void import_dxf_0583667_succeeds() [TestMethod] public void import_dxf_mtext_topcenter_placement_correct() { - RequiresGdiText(); // Regression: CreateMText ignored AttachmentPoint so all MTEXT rendered as // Left/Baseline. For table cells with attachment TopCenter (71=2) the text // appears shifted right and down of where it should be. @@ -567,7 +553,6 @@ public void import_dxf_mtext_topcenter_placement_correct() [TestMethod] public void import_dxf_mtext_bottomright_placement_correct() { - RequiresGdiText(); // MTEXT with attachment BottomRight (71=9) must have Right+Bottom alignment. const string dxf = @" 0 SECTION @@ -710,7 +695,6 @@ public void import_dxf_spline_degenerate_does_not_crash() [TestMethod] public void import_dxf_tolerance_fcf_imported_as_text() { - RequiresGdiText(); // DXF TOLERANCE (AcDbFcf / Feature Control Frame) entities were silently dropped // because there was no case for ACadSharp.Entities.Tolerance in GeoObjectFromEntity. // They must now be imported as Text objects at the insertion point. diff --git a/tests/CADability.Tests/RendererRecoveryTests.cs b/tests/CADability.Tests/RendererRecoveryTests.cs index 4487e7e8f..5e7fdd3af 100644 --- a/tests/CADability.Tests/RendererRecoveryTests.cs +++ b/tests/CADability.Tests/RendererRecoveryTests.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Reflection; -using System.Runtime.InteropServices; using CADability.GeoObject; using CADability.Substitutes; // with UseWindowsForms (the Windows build of the tests), System.Windows.Forms has a PaintEventArgs as well @@ -74,9 +73,9 @@ public void ViewPaintsAgainAfterAnException() [TestMethod] public void ClearingTheFontCacheDisposesTheGlyphLists() { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Assert.Inconclusive("the font cache needs GDI, which is only available on Windows"); + // the font cache gets its fonts from FontProvider.Current and no longer needs GDI (issue #412) FontCache fontCache = new FontCache(); - string[] dictionaries = { "cache", "centerLineCache", "fontCharacteristicsCache", "kerning" }; + string[] dictionaries = { "cache", "centerLineCache", "fontCharacteristicsCache", "faces" }; IDictionary cache = Dictionary(fontCache, "cache"); Type dictKey = typeof(FontCache).GetNestedType("DictKey", BindingFlags.NonPublic); Type dictVal = typeof(FontCache).GetNestedType("DictVal", BindingFlags.NonPublic); @@ -84,12 +83,12 @@ public void ClearingTheFontCacheDisposesTheGlyphLists() object withList = Activator.CreateInstance(dictVal); dictVal.GetField("list").SetValue(withList, list); dictVal.GetField("listCreated").SetValue(withList, true); - cache[Activator.CreateInstance(dictKey, "Arial", 0, 'A')] = withList; + cache[Activator.CreateInstance(dictKey, "Arial", 0, (int)'A')] = withList; object withoutList = Activator.CreateInstance(dictVal); // e.g. a space: the list is null dictVal.GetField("listCreated").SetValue(withoutList, true); - cache[Activator.CreateInstance(dictKey, "Arial", 0, ' ')] = withoutList; - Type kerningKey = typeof(FontCache).GetNestedType("KerningKey", BindingFlags.NonPublic); - Dictionary(fontCache, "kerning")[Activator.CreateInstance(kerningKey, "Arial", 0)] = new Dictionary<(char Left, char Right), double>(); + cache[Activator.CreateInstance(dictKey, "Arial", 0, (int)' ')] = withoutList; + Type fontKey = typeof(FontCache).GetNestedType("FontKey", BindingFlags.NonPublic); + Dictionary(fontCache, "faces")[Activator.CreateInstance(fontKey, "Arial", 0)] = null; fontCache.Clear(); diff --git a/tests/CADability.Tests/SyntheticFontFaceTests.cs b/tests/CADability.Tests/SyntheticFontFaceTests.cs new file mode 100644 index 000000000..aee489f41 --- /dev/null +++ b/tests/CADability.Tests/SyntheticFontFaceTests.cs @@ -0,0 +1,161 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Drawing; + +namespace CADability.Tests +{ + /// + /// A bold or italic style that a family does not have is synthesized like GDI+ did it for the texts of CADability before + /// the font service (issue #412). The expected outlines are what GDI+ returned on Windows for the glyph 'I' of fonts + /// without a bold face (AddString with FontStyle.Bold, in font units, y downwards). + /// + [TestClass] + public class SyntheticFontFaceTests + { + private static PointF[] Em(double em, params double[] xy) + { + PointF[] res = new PointF[xy.Length / 2]; + for (int i = 0; i < res.Length; ++i) res[i] = new PointF((float)(xy[2 * i] / em), (float)(xy[2 * i + 1] / em)); + return res; + } + private static byte[] Polygon(int n) + { + byte[] res = new byte[n]; + for (int i = 1; i < n; ++i) res[i] = 1; + res[n - 1] |= 0x80; + return res; + } + private static void AssertSame(PointF[] expected, PointF[] actual, double em, double tolerance, string what) + { + Assert.AreEqual(expected.Length, actual.Length, what); + for (int i = 0; i < expected.Length; ++i) + { + Assert.AreEqual(expected[i].X * em, actual[i].X * em, tolerance, $"{what}: x of point {i}"); + Assert.AreEqual(expected[i].Y * em, actual[i].Y * em, tolerance, $"{what}: y of point {i}"); + } + } + + [TestMethod] + public void BoldMovesTheEdgesFacingRightAndUpLikeGdiPlus() + { + // Lucida Console 'I' (em 2048): the right edges move 41 to the right, the upper edges 40 up, the others stay + PointF[] regular = Em(2048, 173, 1616, 173, 1468, 518, 1468, 518, 481, 173, 481, 173, 333, 1060, 333, 1060, 481, 715, 481, 715, 1468, 1060, 1468, 1060, 1616); + PointF[] gdiPlus = Em(2048, 173, 1616, 173, 1428, 518, 1428, 518, 481, 173, 481, 173, 293, 1101, 293, 1101, 481, 756, 481, 756, 1428, 1101, 1428, 1101, 1616); + PointF[] bold = SyntheticFontFace.Embolden(regular, Polygon(regular.Length), 41.0 / 2048, 40.0 / 2048); + AssertSame(gdiPlus, bold, 2048, 0.01, "Lucida Console 'I'"); + + // Microsoft Sans Serif 'I', a rectangle with the other orientation + regular = Em(2048, 383, 1888, 188, 1888, 188, 422, 383, 422); + gdiPlus = Em(2048, 424, 1888, 188, 1888, 188, 382, 424, 382); + bold = SyntheticFontFace.Embolden(regular, Polygon(regular.Length), 41.0 / 2048, 40.0 / 2048); + AssertSame(gdiPlus, bold, 2048, 0.01, "Microsoft Sans Serif 'I'"); + } + + [TestMethod] + public void BoldMakesTheHoleOfAGlyphSmaller() + { + // a square of 100 with a hole of 50 in the middle, the hole running the other way (y downwards) + PointF[] points = Em(1000, 0, 0, 100, 0, 100, 100, 0, 100, 25, 25, 25, 75, 75, 75, 75, 25); + byte[] types = { 0, 1, 1, 0x81, 0, 1, 1, 0x81 }; + PointF[] bold = SyntheticFontFace.Embolden(points, types, 10.0 / 1000, 10.0 / 1000); + // outside: the right side and the top grow by 10; the hole: its left side and its bottom move by 10, so it gets + // smaller by 10 in both directions + double Width(int from) => (bold.Skip(from).Take(4).Max(p => p.X) - bold.Skip(from).Take(4).Min(p => p.X)) * 1000; + double Height(int from) => (bold.Skip(from).Take(4).Max(p => p.Y) - bold.Skip(from).Take(4).Min(p => p.Y)) * 1000; + Assert.AreEqual(110, Width(0), 1e-3); + Assert.AreEqual(110, Height(0), 1e-3); + Assert.AreEqual(40, Width(4), 1e-3, "the hole gets narrower"); + Assert.AreEqual(40, Height(4), 1e-3, "the hole gets lower"); + } + + [TestMethod] + public void BoldMovesShortEdgesAndSharpCornersNoFurtherThanTheStrength() + { + // an L (y downwards) whose inner corner is cut by an edge of 1.4: GDI+ moves the ends of the short edge by the + // whole strength like the long edges (FreeType would limit the shift at an inner corner by the length of the edge) + PointF[] points = Em(1000, 0, 100, 100, 100, 100, 60, 41, 60, 40, 59, 40, 0, 0, 0); + PointF[] bold = SyntheticFontFace.Embolden(points, Polygon(points.Length), 10.0 / 1000, 10.0 / 1000); + Assert.AreEqual(50, bold[3].Y * 1000, 1e-3, "the end of the upper edge of the foot"); + Assert.AreEqual(50, bold[4].X * 1000, 1e-3, "the end of the right edge of the stem"); + Assert.AreEqual(50, bold[2].Y * 1000, 1e-3); + Assert.AreEqual(50, bold[5].X * 1000, 1e-3); + + // a thin spike to the right: its tip moves no further than the strength on either axis + points = Em(1000, 0, 100, 0, 0, 500, 45, 500, 55); + bold = SyntheticFontFace.Embolden(points, Polygon(points.Length), 10.0 / 1000, 10.0 / 1000); + for (int i = 0; i < points.Length; ++i) + { + Assert.IsTrue((double)(bold[i].X - points[i].X) * 1000 is >= -1e-3 and <= 10 + 1e-3, "x of point " + i); + Assert.IsTrue((double)(points[i].Y - bold[i].Y) * 1000 is >= -1e-3 and <= 10 + 1e-3, "y of point " + i); + } + } + + /// + /// A face that only answers its metrics, for . + /// + private sealed class MetricsFace : FontFace + { + private readonly int em; private readonly bool bold; + public MetricsFace(int em, bool bold) { this.em = em; this.bold = bold; } + public override string FamilyName => "Metrics"; + public override bool IsBold => bold; + public override bool IsItalic => false; + public override int EmHeight => em; + public override int CellAscent => em; + public override int CellDescent => 0; + public override int LineSpacing => em; + public override double GetAdvanceWidth(int codePoint) => 0.5; + public override double GetKerning(int left, int right) => 0.0; + public override void GetGlyphPath(int codePoint, bool underline, bool strikeout, out PointF[] points, out byte[] types) { points = new PointF[0]; types = new byte[0]; } + } + + [TestMethod] + public void TheBoldStrengthDependsOnTheEmLikeInGdiPlus() + { + // measured with GDI+ on Windows, in font units: right, up + foreach ((int em, int x, int y) in new[] { (2048, 41, 40), (4096, 82, 81), (1024, 21, 20), (1000, 20, 19), (256, 6, 5) }) + Assert.AreEqual(((double)x / em, (double)y / em), SyntheticFontFace.BoldStrengthOf(new MetricsFace(em, false)), "em " + em); + // GDI+ does not make a bold, semibold or heavier face any wider + Assert.AreEqual((0.0, 0.0), SyntheticFontFace.BoldStrengthOf(new MetricsFace(2048, true))); + } + + [TestMethod] + public void AMissingStyleIsSynthesizedFromTheClosestFace() + { + // the test fonts have Liberation Sans in the regular face only + ManagedFontProvider provider = new ManagedFontProvider(new[] { TestFonts.Directory }, false); + FontFace regular = provider.GetFace("Liberation Sans", false, false); + FontFace italic = provider.GetFace("Liberation Sans", false, true); + FontFace bold = provider.GetFace("Liberation Sans", true, false); + FontFace boldItalic = provider.GetFace("Liberation Sans", true, true); + Assert.IsFalse(regular.IsBold || regular.IsItalic); + Assert.IsTrue(italic.IsItalic && !italic.IsBold); + Assert.IsTrue(bold.IsBold && !bold.IsItalic); + Assert.IsTrue(boldItalic.IsBold && boldItalic.IsItalic); + Assert.AreSame(italic, provider.GetFace("Liberation Sans", false, true), "a synthetic face is made once"); + Assert.AreEqual("Liberation Sans", italic.FamilyName); + Assert.AreEqual(regular.GetAdvanceWidth('H'), bold.GetAdvanceWidth('H'), "the advance widths stay the same"); + Assert.AreEqual(regular.CellAscent, italic.CellAscent); + + // italic: a shear by 0x5700/0x10000 around the baseline, the outline is otherwise the same + regular.GetGlyphPath('H', false, false, out PointF[] r, out byte[] rt); + italic.GetGlyphPath('H', false, false, out PointF[] i, out byte[] it); + CollectionAssert.AreEqual(rt, it); + double baseline = regular.CellAscent / (double)regular.EmHeight; + for (int k = 0; k < r.Length; ++k) + { + Assert.AreEqual(r[k].Y, i[k].Y, 1e-6); + Assert.AreEqual(r[k].X + 0x5700 / 65536.0 * (baseline - r[k].Y), i[k].X, 1e-5); + } + + // bold: wider to the right and higher, the left side and the baseline stay + bold.GetGlyphPath('I', false, false, out PointF[] b, out _); + regular.GetGlyphPath('I', false, false, out PointF[] ri, out _); + // GDI+: ceiling(em / 50) font units to the right, one less up (Liberation Sans has 2048 units per em: 41 and 40) + Assert.AreEqual((41.0 / 2048, 40.0 / 2048), SyntheticFontFace.BoldStrengthOf(regular)); + Assert.AreEqual(ri.Min(p => p.X), b.Min(p => p.X), 1e-5, "the left side"); + Assert.AreEqual(ri.Max(p => p.Y), b.Max(p => p.Y), 1e-5, "the baseline"); + Assert.AreEqual(ri.Max(p => p.X) + 41.0 / 2048, b.Max(p => p.X), 1e-5, "the right side"); + Assert.AreEqual(ri.Min(p => p.Y) - 40.0 / 2048, b.Min(p => p.Y), 1e-5, "the top"); + } + } +} diff --git a/tests/CADability.Tests/SyntheticStyleGdiTests.cs b/tests/CADability.Tests/SyntheticStyleGdiTests.cs new file mode 100644 index 000000000..94f97896c --- /dev/null +++ b/tests/CADability.Tests/SyntheticStyleGdiTests.cs @@ -0,0 +1,97 @@ +#if WINDOWS +using CADability.Forms; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Drawing; +using System.Text; + +namespace CADability.Tests +{ + /// + /// The synthetic bold and italic of the managed provider against GDI+, which the texts of CADability used before the + /// font service (issue #412). asks GDI+ for the outline with the requested style, and + /// GDI+ synthesizes a style that the family does not have. GDI+ returns hinted outlines, so the change that the + /// synthetic style makes to the regular outline is compared point by point: the bold or italic outline minus the + /// regular one, of the managed provider and of GDI+. + /// + [TestClass] + public class SyntheticStyleGdiTests + { + private static readonly GdiFontProvider gdi = new GdiFontProvider(); + private static readonly ManagedFontProvider managed = new ManagedFontProvider(); + + /// + /// The point of the GDI+ outline for every point of the managed one: GDI+ may start a contour at another point (e.g. + /// for MS Gothic), so the points are matched by their position. Null, if a point has no counterpart. + /// + private static int[] Match(PointF[] managedPoints, PointF[] gdiPoints, double em) + { + int[] res = new int[managedPoints.Length]; + for (int i = 0; i < managedPoints.Length; ++i) + { + bool Same(int j) => Math.Abs(managedPoints[i].X - gdiPoints[j].X) * em < 0.5 && Math.Abs(managedPoints[i].Y - gdiPoints[j].Y) * em < 0.5; + if (Same(i)) { res[i] = i; continue; } + res[i] = Enumerable.Range(0, gdiPoints.Length).Where(Same).DefaultIfEmpty(-1).First(); + if (res[i] < 0) return null; + } + return res; + } + + [TestMethod] + [DataRow("Microsoft Sans Serif", 0.0)] + [DataRow("Lucida Console", 0.0)] + [DataRow("Lucida Sans Unicode", 0.0)] + [DataRow("Impact", 0.0)] + [DataRow("MS Gothic", 0.0)] // 256 units per em + // GDI+ moves single points of these two differently, without a rule that could be found: e.g. a point in the middle + // of a straight slanted edge of the '8' of Sylfaen only moves up. The differences are up to 1.6% (Sylfaen) and 0.85% + // (Gabriola) of the em height, so these families are compared with a tolerance of 2% of the em height. + [DataRow("Sylfaen", 0.02)] + [DataRow("Gabriola", 0.02)] // 4096 units per em + public void SyntheticStylesMatchGdiPlus(string family, double toleranceOfEm) + { + FontFace mRegular = managed.GetFace(family, false, false); + if (mRegular == null || gdi.GetFace(family, false, false) == null) Assert.Inconclusive(family + " is not installed"); + StringBuilder report = new StringBuilder(); + int compared = 0; + foreach ((bool bold, bool italic) in new[] { (true, false), (false, true), (true, true) }) + { + FontFace m = managed.GetFace(family, bold, italic); + if (!(m is SyntheticFontFace)) continue; // the family has this face + FontFace g = gdi.GetFace(family, bold, italic), gRegular = gdi.GetFace(family, false, false); + double em = mRegular.EmHeight; + double tolerance = Math.Max(2.0, toleranceOfEm * em); // in font units + foreach (char c in "HIlo8") + { + mRegular.GetGlyphPath(c, false, false, out PointF[] mr, out _); + m.GetGlyphPath(c, false, false, out PointF[] ms, out _); + gRegular.GetGlyphPath(c, false, false, out PointF[] gr, out _); + g.GetGlyphPath(c, false, false, out PointF[] gs, out _); + string what = $"{family}{(bold ? " bold" : "")}{(italic ? " italic" : "")} '{c}'"; + double worst = 0.0; + int[] match = mr.Length == ms.Length && gr.Length == gs.Length && mr.Length == gr.Length ? Match(mr, gr, em) : null; + if (match != null) + { + for (int i = 0; i < mr.Length; ++i) + { + // the change of every point in font units + int j = match[i]; + double dx = ((ms[i].X - mr[i].X) - (gs[j].X - gr[j].X)) * em, dy = ((ms[i].Y - mr[i].Y) - (gs[j].Y - gr[j].Y)) * em; + worst = Math.Max(worst, Math.Max(Math.Abs(dx), Math.Abs(dy))); + } + } + else + { // GDI+ splits the curves differently or has other points: compare the change of the bounding box + double[] Box(PointF[] p) => new double[] { p.Min(q => q.X), p.Min(q => q.Y), p.Max(q => q.X), p.Max(q => q.Y) }; + double[] a = Box(mr), b = Box(ms), c2 = Box(gr), d = Box(gs); + for (int k = 0; k < 4; ++k) worst = Math.Max(worst, Math.Abs((b[k] - a[k]) - (d[k] - c2[k])) * em); + } + if (worst > tolerance) report.AppendLine($"{what}: the synthetic style differs from GDI+ by {worst:F1} font units"); + ++compared; + } + } + if (compared == 0) Assert.Inconclusive(family + " has all styles"); + Assert.AreEqual(0, report.Length, report.ToString()); + } + } +} +#endif diff --git a/tests/CADability.Tests/TestFonts.cs b/tests/CADability.Tests/TestFonts.cs new file mode 100644 index 000000000..ea18ad0f9 --- /dev/null +++ b/tests/CADability.Tests/TestFonts.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CADability.Tests +{ + /// + /// Texts get their fonts from FontProvider.Current. The tests add Liberation Sans (Files/Fonts) to the fonts of the system, + /// so that texts have a font on every machine, also on a build server without any fonts installed (issue #412). + /// + [TestClass] + public static class TestFonts + { + public static string Directory => System.IO.Path.Combine(AppContext.BaseDirectory, "Files", "Fonts"); + + [AssemblyInitialize] + public static void UseTestFonts(TestContext context) + { + FontProvider.Current = new ManagedFontProvider(new[] { Directory }, true); + } + } +} diff --git a/tests/CADability.Tests/TrueTypeFontTests.cs b/tests/CADability.Tests/TrueTypeFontTests.cs new file mode 100644 index 000000000..2d5f6b7c7 --- /dev/null +++ b/tests/CADability.Tests/TrueTypeFontTests.cs @@ -0,0 +1,305 @@ +using CADability.Curve2D; +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 managed TrueType reader, which replaces GDI for text on every platform (issue #412, step 6). The reference data + /// (LiberationSans-Regular.reference.json) was written by fontTools from the same font file, so every value here is + /// checked against an independent implementation. + /// + [TestClass] + public class TrueTypeFontTests + { + private static string FontFile(string name, [CallerFilePath] string thisFile = "") + => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "Fonts", name); + + private static TrueTypeFont LoadLiberationSans() + { + TrueTypeFont[] fonts = TrueTypeFont.Load(FontFile("LiberationSans-Regular.ttf")); + Assert.AreEqual(1, fonts.Length); + return fonts[0]; + } + private static JsonElement Reference() + { + return JsonDocument.Parse(File.ReadAllText(FontFile("LiberationSans-Regular.reference.json"))).RootElement; + } + + [TestMethod] + public void NamesAndMetricsMatchFontTools() + { + TrueTypeFont font = LoadLiberationSans(); + JsonElement reference = Reference(); + Assert.AreEqual(reference.GetProperty("family").GetString(), font.FamilyName); + Assert.AreEqual(reference.GetProperty("subfamily").GetString(), font.SubfamilyName); + Assert.AreEqual(reference.GetProperty("full").GetString(), font.FullName); + Assert.AreEqual(reference.GetProperty("unitsPerEm").GetInt32(), font.UnitsPerEm); + Assert.AreEqual(reference.GetProperty("ascender").GetInt32(), font.Ascender); + Assert.AreEqual(reference.GetProperty("descender").GetInt32(), font.Descender); + Assert.AreEqual(reference.GetProperty("lineGap").GetInt32(), font.LineGap); + Assert.AreEqual(reference.GetProperty("winAscent").GetInt32(), font.WinAscent); + Assert.AreEqual(reference.GetProperty("winDescent").GetInt32(), font.WinDescent); + Assert.AreEqual(reference.GetProperty("glyphCount").GetInt32(), font.GlyphCount); + Assert.IsFalse(font.IsBold); + Assert.IsFalse(font.IsItalic); + Assert.IsTrue(font.HasTrueTypeOutlines); + } + + [TestMethod] + public void GlyphsMatchFontTools() + { + TrueTypeFont font = LoadLiberationSans(); + int composites = 0; + foreach (JsonElement expected in Reference().GetProperty("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}'"); + Assert.AreEqual(expected.GetProperty("advance").GetInt32(), font.GetAdvanceWidth(glyph), $"advance width of '{c}'"); + if (expected.GetProperty("composite").GetBoolean()) ++composites; + List contours = font.GetContours(glyph); + JsonElement expectedContours = expected.GetProperty("contours"); + Assert.AreEqual(expectedContours.GetArrayLength(), contours.Count, $"number of contours of '{c}'"); + int ci = 0; + foreach (JsonElement expectedContour in expectedContours.EnumerateArray()) + { + GlyphPoint[] contour = contours[ci++]; + Assert.AreEqual(expectedContour.GetArrayLength(), contour.Length, $"points of contour {ci} of '{c}'"); + int pi = 0; + foreach (JsonElement p in expectedContour.EnumerateArray()) + { + GlyphPoint actual = contour[pi++]; + // fontTools rounds the points of transformed components to integers + Assert.AreEqual(p[0].GetInt32(), actual.X, 0.51, $"x of point {pi} of '{c}'"); + Assert.AreEqual(p[1].GetInt32(), actual.Y, 0.51, $"y of point {pi} of '{c}'"); + Assert.AreEqual(p[2].GetInt32() == 1, actual.OnCurve, $"on curve flag of point {pi} of '{c}'"); + } + } + } + Assert.IsTrue(composites > 0, "the reference contains composite glyphs (e.g. Ä)"); + } + + [TestMethod] + public void KerningMatchesFontTools() + { + TrueTypeFont font = LoadLiberationSans(); + JsonElement reference = Reference(); + Assert.AreEqual(reference.GetProperty("kerningPairCount").GetInt32(), font.GetKerningPairs().Count()); + foreach (JsonElement pair in reference.GetProperty("kerning").EnumerateArray()) + { + int left = font.GetGlyphIndex(pair.GetProperty("left").GetString()[0]); + int right = font.GetGlyphIndex(pair.GetProperty("right").GetString()[0]); + Assert.AreEqual(pair.GetProperty("value").GetInt32(), font.GetKerning(left, right), pair.GetProperty("left").GetString() + pair.GetProperty("right").GetString()); + } + Assert.AreEqual(0, font.GetKerning(font.GetGlyphIndex('H'), font.GetGlyphIndex('H')), "no kerning between straight letters"); + } + + [TestMethod] + public void MissingCharacterAndSpace() + { + TrueTypeFont font = LoadLiberationSans(); + Assert.AreEqual(0, font.GetGlyphIndex(0x10FFFF), "a character the font does not have gives the missing glyph"); + int space = font.GetGlyphIndex(' '); + Assert.AreNotEqual(0, space); + Assert.AreEqual(0, font.GetContours(space).Count); + font.GetPathData(space, out PointF[] points, out byte[] types); + Assert.AreEqual(0, points.Length); + Assert.AreEqual(0, types.Length); + } + + [TestMethod] + public void FontCollectionIsRead() + { + // a .ttc file with the same font twice: the table offsets of a collection count from the start of the file + byte[] ttf = File.ReadAllBytes(FontFile("LiberationSans-Regular.ttf")); + byte[] ttc = MakeCollection(ttf, 2); + TrueTypeFont[] fonts = TrueTypeFont.Load(ttc); + Assert.AreEqual(2, fonts.Length); + TrueTypeFont single = LoadLiberationSans(); + foreach (TrueTypeFont font in fonts) + { + Assert.AreEqual(single.FamilyName, font.FamilyName); + int glyph = font.GetGlyphIndex('g'); + Assert.AreEqual(single.GetGlyphIndex('g'), glyph); + List expected = single.GetContours(glyph), actual = font.GetContours(glyph); + Assert.AreEqual(expected.Count, actual.Count); + for (int i = 0; i < expected.Count; ++i) CollectionAssert.AreEqual(expected[i], actual[i]); + } + } + + [TestMethod] + public void PathDataIsInTheGdiPlusLayout() + { + TrueTypeFont font = LoadLiberationSans(); + double em = font.UnitsPerEm; + // the "H" stands on the baseline, which is at CellAscent / em below the top of the cell + font.GetPathData(font.GetGlyphIndex('H'), out PointF[] points, out byte[] types); + Assert.AreEqual(0, types[0], "a figure starts with a start point"); + Assert.AreEqual(1, types.Count(t => (t & 0x80) != 0), "the H is a single closed figure"); + Assert.IsTrue(types.All(t => (t & 0x7F) == 0 || (t & 0x7F) == 1), "the H consists of lines only"); + Assert.AreEqual(font.CellAscent / em, points.Max(p => p.Y), 1e-6, "the bottom of the H is the baseline"); + // the "o" has two closed figures made of Bezier segments + font.GetPathData(font.GetGlyphIndex('o'), out points, out types); + Assert.AreEqual(2, types.Count(t => (t & 0x80) != 0)); + Assert.IsTrue(types.Count(t => (t & 0x7F) == 3) % 3 == 0, "Bezier points come in groups of three"); + } + + [TestMethod] + public void OutlinesAreTheExactQuadraticCurves() + { + // the cubic segments of the path data are the degree elevation of the quadratic segments of the font: points of the + // quadratic curves lie on the outline that GlyphShapeBuilder builds with the highest precision (exact splines) + TrueTypeFont font = LoadLiberationSans(); + double em = font.UnitsPerEm; + foreach (char c in "S@&8g") + { + int glyph = font.GetGlyphIndex(c); + font.GetPathData(glyph, out PointF[] points, out byte[] types); + Path2D[] paths = GlyphShapeBuilder.PathDataToPath2D(points, types, 2); + Assert.AreEqual(font.GetContours(glyph).Count, paths.Length, $"one path per contour of '{c}'"); + Assert.IsTrue(paths.All(p => p.IsClosed), $"all paths of '{c}' are closed"); + foreach (GeoPoint2D p in PointsOnQuadraticSegments(font.GetContours(glyph))) + { + GeoPoint2D mapped = new GeoPoint2D(p.x / em, 1.0 - (font.CellAscent - p.y) / em); // PathDataToPath2D turns y upwards + double distance = paths.Min(path => path.MinDistance(mapped)); + Assert.AreEqual(0.0, distance, 1e-5, $"point {p} of '{c}'"); + } + } + } + + /// + /// Liberation Sans with one table changed: gets the font data and the offset of the table. + /// + private static TrueTypeFont PatchedLiberationSans(string table, Action patch) + { + byte[] data = File.ReadAllBytes(FontFile("LiberationSans-Regular.ttf")); + int numTables = data[4] << 8 | data[5]; + for (int i = 0; i < numTables; ++i) + { + int record = 12 + 16 * i; + if (System.Text.Encoding.ASCII.GetString(data, record, 4) != table) continue; + patch(data, data[record + 8] << 24 | data[record + 9] << 16 | data[record + 10] << 8 | data[record + 11]); + return TrueTypeFont.Load(data).Single(); + } + throw new AssertFailedException("no table " + table); + } + + [TestMethod] + public void TheOutlineStartsAtTheLeftSideBearingOfHmtx() + { + // Times New Roman Italic and Symbol have glyphs whose xMin is not the left side bearing in "hmtx". Windows (and + // FreeType) move such a glyph so that its left edge is at the left side bearing; the test makes 'H' such a glyph. + TrueTypeFont font = LoadLiberationSans(); + int glyph = font.GetGlyphIndex('H'); + Assert.IsTrue(glyph < font.GlyphCount); + TrueTypeFont patched = PatchedLiberationSans("hmtx", (data, hmtx) => + { + int lsb = (short)(data[hmtx + 4 * glyph + 2] << 8 | data[hmtx + 4 * glyph + 3]) + 100; + data[hmtx + 4 * glyph + 2] = (byte)(lsb >> 8); + data[hmtx + 4 * glyph + 3] = (byte)lsb; + }); + List original = font.GetContours(glyph), moved = patched.GetContours(glyph); + Assert.AreEqual(original.Count, moved.Count); + for (int c = 0; c < original.Count; ++c) + for (int i = 0; i < original[c].Length; ++i) + { + Assert.AreEqual(original[c][i].X + 100, moved[c][i].X); + Assert.AreEqual(original[c][i].Y, moved[c][i].Y); + } + Assert.AreEqual(font.GetAdvanceWidth(glyph), patched.GetAdvanceWidth(glyph), "the advance width stays"); + } + + [TestMethod] + public void ACompositeWithUseMyMetricsHasTheOriginOfThatComponent() + { + // 'Ü' of Liberation Sans is 'U' (with USE_MY_METRICS) and the dieresis. Times New Roman Bold has an 'Ü' whose left side + // bearing differs from its xMin, and Windows draws it like its 'U'; the test gives 'Ü' such a left side bearing. + TrueTypeFont font = LoadLiberationSans(); + int glyph = font.GetGlyphIndex('Ü'); + TrueTypeFont patched = PatchedLiberationSans("hmtx", (data, hmtx) => + { + int lsb = (short)(data[hmtx + 4 * glyph + 2] << 8 | data[hmtx + 4 * glyph + 3]) + 100; + data[hmtx + 4 * glyph + 2] = (byte)(lsb >> 8); + data[hmtx + 4 * glyph + 3] = (byte)lsb; + }); + List original = font.GetContours(glyph), unchanged = patched.GetContours(glyph); + Assert.AreEqual(3, original.Count); + for (int c = 0; c < original.Count; ++c) + for (int i = 0; i < original[c].Length; ++i) Assert.AreEqual(original[c][i].X, unchanged[c][i].X); + } + + [TestMethod] + public void UseTypoMetricsSwitchesTheCellToTheTypographicMetrics() + { + // Bahnschrift, Cascadia, Sitka and Cambria Math set USE_TYPO_METRICS (bit 7 of fsSelection), and GDI+ then takes + // the cell and the line spacing from the typographic metrics; the test sets the bit in Liberation Sans + TrueTypeFont font = LoadLiberationSans(); + Assert.IsFalse(font.UseTypoMetrics); + Assert.AreEqual(font.WinAscent, font.CellAscent); + Assert.AreEqual(font.WinDescent, font.CellDescent); + TrueTypeFont patched = PatchedLiberationSans("OS/2", (data, os2) => data[os2 + 63] |= 0x80); + Assert.IsTrue(patched.UseTypoMetrics); + Assert.AreEqual(1491, patched.CellAscent); + Assert.AreEqual(431, patched.CellDescent); + Assert.AreEqual(1491 + 431 + 307, patched.LineSpacing); + } + + [TestMethod] + public void InvalidDataIsRejected() + { + Assert.ThrowsException(() => TrueTypeFont.Load(new byte[0])); + Assert.ThrowsException(() => TrueTypeFont.Load(System.Text.Encoding.ASCII.GetBytes("this is not a font file at all"))); + } + + private static IEnumerable PointsOnQuadraticSegments(List contours) + { + foreach (GlyphPoint[] contour in contours) + { + int n = contour.Length; + for (int i = 0; i < n; ++i) + { + GlyphPoint p = contour[i]; + if (p.OnCurve) continue; + GlyphPoint prev = contour[(i + n - 1) % n], next = contour[(i + 1) % n]; + // implied on-curve points between two off-curve points + GeoPoint2D s = prev.OnCurve ? new GeoPoint2D(prev.X, prev.Y) : new GeoPoint2D((prev.X + p.X) / 2, (prev.Y + p.Y) / 2); + GeoPoint2D e = next.OnCurve ? new GeoPoint2D(next.X, next.Y) : new GeoPoint2D((next.X + p.X) / 2, (next.Y + p.Y) / 2); + GeoPoint2D ctrl = new GeoPoint2D(p.X, p.Y); + foreach (double t in new[] { 0.25, 0.5, 0.75 }) + { + double u = 1 - t; + yield return new GeoPoint2D(u * u * s.x + 2 * u * t * ctrl.x + t * t * e.x, u * u * s.y + 2 * u * t * ctrl.y + t * t * e.y); + } + } + } + } + private static byte[] MakeCollection(byte[] ttf, int count) + { + int header = 12 + 4 * count; + byte[] res = new byte[header + count * ttf.Length]; + void Put32(int pos, uint v) { res[pos] = (byte)(v >> 24); res[pos + 1] = (byte)(v >> 16); res[pos + 2] = (byte)(v >> 8); res[pos + 3] = (byte)v; } + res[0] = (byte)'t'; res[1] = (byte)'t'; res[2] = (byte)'c'; res[3] = (byte)'f'; + Put32(4, 0x00010000); + Put32(8, (uint)count); + int numTables = (ttf[4] << 8) | ttf[5]; + for (int f = 0; f < count; ++f) + { + int start = header + f * ttf.Length; + Put32(12 + 4 * f, (uint)start); + Array.Copy(ttf, 0, res, start, ttf.Length); + for (int i = 0; i < numTables; ++i) + { // move the table offsets of the table directory + int rec = start + 12 + 16 * i + 8; + uint offset = (uint)((res[rec] << 24) | (res[rec + 1] << 16) | (res[rec + 2] << 8) | res[rec + 3]); + Put32(rec, offset + (uint)start); + } + } + return res; + } + } +} diff --git a/tests/CADability.Tests/VariableFontTests.cs b/tests/CADability.Tests/VariableFontTests.cs new file mode 100644 index 000000000..963ab0847 --- /dev/null +++ b/tests/CADability.Tests/VariableFontTests.cs @@ -0,0 +1,336 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Text.Json; +using Path = System.IO.Path; + +namespace CADability.Tests +{ + /// + /// Variable fonts (issue #412, step 6): the named instances become faces of their own, and their outlines and advance widths + /// are varied with "gvar" (TrueType), "CFF2" and "HVAR". The test fonts are subsets of Source Sans 3 VF made by + /// make_variable_test_fonts.py, which also wrote the reference data with fontTools. + /// + [TestClass] + public class VariableFontTests + { + private static string FontFile(string name, [CallerFilePath] string thisFile = "") + => Path.Combine(Path.GetDirectoryName(thisFile), "Files", "Fonts", name); + + private static JsonElement Reference(string file) + { + foreach (JsonElement e in JsonDocument.Parse(File.ReadAllText(FontFile("VarTestSans.reference.json"))).RootElement.EnumerateArray()) + if (e.GetProperty("file").GetString() == file) return e; + throw new AssertFailedException("no reference for " + file); + } + private static double[] Numbers(JsonElement array) => array.EnumerateArray().Select(v => v.GetDouble()).ToArray(); + + 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])); + } + + [TestMethod] + public void AxesAndNamedInstances() + { + foreach (string file in new[] { "VarTestSans.ttf", "VarTestSans.otf" }) + { + TrueTypeFont font = TrueTypeFont.Load(FontFile(file)).Single(); + Assert.IsTrue(font.IsVariable, file); + VariationAxis axis = font.Axes.Single(); + Assert.AreEqual("wght", axis.Tag); + Assert.AreEqual(200, axis.Min); + Assert.AreEqual(200, axis.Default); + Assert.AreEqual(900, axis.Max); + CollectionAssert.AreEqual(Reference(file).GetProperty("instances").EnumerateArray().Select(n => n.GetString()).ToArray(), + font.NamedInstances.Select(i => i.SubfamilyName).ToArray(), file); + Assert.IsNull(font.WithVariation(new[] { 200.0 }).NormalizedCoordinates, "the default instance is not varied"); + } + Assert.IsFalse(TrueTypeFont.Load(FontFile("LiberationSans-Regular.ttf")).Single().IsVariable); + } + + [TestMethod] + public void AdvanceWidthsMatchFontTools() + { + foreach (string file in new[] { "VarTestSans.ttf", "VarTestSans-NoHVAR.ttf", "VarTestSans.otf" }) + { + JsonElement reference = Reference(file); + // with HVAR the widths are exact; without it fontTools rounds the widths from the phantom points to whole units + double tolerance = file.Contains("NoHVAR") ? 0.5 : 1e-6; + TrueTypeFont font = TrueTypeFont.Load(FontFile(file)).Single(); + int[] glyphs = reference.GetProperty("glyphs").EnumerateArray().Select(g => g.GetInt32()).ToArray(); + string chars = reference.GetProperty("chars").GetString(); + foreach (JsonElement instance in reference.GetProperty("advances").EnumerateArray()) + { + double[] coordinates = Numbers(instance.GetProperty("coordinates")); + TrueTypeFont varied = font.WithVariation(coordinates); + double[] widths = Numbers(instance.GetProperty("widths")); + for (int i = 0; i < glyphs.Length; ++i) + { + Assert.AreEqual(glyphs[i], varied.GetGlyphIndex(chars[i])); + Assert.AreEqual(widths[i], varied.GetAdvanceWidth(glyphs[i]), tolerance, $"{file} wght {coordinates[0]}: advance of '{chars[i]}'"); + } + } + } + } + + [TestMethod] + public void TheStrikeoutLineVariesWithMvar() + { + // "MVAR" of the test fonts varies the strikeout position ("stro"); the reference has the values of fontTools' + // instancer. Bahnschrift varies the strikeout size ("strs") this way. + foreach (string file in new[] { "VarTestSans.ttf", "VarTestSans.otf" }) + { + TrueTypeFont font = TrueTypeFont.Load(FontFile(file)).Single(); + foreach (JsonElement instance in Reference(file).GetProperty("advances").EnumerateArray()) + { + double[] coordinates = Numbers(instance.GetProperty("coordinates")); + double[] strikeout = Numbers(instance.GetProperty("strikeout")); + TrueTypeFont varied = font.WithVariation(coordinates); + Assert.AreEqual(strikeout[0], varied.StrikeoutPosition, $"{file} wght {coordinates[0]}"); + Assert.AreEqual(strikeout[1], varied.StrikeoutSize, $"{file} wght {coordinates[0]}"); + } + } + } + + [TestMethod] + public void InstancesAreNamedInTheAxisOrderingOfStat() + { + // Windows names the instances from "STAT": the names of the axis values in the axis ordering, without the elidable + // ones. Segoe UI Variable has the instance "Light Display" in "fvar", which Windows calls "Display Light". The test + // font has the axes wght (ordering 0) and ital (ordering 1, not in "fvar", its only value "Regular" is elidable). + byte[] data = File.ReadAllBytes(FontFile("VarTestSans.ttf")); + Assert.AreEqual("Light", TrueTypeFont.Load(data).Single().InstanceName(TrueTypeFont.Load(data).Single().NamedInstances[1])); + int stat = TableOffset(data, "STAT"); + int axisSize = U16(data, stat + 4), axes = stat + U32(data, stat + 8); + int values = stat + U32(data, stat + 14), valueCount = U16(data, stat + 12); + for (int v = 0; v < valueCount; ++v) + { // make the value of ital not elidable + int p = values + U16(data, values + 2 * v); + if (U16(data, p) == 3) data[p + 5] = 0; + } + TrueTypeFont font = TrueTypeFont.Load((byte[])data.Clone()).Single(); + Assert.AreEqual("Light Regular", font.InstanceName(font.NamedInstances[1])); + // swap the ordering of the axes + data[axes + 7] = 1; + data[axes + axisSize + 7] = 0; + font = TrueTypeFont.Load(data).Single(); + Assert.AreEqual("Regular Light", font.InstanceName(font.NamedInstances[1])); + Assert.AreEqual("Regular", font.InstanceName(font.NamedInstances[2]), "the elidable Regular of wght is left out"); + } + private static int U16(byte[] data, int p) => data[p] << 8 | data[p + 1]; + private static int U32(byte[] data, int p) => data[p] << 24 | data[p + 1] << 16 | data[p + 2] << 8 | data[p + 3]; + private static int TableOffset(byte[] data, string tag) + { + for (int i = 0; i < U16(data, 4); ++i) + if (System.Text.Encoding.ASCII.GetString(data, 12 + 16 * i, 4) == tag) return U32(data, 12 + 16 * i + 8); + throw new AssertFailedException("no table " + tag); + } + + [TestMethod] + public void Cff2OutlinesMatchFontTools() + { + JsonElement reference = Reference("VarTestSans.otf"); + TrueTypeFont font = TrueTypeFont.Load(FontFile("VarTestSans.otf")).Single(); + Assert.IsTrue(font.IsCff2); + foreach (JsonElement location in reference.GetProperty("locations").EnumerateArray()) + { + double[] coordinates = Numbers(location.GetProperty("coordinates")); + TrueTypeFont varied = font.WithVariation(coordinates); + foreach (JsonElement glyph in location.GetProperty("glyphs").EnumerateArray()) + { + string c = glyph.GetProperty("char").GetString(); + Recorder recorder = new Recorder(); + varied.DrawCff(varied.GetGlyphIndex(c[0]), recorder); + JsonElement commands = glyph.GetProperty("commands"); + Assert.AreEqual(commands.GetArrayLength(), recorder.Commands.Count, $"wght {coordinates[0]}: 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); + double[] expected = Numbers(command[1]); + for (int k = 0; k < args.Length; ++k) Assert.AreEqual(expected[k], args[k], 1e-5, $"wght {coordinates[0]}: command {i} of '{c}'"); + } + } + } + } + + [TestMethod] + public void TrueTypeOutlinesMatchFontTools() + { + // the path data and fontTools may start a contour at different points, so the outlines are compared as point sets: + // every point sampled on one outline must lie on the other (the path data are floats, hence the tolerance) + foreach (string file in new[] { "VarTestSans.ttf", "VarTestSans-NoHVAR.ttf" }) + { + JsonElement reference = Reference(file); + TrueTypeFont font = TrueTypeFont.Load(FontFile(file)).Single(); + foreach (JsonElement location in reference.GetProperty("locations").EnumerateArray()) + { + double[] coordinates = Numbers(location.GetProperty("coordinates")); + TrueTypeFont varied = font.WithVariation(coordinates); + foreach (JsonElement glyph in location.GetProperty("glyphs").EnumerateArray()) + { + string c = glyph.GetProperty("char").GetString(); + List<(string op, double[] args)> expected = glyph.GetProperty("commands").EnumerateArray() + .Select(command => (command[0].GetString(), Numbers(command[1]))).ToList(); + varied.GetPathData(varied.GetGlyphIndex(c[0]), out PointF[] points, out byte[] types); + List<(string op, double[] args)> actual = Commands(points, types, varied.UnitsPerEm, varied.CellAscent); + List<(double x, double y)> a = Sample(actual), b = Sample(expected); + double distance = Math.Max(Directed(a, b), Directed(b, a)); + Assert.AreEqual(0.0, distance, 1e-3, $"{file} wght {coordinates[0]}: outline of '{c}'"); + } + } + } + } + + [TestMethod] + public void VariedOutlinesDifferFromTheDefault() + { + // a guard against comparing the default instance with itself: Black is much bolder than ExtraLight + TrueTypeFont font = TrueTypeFont.Load(FontFile("VarTestSans.ttf")).Single(); + int glyph = font.GetGlyphIndex('O'); + double light = font.WithVariation(new[] { 200.0 }).GetAdvanceWidth(glyph), black = font.WithVariation(new[] { 900.0 }).GetAdvanceWidth(glyph); + Assert.IsTrue(black > light + 20, $"{black} > {light}"); + } + + [TestMethod] + public void NamedInstancesAreFacesNamedLikeGdi() + { + ManagedFontProvider.InstanceFamily("Bahnschrift", "Light Condensed", out string family, out bool bold, out bool italic); + Assert.AreEqual(("Bahnschrift Light Condensed", false, false), (family, bold, italic)); + ManagedFontProvider.InstanceFamily("Bahnschrift", "Bold Condensed", out family, out bold, out italic); + Assert.AreEqual(("Bahnschrift Condensed", true, false), (family, bold, italic)); + ManagedFontProvider.InstanceFamily("Segoe UI Variable", "Text Semibold Italic", out family, out bold, out italic); + Assert.AreEqual(("Segoe UI Variable Text Semibold", false, true), (family, bold, italic)); + ManagedFontProvider.InstanceFamily("X", "Regular", out family, out bold, out italic); + Assert.AreEqual(("X", false, false), (family, bold, italic)); + + string directory = Path.Combine(Path.GetTempPath(), "CADability-vf-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(directory); + try + { + File.Copy(FontFile("VarTestSans.ttf"), Path.Combine(directory, "VarTestSans.ttf")); + File.Copy(FontFile("VarTestSans.otf"), Path.Combine(directory, "VarTestSans.otf")); + ManagedFontProvider provider = new ManagedFontProvider(new[] { directory }, false); + string[] families = provider.GetFamilyNames(); + foreach (string expected in new[] { "VarTestSans ExtraLight", "VarTestSans Light", "VarTestSans", "VarTestSans Medium", "VarTestSans Semibold", "VarTestSans Black", + "VarTestSansCFF2 Light", "VarTestSansCFF2" }) + CollectionAssert.Contains(families, expected); + + TrueTypeFont font = TrueTypeFont.Load(FontFile("VarTestSans.ttf")).Single(); + foreach ((string name, bool isBold, double wght) in new[] { ("VarTestSans", false, 400.0), ("VarTestSans", true, 700.0), ("VarTestSans Light", false, 300.0), ("VarTestSans Black", false, 900.0) }) + { + FontFace face = provider.GetFace(name, isBold, false); + Assert.AreEqual(name, face.FamilyName); + Assert.AreEqual(isBold, face.IsBold); + TrueTypeFont instance = font.WithVariation(new[] { wght }); + Assert.AreEqual(instance.GetAdvanceWidth(instance.GetGlyphIndex('W')) / instance.UnitsPerEm, face.GetAdvanceWidth('W'), 1e-12, $"{name} bold={isBold}"); + } + } + finally + { + Directory.Delete(directory, true); + } + } + + [TestMethod] + public void UntouchedPointsAreInterpolatedAndClampedLikeTheSpecification() + { + // The subsets have sparse tuples only in rectilinear glyphs, where no untouched point lies outside its reference + // points, so the clamping of IUP is checked here with a made-up glyph of three contours: in the first, the + // points 1 and 3 have deltas; in the second, only point 6; in the third, no point at all. + double[] x = { -50, 100, 150, 300, 400, 0, 10, 20, 0, 10 }; + double[] y = { 0, 0, 0, 0, 0, 0, 10, 20, 0, 10 }; + bool[] touched = { false, true, false, true, false, false, true, false, false, false }; + double[] dx = { 0, 10, 0, 30, 0, 0, 4, 0, 0, 0 }; + double[] dy = { 0, 5, 0, 5, 0, 0, -2, 0, 0, 0 }; + FontVariations.InterpolateUntouched(x, y, new[] { 4, 7, 9 }, touched, dx, dy); + // point 2 lies between its references, the points 0 and 4 outside: they get the delta of the nearer one + CollectionAssert.AreEqual(new double[] { 10, 10, 15, 30, 30, 4, 4, 4, 0, 0 }, dx); + // all y coordinates of the references are equal and so are their deltas + CollectionAssert.AreEqual(new double[] { 5, 5, 5, 5, 5, -2, -2, -2, 0, 0 }, dy); + } + + [TestMethod] + public void HeadersOfAVariableFontNameTheInstances() + { + TrueTypeFont header = TrueTypeFont.ReadHeaders(FontFile("VarTestSans.ttf")).Single(); + Assert.IsTrue(header.IsHeaderOnly); + Assert.IsTrue(header.IsVariable); + Assert.AreEqual(7, header.NamedInstances.Length); + Assert.AreEqual("Black", header.NamedInstances.Last().SubfamilyName); + } + + private static List<(string op, double[] args)> Commands(PointF[] points, byte[] types, int unitsPerEm, int cellAscent) + { // the path data back in font units, as drawing commands + List<(string, double[])> res = new List<(string, double[])>(); + (double, double) F(PointF p) => (p.X * unitsPerEm, cellAscent - p.Y * unitsPerEm); + for (int i = 0; i < points.Length; ++i) + { + int t = types[i] & 0x7F; + var (x, y) = F(points[i]); + if (t == 0) res.Add(("moveTo", new[] { x, y })); + else if (t == 1) res.Add(("lineTo", new[] { x, y })); + else + { + var (x2, y2) = F(points[i + 1]); + var (x3, y3) = F(points[i + 2]); + res.Add(("curveTo", new[] { x, y, x2, y2, x3, y3 })); + i += 2; + } + if ((types[i] & 0x80) != 0) res.Add(("closePath", new double[0])); + } + return res; + } + private static List<(double x, double y)> Sample(List<(string op, double[] args)> commands) + { + const int n = 8; + List<(double, double)> res = new List<(double, double)>(); + double cx = 0, cy = 0, sx = 0, sy = 0; + foreach (var (op, p) in commands) + { + if (op == "moveTo") + { + cx = sx = p[0]; + cy = sy = p[1]; + res.Add((cx, cy)); + } + else if (op == "lineTo" || op == "closePath") + { + double ex = op == "lineTo" ? p[0] : sx, ey = op == "lineTo" ? p[1] : sy; + for (int i = 1; i <= n; ++i) res.Add((cx + (ex - cx) * i / n, cy + (ey - cy) * i / n)); + cx = ex; + cy = ey; + } + else + { + for (int i = 1; i <= n; ++i) + { + double t = (double)i / n, u = 1 - t; + res.Add((u * u * u * cx + 3 * u * u * t * p[0] + 3 * u * t * t * p[2] + t * t * t * p[4], + u * u * u * cy + 3 * u * u * t * p[1] + 3 * u * t * t * p[3] + t * t * t * p[5])); + } + cx = p[4]; + cy = p[5]; + } + } + return res; + } + private static double Directed(List<(double x, double y)> a, List<(double x, double y)> b) + { + double max = 0; + foreach (var p in a) + { + double best = double.MaxValue; + foreach (var q in b) best = Math.Min(best, (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); + max = Math.Max(max, Math.Sqrt(best)); + } + return max; + } + } +}