From 4eb4251db53090b3bea1330120dc4693a0a1df32 Mon Sep 17 00:00:00 2001 From: ema Date: Mon, 30 Dec 2024 04:21:24 +0800 Subject: [PATCH] Temporary solution to read woff2 --- .../OurOpenFontSystemSetup.cs | 85 + .../QuickLook.Plugin.FontViewer/Plugin.cs | 52 +- .../QuickLook.Plugin.FontViewer.csproj | 5 + .../AdditionalInfo/AdobeGlyphList.cs | 4450 +++++++++++++++++ .../AdobeGlyphListForNewFont.cs | 713 +++ .../AdditionalInfo/Languages.cs | 157 + .../AdditionalInfo/MacPostFormat1.cs | 333 ++ .../OS2_IBMFontClassParameters.cs | 51 + .../BinaryReaderExtensions.cs | 18 + .../Typography.OpenFont/Bounds.cs | 34 + .../ByteOrderSwappingBinaryReader.cs | 70 + .../Typography.OpenFont/Geometry.cs | 76 + .../Typography.OpenFont/Glyph.cs | 359 ++ .../Typography.OpenFont/IGlyphTranslator.cs | 466 ++ .../Typography.OpenFont/OpenFontReader.cs | 629 +++ .../Typography.OpenFont/PreviewFontInfo.cs | 75 + .../Typography.OpenFont/README.MD | 23 + .../AttachmentListTable.cs | 63 + .../Tables.AdvancedLayout/Base.cs | 637 +++ .../Tables.AdvancedLayout/COLR.cs | 59 + .../Tables.AdvancedLayout/CPAL.cs | 93 + .../Tables.AdvancedLayout/ClassDefTable.cs | 206 + .../Tables.AdvancedLayout/CoverageTable.cs | 162 + .../Tables.AdvancedLayout/FeatureInfo.cs | 195 + .../Tables.AdvancedLayout/FeatureList.cs | 148 + .../Tables.AdvancedLayout/GDEF.cs | 328 ++ .../Tables.AdvancedLayout/GPOS.Others.cs | 888 ++++ .../Tables.AdvancedLayout/GPOS.cs | 1293 +++++ .../Tables.AdvancedLayout/GSUB.cs | 1628 ++++++ .../GlyphShapingTableEntry.cs | 218 + .../Tables.AdvancedLayout/IGlyphIndexList.cs | 35 + .../JustificationTable.cs | 290 ++ .../LigatureCaretListTable.cs | 215 + .../Tables.AdvancedLayout/MathTable.cs | 1331 +++++ .../Tables.AdvancedLayout/ScriptList.cs | 57 + .../Tables.AdvancedLayout/ScriptTable.cs | 173 + .../BitmapFontGlyphSource.cs | 42 + .../BitmapFontsCommon.cs | 851 ++++ .../Tables.BitmapAndSvgFonts/CBDT.cs | 110 + .../Tables.BitmapAndSvgFonts/CBLC.cs | 133 + .../Tables.BitmapAndSvgFonts/EBDT.cs | 64 + .../Tables.BitmapAndSvgFonts/EBLC.cs | 116 + .../Tables.BitmapAndSvgFonts/EBSC.cs | 39 + .../Tables.BitmapAndSvgFonts/SvgTable.cs | 195 + .../Typography.OpenFont/Tables.CFF/CFF.cs | 2183 ++++++++ .../Tables.CFF/CFFTable.cs | 116 + .../Tables.CFF/CffEvaluationEngine.cs | 1419 ++++++ .../Tables.CFF/Type2CharStringParser.cs | 1301 +++++ .../Tables.CFF/Type2InstructionCompacter.cs | 483 ++ .../Tables.Others/HorizontalDeviceMetrics.cs | 59 + .../Typography.OpenFont/Tables.Others/Kern.cs | 180 + .../Tables.Others/LinearThreashold.cs | 1 + .../Tables.Others/Merge.cs | 1 + .../Typography.OpenFont/Tables.Others/Meta.cs | 237 + .../Typography.OpenFont/Tables.Others/STAT.cs | 409 ++ .../Tables.Others/VerticalDeviceMetrics.cs | 71 + .../Tables.Others/VerticalMetrics.cs | 98 + .../Tables.Others/VerticalMetricsHeader.cs | 116 + .../Tables.TrueType/Cvt_Programs.cs | 54 + .../Tables.TrueType/Gasp.cs | 106 + .../Tables.TrueType/Glyf.cs | 533 ++ .../Tables.TrueType/GlyphLocations.cs | 82 + .../Tables.Variations/AVar.cs | 127 + .../Tables.Variations/CVar.cs | 28 + .../Common.ItemVariationStore.cs | 119 + .../Common.TupleVariationStore.cs | 504 ++ .../Tables.Variations/FVar.cs | 200 + .../Tables.Variations/GVar.cs | 415 ++ .../Tables.Variations/HVar.cs | 153 + .../Tables.Variations/MVar.cs | 307 ++ .../Tables.Variations/VVar.cs | 2 + .../Tables/CharacterMap.cs | 417 ++ .../Typography.OpenFont/Tables/Cmap.cs | 436 ++ .../Typography.OpenFont/Tables/Head.cs | 122 + .../Tables/HorizontalHeader.cs | 79 + .../Tables/HorizontalMetrics.cs | 100 + .../Typography.OpenFont/Tables/MaxProfile.cs | 79 + .../Typography.OpenFont/Tables/NameEntry.cs | 193 + .../Typography.OpenFont/Tables/OS2.cs | 622 +++ .../Typography.OpenFont/Tables/Post.cs | 214 + .../Typography.OpenFont/Tables/TableEntry.cs | 55 + .../Tables/TableEntryCollection.cs | 30 + .../Typography.OpenFont/Tables/TableHeader.cs | 40 + .../Typography.OpenFont/Tables/Utils.cs | 133 + .../InvalidFontException.cs | 22 + .../TrueTypeInterpreter.cs | 2148 ++++++++ .../Typography.OpenFont/Typeface.cs | 569 +++ .../Typeface_Extensions.cs | 652 +++ .../Typeface_TrimableExtensions.cs | 206 + .../WebFont/Woff2Reader.cs | 1928 +++++++ .../Typography.OpenFont/WebFont/WoffReader.cs | 364 ++ 91 files changed, 34874 insertions(+), 4 deletions(-) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/OurOpenFontSystemSetup.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphList.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphListForNewFont.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/Languages.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/MacPostFormat1.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/OS2_IBMFontClassParameters.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/BinaryReaderExtensions.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Bounds.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/ByteOrderSwappingBinaryReader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Geometry.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Glyph.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/IGlyphTranslator.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/OpenFontReader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/PreviewFontInfo.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/README.MD create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/AttachmentListTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/Base.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/COLR.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CPAL.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ClassDefTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CoverageTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureInfo.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureList.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GDEF.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.Others.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GSUB.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GlyphShapingTableEntry.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/IGlyphIndexList.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/JustificationTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/LigatureCaretListTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/MathTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptList.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontGlyphSource.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontsCommon.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBDT.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBLC.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBDT.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBLC.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBSC.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/SvgTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFF.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFFTable.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CffEvaluationEngine.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2CharStringParser.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2InstructionCompacter.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/HorizontalDeviceMetrics.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Kern.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/LinearThreashold.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Merge.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Meta.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/STAT.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalDeviceMetrics.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetrics.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetricsHeader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Cvt_Programs.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Gasp.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Glyf.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/GlyphLocations.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/AVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/CVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.ItemVariationStore.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.TupleVariationStore.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/FVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/GVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/HVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/MVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/VVar.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/CharacterMap.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Cmap.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Head.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalHeader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalMetrics.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/MaxProfile.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/NameEntry.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/OS2.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Post.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntry.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntryCollection.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableHeader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Utils.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/InvalidFontException.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/TrueTypeInterpreter.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_Extensions.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_TrimableExtensions.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/Woff2Reader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/WoffReader.cs diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/OurOpenFontSystemSetup.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/OurOpenFontSystemSetup.cs new file mode 100644 index 00000000..a6656729 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/OurOpenFontSystemSetup.cs @@ -0,0 +1,85 @@ +//MIT, 2016-present, WinterDev +using System; +using System.IO; + +using Typography.OpenFont.WebFont; +using BrotliSharpLib; +using System.IO.Compression; + +namespace SampleWinForms; + +public static class OurOpenFontSystemSetup +{ + public static void SetupWoffDecompressFunctions() + { + // + //Woff + // WoffDefaultZlibDecompressFunc.DecompressHandler = (byte[] compressedBytes, byte[] decompressedResult) => + // { + // //ZLIB + // //**** + // //YOU can change to your prefer decode libs*** + // //**** + + // bool result = false; + // try + // { + // var inflater = new ICSharpCode.SharpZipLib.Zip.Compression.Inflater(); + // inflater.SetInput(compressedBytes); + // inflater.Inflate(decompressedResult); + //#if DEBUG + // long outputLen = inflater.TotalOut; + // if (outputLen != decompressedResult.Length) + // { + // } + //#endif + + // result = true; + // } + // catch (Exception ex) + // { + // } + // return result; + // }; + //Woff2 + + Woff2DefaultBrotliDecompressFunc.DecompressHandler = (byte[] compressedBytes, Stream output) => + { + //BROTLI + //**** + //YOU can change to your prefer decode libs*** + //**** + + bool result = false; + try + { + using (var ms = new MemoryStream(compressedBytes)) + { + ms.Position = 0;//set to start pos + Decompress(ms, output); + } + result = true; + } + catch (Exception ex) + { + } + return result; + }; + } + + static void Decompress(Stream input, Stream output) + { + try + { + using (var bs = new BrotliStream(input, CompressionMode.Decompress)) + using (var ms = new MemoryStream()) + { + bs.CopyTo(output); + } + } + catch (IOException ex) + { + throw ex; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs index b8a708e8..4b8be324 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Plugin.cs @@ -18,6 +18,7 @@ using QuickLook.Common.Helpers; using QuickLook.Common.Plugin; using QuickLook.Plugin.HtmlViewer; +using SampleWinForms; using System; using System.IO; using System.IO.Packaging; @@ -26,6 +27,7 @@ using System.Text; using System.Windows; using System.Windows.Resources; +using Typography.OpenFont.WebFont; namespace QuickLook.Plugin.FontViewer; @@ -44,7 +46,8 @@ public void Init() public bool CanHandle(string path) { // The `*.eot` and `*.svg` font types are not supported - return !Directory.Exists(path) && new string[] { ".ttf", ".otf", ".woff", ".woff2" }.Any(path.ToLower().EndsWith); + // TODO: Check `*.otc` type + return !Directory.Exists(path) && new string[] { ".ttf", ".otf", ".woff", ".woff2", ".ttc" }.Any(path.ToLower().EndsWith); } public void Prepare(string path, ContextObject context) @@ -109,6 +112,7 @@ private string GenerateFontHtml(string path) // url('xxx.ttf') format('truetype'), // url('xxx.svg#xxx') format('svg'); var fileName = Path.GetFileName(path); + var fileNameWithoutExt = Path.GetFileNameWithoutExtension(path); var fileExt = Path.GetExtension(fileName); static string GenerateRandomString(int length) { @@ -124,9 +128,8 @@ static string GenerateRandomString(int length) return new string(result); } - string cssUrl = $"src: url('{fileName}'), url('{fileName.Substring(0, fileExt.Length)}?#{GenerateRandomString(5)}{fileExt}')" - + Path.GetExtension(path) - switch + string cssUrl = $"src: url('{fileName}'), url('{fileNameWithoutExt}?#{GenerateRandomString(5)}{fileExt}')" + + fileExt switch { ".eot" => " format('embedded-opentype');", ".woff" => " format('woff');", @@ -136,6 +139,47 @@ static string GenerateRandomString(int length) _ => ";", }; + if (string.IsNullOrEmpty(fontFamilyName)) + { + string GetWoff2FontFamilyName() + { + OurOpenFontSystemSetup.SetupWoffDecompressFunctions(); + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); + using var input = new BinaryReader(fs); + var woffReader = new Woff2Reader + { + DecompressHandler = Woff2DefaultBrotliDecompressFunc.DecompressHandler + }; + input.BaseStream.Position = 0; + var info = woffReader.ReadPreview(input); + + return info?.Name; + } + //string GetWoffFontFamilyName() + //{ + // OurOpenFontSystemSetup.SetupWoffDecompressFunctions(); + // using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); + // using var input = new BinaryReader(fs); + // var woffReader = new WoffReader + // { + // DecompressHandler = WoffDefaultZlibDecompressFunc.DecompressHandler + // }; + // input.BaseStream.Position = 0; + // var info = woffReader.ReadPreview(input); + + // return info?.Name; + //} + + if (fileExt.ToLower().Equals(".woff2")) + { + fontFamilyName = GetWoff2FontFamilyName(); + } + //else if (fileExt.ToLower().Equals(".woff")) + //{ + // fontFamilyName = GetWoffFontFamilyName(); + //} + } + html = html.Replace("--font-family;", $"font-family: '{fontFamilyName}';") .Replace("--font-url;", cssUrl) .Replace("{{h1}}", fontFamilyName ?? fileName); diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj index 9fb48fdf..80d3552a 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/QuickLook.Plugin.FontViewer.csproj @@ -80,6 +80,11 @@ + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphList.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphList.cs new file mode 100644 index 00000000..54340272 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphList.cs @@ -0,0 +1,4450 @@ +//BSD, 2015, Adobe Systems Incorporated +//from https://github.com/adobe-type-tools/agl-aglfn/blob/master/glyphlist.txt +//# ----------------------------------------------------------- +//# Copyright 2002, 2010, 2015 Adobe Systems Incorporated. +//# All rights reserved. +//# +//# Redistribution and use in source and binary forms, with or +//# without modification, are permitted provided that the +//# following conditions are met: +//# +//# Redistributions of source code must retain the above +//# copyright notice, this list of conditions and the following +//# disclaimer. +//# +//# Redistributions in binary form must reproduce the above +//# copyright notice, this list of conditions and the following +//# disclaimer in the documentation and/or other materials +//# provided with the distribution. +//# +//# Neither the name of Adobe Systems Incorporated nor the names +//# of its contributors may be used to endorse or promote +//# products derived from this software without specific prior +//# written permission. +//# +//# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +//# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +//# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +//# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +//# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +//# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +//# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +//# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +//# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//# ----------------------------------------------------------- +//# Name: Adobe Glyph List +//# Table version: 2.0 +//# Date: September 20, 2002 +//# URL: https://github.com/adobe-type-tools/agl-aglfn +//# +//# Format: two semicolon-delimited fields: +//# (1) glyph name--upper/lowercase letters and digits +//# (2) Unicode scalar value--four uppercase hexadecimal digits +//# + +using System.Text; +using System.IO; +using System.Collections.Generic; +namespace Typography.OpenFont +{ + static class AdobeGlyphList + { + + static Dictionary s_glyphNameToUnicodeScalarValueDic = new Dictionary(); + static Dictionary s_unicodeScalarValueToGlyphNameDic = new Dictionary(); + static bool s_init = false; + public static string GetGlyphNameByUnicodeValue(int unicodeValue) + { + if (!s_init) + { + InitData(); + } + // + s_unicodeScalarValueToGlyphNameDic.TryGetValue(unicodeValue, out string glyphName); + return glyphName; + } + public static int GetUnicodeValueByGlyphName(string glyphName) + { + if (!s_init) + { + InitData(); + } + // + s_glyphNameToUnicodeScalarValueDic.TryGetValue(glyphName, out int unicodeValue); + return unicodeValue; + } + static void InitData() + { + using (StringReader strReader = new StringReader(glyphListTxt)) + { + string line = strReader.ReadLine(); + while (line != null) + { + //parse each line + + line = line.Trim(); + if (line.StartsWith("#")) + { + //line comment + line = strReader.ReadLine(); + continue; + } + + string[] kp = line.Split(';'); + if (kp.Length == 2) + { + // + string glyphName = kp[0].Trim(); + string[] unicodeParts = kp[1].Trim().Split(' '); + int partCount = unicodeParts.Length; + int unicodeValue = 0; + switch (partCount) + { + case 0: + default: throw new System.Exception("??"); + case 1: + unicodeValue = + int.Parse(unicodeParts[0], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); + break; + case 2: + unicodeValue = + int.Parse(unicodeParts[0], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 8 | + int.Parse(unicodeParts[1], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); + break; + case 3: + unicodeValue = + int.Parse(unicodeParts[0], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 16 | + int.Parse(unicodeParts[1], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 8 | + int.Parse(unicodeParts[2], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); + break; + case 4: + unicodeValue = + int.Parse(unicodeParts[0], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 24 | + int.Parse(unicodeParts[1], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 16 | + int.Parse(unicodeParts[2], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture) << 8 | + int.Parse(unicodeParts[3], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); + break; + } + + + if (!s_unicodeScalarValueToGlyphNameDic.ContainsKey(unicodeValue)) + { + s_unicodeScalarValueToGlyphNameDic.Add(unicodeValue, glyphName); + } + else + { + + //one unicode may has more than 1 name + //eg. .. + //Cdot; 010A + //Cdotaccent; 010A + } + // + if (!s_glyphNameToUnicodeScalarValueDic.ContainsKey(glyphName)) + { + s_glyphNameToUnicodeScalarValueDic.Add(glyphName, unicodeValue); + } + else + { + //TODO: review here + throw new System.Exception("duplicate?"); + } + + } + //--------------------------- + line = strReader.ReadLine(); + } + + } + s_init = true; + } + const string glyphListTxt = @" +A;0041 +AE;00C6 +AEacute;01FC +AEmacron;01E2 +AEsmall;F7E6 +Aacute;00C1 +Aacutesmall;F7E1 +Abreve;0102 +Abreveacute;1EAE +Abrevecyrillic;04D0 +Abrevedotbelow;1EB6 +Abrevegrave;1EB0 +Abrevehookabove;1EB2 +Abrevetilde;1EB4 +Acaron;01CD +Acircle;24B6 +Acircumflex;00C2 +Acircumflexacute;1EA4 +Acircumflexdotbelow;1EAC +Acircumflexgrave;1EA6 +Acircumflexhookabove;1EA8 +Acircumflexsmall;F7E2 +Acircumflextilde;1EAA +Acute;F6C9 +Acutesmall;F7B4 +Acyrillic;0410 +Adblgrave;0200 +Adieresis;00C4 +Adieresiscyrillic;04D2 +Adieresismacron;01DE +Adieresissmall;F7E4 +Adotbelow;1EA0 +Adotmacron;01E0 +Agrave;00C0 +Agravesmall;F7E0 +Ahookabove;1EA2 +Aiecyrillic;04D4 +Ainvertedbreve;0202 +Alpha;0391 +Alphatonos;0386 +Amacron;0100 +Amonospace;FF21 +Aogonek;0104 +Aring;00C5 +Aringacute;01FA +Aringbelow;1E00 +Aringsmall;F7E5 +Asmall;F761 +Atilde;00C3 +Atildesmall;F7E3 +Aybarmenian;0531 +B;0042 +Bcircle;24B7 +Bdotaccent;1E02 +Bdotbelow;1E04 +Becyrillic;0411 +Benarmenian;0532 +Beta;0392 +Bhook;0181 +Blinebelow;1E06 +Bmonospace;FF22 +Brevesmall;F6F4 +Bsmall;F762 +Btopbar;0182 +C;0043 +Caarmenian;053E +Cacute;0106 +Caron;F6CA +Caronsmall;F6F5 +Ccaron;010C +Ccedilla;00C7 +Ccedillaacute;1E08 +Ccedillasmall;F7E7 +Ccircle;24B8 +Ccircumflex;0108 +Cdot;010A +Cdotaccent;010A +Cedillasmall;F7B8 +Chaarmenian;0549 +Cheabkhasiancyrillic;04BC +Checyrillic;0427 +Chedescenderabkhasiancyrillic;04BE +Chedescendercyrillic;04B6 +Chedieresiscyrillic;04F4 +Cheharmenian;0543 +Chekhakassiancyrillic;04CB +Cheverticalstrokecyrillic;04B8 +Chi;03A7 +Chook;0187 +Circumflexsmall;F6F6 +Cmonospace;FF23 +Coarmenian;0551 +Csmall;F763 +D;0044 +DZ;01F1 +DZcaron;01C4 +Daarmenian;0534 +Dafrican;0189 +Dcaron;010E +Dcedilla;1E10 +Dcircle;24B9 +Dcircumflexbelow;1E12 +Dcroat;0110 +Ddotaccent;1E0A +Ddotbelow;1E0C +Decyrillic;0414 +Deicoptic;03EE +Delta;2206 +Deltagreek;0394 +Dhook;018A +Dieresis;F6CB +DieresisAcute;F6CC +DieresisGrave;F6CD +Dieresissmall;F7A8 +Digammagreek;03DC +Djecyrillic;0402 +Dlinebelow;1E0E +Dmonospace;FF24 +Dotaccentsmall;F6F7 +Dslash;0110 +Dsmall;F764 +Dtopbar;018B +Dz;01F2 +Dzcaron;01C5 +Dzeabkhasiancyrillic;04E0 +Dzecyrillic;0405 +Dzhecyrillic;040F +E;0045 +Eacute;00C9 +Eacutesmall;F7E9 +Ebreve;0114 +Ecaron;011A +Ecedillabreve;1E1C +Echarmenian;0535 +Ecircle;24BA +Ecircumflex;00CA +Ecircumflexacute;1EBE +Ecircumflexbelow;1E18 +Ecircumflexdotbelow;1EC6 +Ecircumflexgrave;1EC0 +Ecircumflexhookabove;1EC2 +Ecircumflexsmall;F7EA +Ecircumflextilde;1EC4 +Ecyrillic;0404 +Edblgrave;0204 +Edieresis;00CB +Edieresissmall;F7EB +Edot;0116 +Edotaccent;0116 +Edotbelow;1EB8 +Efcyrillic;0424 +Egrave;00C8 +Egravesmall;F7E8 +Eharmenian;0537 +Ehookabove;1EBA +Eightroman;2167 +Einvertedbreve;0206 +Eiotifiedcyrillic;0464 +Elcyrillic;041B +Elevenroman;216A +Emacron;0112 +Emacronacute;1E16 +Emacrongrave;1E14 +Emcyrillic;041C +Emonospace;FF25 +Encyrillic;041D +Endescendercyrillic;04A2 +Eng;014A +Enghecyrillic;04A4 +Enhookcyrillic;04C7 +Eogonek;0118 +Eopen;0190 +Epsilon;0395 +Epsilontonos;0388 +Ercyrillic;0420 +Ereversed;018E +Ereversedcyrillic;042D +Escyrillic;0421 +Esdescendercyrillic;04AA +Esh;01A9 +Esmall;F765 +Eta;0397 +Etarmenian;0538 +Etatonos;0389 +Eth;00D0 +Ethsmall;F7F0 +Etilde;1EBC +Etildebelow;1E1A +Euro;20AC +Ezh;01B7 +Ezhcaron;01EE +Ezhreversed;01B8 +F;0046 +Fcircle;24BB +Fdotaccent;1E1E +Feharmenian;0556 +Feicoptic;03E4 +Fhook;0191 +Fitacyrillic;0472 +Fiveroman;2164 +Fmonospace;FF26 +Fourroman;2163 +Fsmall;F766 +G;0047 +GBsquare;3387 +Gacute;01F4 +Gamma;0393 +Gammaafrican;0194 +Gangiacoptic;03EA +Gbreve;011E +Gcaron;01E6 +Gcedilla;0122 +Gcircle;24BC +Gcircumflex;011C +Gcommaaccent;0122 +Gdot;0120 +Gdotaccent;0120 +Gecyrillic;0413 +Ghadarmenian;0542 +Ghemiddlehookcyrillic;0494 +Ghestrokecyrillic;0492 +Gheupturncyrillic;0490 +Ghook;0193 +Gimarmenian;0533 +Gjecyrillic;0403 +Gmacron;1E20 +Gmonospace;FF27 +Grave;F6CE +Gravesmall;F760 +Gsmall;F767 +Gsmallhook;029B +Gstroke;01E4 +H;0048 +H18533;25CF +H18543;25AA +H18551;25AB +H22073;25A1 +HPsquare;33CB +Haabkhasiancyrillic;04A8 +Hadescendercyrillic;04B2 +Hardsigncyrillic;042A +Hbar;0126 +Hbrevebelow;1E2A +Hcedilla;1E28 +Hcircle;24BD +Hcircumflex;0124 +Hdieresis;1E26 +Hdotaccent;1E22 +Hdotbelow;1E24 +Hmonospace;FF28 +Hoarmenian;0540 +Horicoptic;03E8 +Hsmall;F768 +Hungarumlaut;F6CF +Hungarumlautsmall;F6F8 +Hzsquare;3390 +I;0049 +IAcyrillic;042F +IJ;0132 +IUcyrillic;042E +Iacute;00CD +Iacutesmall;F7ED +Ibreve;012C +Icaron;01CF +Icircle;24BE +Icircumflex;00CE +Icircumflexsmall;F7EE +Icyrillic;0406 +Idblgrave;0208 +Idieresis;00CF +Idieresisacute;1E2E +Idieresiscyrillic;04E4 +Idieresissmall;F7EF +Idot;0130 +Idotaccent;0130 +Idotbelow;1ECA +Iebrevecyrillic;04D6 +Iecyrillic;0415 +Ifraktur;2111 +Igrave;00CC +Igravesmall;F7EC +Ihookabove;1EC8 +Iicyrillic;0418 +Iinvertedbreve;020A +Iishortcyrillic;0419 +Imacron;012A +Imacroncyrillic;04E2 +Imonospace;FF29 +Iniarmenian;053B +Iocyrillic;0401 +Iogonek;012E +Iota;0399 +Iotaafrican;0196 +Iotadieresis;03AA +Iotatonos;038A +Ismall;F769 +Istroke;0197 +Itilde;0128 +Itildebelow;1E2C +Izhitsacyrillic;0474 +Izhitsadblgravecyrillic;0476 +J;004A +Jaarmenian;0541 +Jcircle;24BF +Jcircumflex;0134 +Jecyrillic;0408 +Jheharmenian;054B +Jmonospace;FF2A +Jsmall;F76A +K;004B +KBsquare;3385 +KKsquare;33CD +Kabashkircyrillic;04A0 +Kacute;1E30 +Kacyrillic;041A +Kadescendercyrillic;049A +Kahookcyrillic;04C3 +Kappa;039A +Kastrokecyrillic;049E +Kaverticalstrokecyrillic;049C +Kcaron;01E8 +Kcedilla;0136 +Kcircle;24C0 +Kcommaaccent;0136 +Kdotbelow;1E32 +Keharmenian;0554 +Kenarmenian;053F +Khacyrillic;0425 +Kheicoptic;03E6 +Khook;0198 +Kjecyrillic;040C +Klinebelow;1E34 +Kmonospace;FF2B +Koppacyrillic;0480 +Koppagreek;03DE +Ksicyrillic;046E +Ksmall;F76B +L;004C +LJ;01C7 +LL;F6BF +Lacute;0139 +Lambda;039B +Lcaron;013D +Lcedilla;013B +Lcircle;24C1 +Lcircumflexbelow;1E3C +Lcommaaccent;013B +Ldot;013F +Ldotaccent;013F +Ldotbelow;1E36 +Ldotbelowmacron;1E38 +Liwnarmenian;053C +Lj;01C8 +Ljecyrillic;0409 +Llinebelow;1E3A +Lmonospace;FF2C +Lslash;0141 +Lslashsmall;F6F9 +Lsmall;F76C +M;004D +MBsquare;3386 +Macron;F6D0 +Macronsmall;F7AF +Macute;1E3E +Mcircle;24C2 +Mdotaccent;1E40 +Mdotbelow;1E42 +Menarmenian;0544 +Mmonospace;FF2D +Msmall;F76D +Mturned;019C +Mu;039C +N;004E +NJ;01CA +Nacute;0143 +Ncaron;0147 +Ncedilla;0145 +Ncircle;24C3 +Ncircumflexbelow;1E4A +Ncommaaccent;0145 +Ndotaccent;1E44 +Ndotbelow;1E46 +Nhookleft;019D +Nineroman;2168 +Nj;01CB +Njecyrillic;040A +Nlinebelow;1E48 +Nmonospace;FF2E +Nowarmenian;0546 +Nsmall;F76E +Ntilde;00D1 +Ntildesmall;F7F1 +Nu;039D +O;004F +OE;0152 +OEsmall;F6FA +Oacute;00D3 +Oacutesmall;F7F3 +Obarredcyrillic;04E8 +Obarreddieresiscyrillic;04EA +Obreve;014E +Ocaron;01D1 +Ocenteredtilde;019F +Ocircle;24C4 +Ocircumflex;00D4 +Ocircumflexacute;1ED0 +Ocircumflexdotbelow;1ED8 +Ocircumflexgrave;1ED2 +Ocircumflexhookabove;1ED4 +Ocircumflexsmall;F7F4 +Ocircumflextilde;1ED6 +Ocyrillic;041E +Odblacute;0150 +Odblgrave;020C +Odieresis;00D6 +Odieresiscyrillic;04E6 +Odieresissmall;F7F6 +Odotbelow;1ECC +Ogoneksmall;F6FB +Ograve;00D2 +Ogravesmall;F7F2 +Oharmenian;0555 +Ohm;2126 +Ohookabove;1ECE +Ohorn;01A0 +Ohornacute;1EDA +Ohorndotbelow;1EE2 +Ohorngrave;1EDC +Ohornhookabove;1EDE +Ohorntilde;1EE0 +Ohungarumlaut;0150 +Oi;01A2 +Oinvertedbreve;020E +Omacron;014C +Omacronacute;1E52 +Omacrongrave;1E50 +Omega;2126 +Omegacyrillic;0460 +Omegagreek;03A9 +Omegaroundcyrillic;047A +Omegatitlocyrillic;047C +Omegatonos;038F +Omicron;039F +Omicrontonos;038C +Omonospace;FF2F +Oneroman;2160 +Oogonek;01EA +Oogonekmacron;01EC +Oopen;0186 +Oslash;00D8 +Oslashacute;01FE +Oslashsmall;F7F8 +Osmall;F76F +Ostrokeacute;01FE +Otcyrillic;047E +Otilde;00D5 +Otildeacute;1E4C +Otildedieresis;1E4E +Otildesmall;F7F5 +P;0050 +Pacute;1E54 +Pcircle;24C5 +Pdotaccent;1E56 +Pecyrillic;041F +Peharmenian;054A +Pemiddlehookcyrillic;04A6 +Phi;03A6 +Phook;01A4 +Pi;03A0 +Piwrarmenian;0553 +Pmonospace;FF30 +Psi;03A8 +Psicyrillic;0470 +Psmall;F770 +Q;0051 +Qcircle;24C6 +Qmonospace;FF31 +Qsmall;F771 +R;0052 +Raarmenian;054C +Racute;0154 +Rcaron;0158 +Rcedilla;0156 +Rcircle;24C7 +Rcommaaccent;0156 +Rdblgrave;0210 +Rdotaccent;1E58 +Rdotbelow;1E5A +Rdotbelowmacron;1E5C +Reharmenian;0550 +Rfraktur;211C +Rho;03A1 +Ringsmall;F6FC +Rinvertedbreve;0212 +Rlinebelow;1E5E +Rmonospace;FF32 +Rsmall;F772 +Rsmallinverted;0281 +Rsmallinvertedsuperior;02B6 +S;0053 +SF010000;250C +SF020000;2514 +SF030000;2510 +SF040000;2518 +SF050000;253C +SF060000;252C +SF070000;2534 +SF080000;251C +SF090000;2524 +SF100000;2500 +SF110000;2502 +SF190000;2561 +SF200000;2562 +SF210000;2556 +SF220000;2555 +SF230000;2563 +SF240000;2551 +SF250000;2557 +SF260000;255D +SF270000;255C +SF280000;255B +SF360000;255E +SF370000;255F +SF380000;255A +SF390000;2554 +SF400000;2569 +SF410000;2566 +SF420000;2560 +SF430000;2550 +SF440000;256C +SF450000;2567 +SF460000;2568 +SF470000;2564 +SF480000;2565 +SF490000;2559 +SF500000;2558 +SF510000;2552 +SF520000;2553 +SF530000;256B +SF540000;256A +Sacute;015A +Sacutedotaccent;1E64 +Sampigreek;03E0 +Scaron;0160 +Scarondotaccent;1E66 +Scaronsmall;F6FD +Scedilla;015E +Schwa;018F +Schwacyrillic;04D8 +Schwadieresiscyrillic;04DA +Scircle;24C8 +Scircumflex;015C +Scommaaccent;0218 +Sdotaccent;1E60 +Sdotbelow;1E62 +Sdotbelowdotaccent;1E68 +Seharmenian;054D +Sevenroman;2166 +Shaarmenian;0547 +Shacyrillic;0428 +Shchacyrillic;0429 +Sheicoptic;03E2 +Shhacyrillic;04BA +Shimacoptic;03EC +Sigma;03A3 +Sixroman;2165 +Smonospace;FF33 +Softsigncyrillic;042C +Ssmall;F773 +Stigmagreek;03DA +T;0054 +Tau;03A4 +Tbar;0166 +Tcaron;0164 +Tcedilla;0162 +Tcircle;24C9 +Tcircumflexbelow;1E70 +Tcommaaccent;0162 +Tdotaccent;1E6A +Tdotbelow;1E6C +Tecyrillic;0422 +Tedescendercyrillic;04AC +Tenroman;2169 +Tetsecyrillic;04B4 +Theta;0398 +Thook;01AC +Thorn;00DE +Thornsmall;F7FE +Threeroman;2162 +Tildesmall;F6FE +Tiwnarmenian;054F +Tlinebelow;1E6E +Tmonospace;FF34 +Toarmenian;0539 +Tonefive;01BC +Tonesix;0184 +Tonetwo;01A7 +Tretroflexhook;01AE +Tsecyrillic;0426 +Tshecyrillic;040B +Tsmall;F774 +Twelveroman;216B +Tworoman;2161 +U;0055 +Uacute;00DA +Uacutesmall;F7FA +Ubreve;016C +Ucaron;01D3 +Ucircle;24CA +Ucircumflex;00DB +Ucircumflexbelow;1E76 +Ucircumflexsmall;F7FB +Ucyrillic;0423 +Udblacute;0170 +Udblgrave;0214 +Udieresis;00DC +Udieresisacute;01D7 +Udieresisbelow;1E72 +Udieresiscaron;01D9 +Udieresiscyrillic;04F0 +Udieresisgrave;01DB +Udieresismacron;01D5 +Udieresissmall;F7FC +Udotbelow;1EE4 +Ugrave;00D9 +Ugravesmall;F7F9 +Uhookabove;1EE6 +Uhorn;01AF +Uhornacute;1EE8 +Uhorndotbelow;1EF0 +Uhorngrave;1EEA +Uhornhookabove;1EEC +Uhorntilde;1EEE +Uhungarumlaut;0170 +Uhungarumlautcyrillic;04F2 +Uinvertedbreve;0216 +Ukcyrillic;0478 +Umacron;016A +Umacroncyrillic;04EE +Umacrondieresis;1E7A +Umonospace;FF35 +Uogonek;0172 +Upsilon;03A5 +Upsilon1;03D2 +Upsilonacutehooksymbolgreek;03D3 +Upsilonafrican;01B1 +Upsilondieresis;03AB +Upsilondieresishooksymbolgreek;03D4 +Upsilonhooksymbol;03D2 +Upsilontonos;038E +Uring;016E +Ushortcyrillic;040E +Usmall;F775 +Ustraightcyrillic;04AE +Ustraightstrokecyrillic;04B0 +Utilde;0168 +Utildeacute;1E78 +Utildebelow;1E74 +V;0056 +Vcircle;24CB +Vdotbelow;1E7E +Vecyrillic;0412 +Vewarmenian;054E +Vhook;01B2 +Vmonospace;FF36 +Voarmenian;0548 +Vsmall;F776 +Vtilde;1E7C +W;0057 +Wacute;1E82 +Wcircle;24CC +Wcircumflex;0174 +Wdieresis;1E84 +Wdotaccent;1E86 +Wdotbelow;1E88 +Wgrave;1E80 +Wmonospace;FF37 +Wsmall;F777 +X;0058 +Xcircle;24CD +Xdieresis;1E8C +Xdotaccent;1E8A +Xeharmenian;053D +Xi;039E +Xmonospace;FF38 +Xsmall;F778 +Y;0059 +Yacute;00DD +Yacutesmall;F7FD +Yatcyrillic;0462 +Ycircle;24CE +Ycircumflex;0176 +Ydieresis;0178 +Ydieresissmall;F7FF +Ydotaccent;1E8E +Ydotbelow;1EF4 +Yericyrillic;042B +Yerudieresiscyrillic;04F8 +Ygrave;1EF2 +Yhook;01B3 +Yhookabove;1EF6 +Yiarmenian;0545 +Yicyrillic;0407 +Yiwnarmenian;0552 +Ymonospace;FF39 +Ysmall;F779 +Ytilde;1EF8 +Yusbigcyrillic;046A +Yusbigiotifiedcyrillic;046C +Yuslittlecyrillic;0466 +Yuslittleiotifiedcyrillic;0468 +Z;005A +Zaarmenian;0536 +Zacute;0179 +Zcaron;017D +Zcaronsmall;F6FF +Zcircle;24CF +Zcircumflex;1E90 +Zdot;017B +Zdotaccent;017B +Zdotbelow;1E92 +Zecyrillic;0417 +Zedescendercyrillic;0498 +Zedieresiscyrillic;04DE +Zeta;0396 +Zhearmenian;053A +Zhebrevecyrillic;04C1 +Zhecyrillic;0416 +Zhedescendercyrillic;0496 +Zhedieresiscyrillic;04DC +Zlinebelow;1E94 +Zmonospace;FF3A +Zsmall;F77A +Zstroke;01B5 +a;0061 +aabengali;0986 +aacute;00E1 +aadeva;0906 +aagujarati;0A86 +aagurmukhi;0A06 +aamatragurmukhi;0A3E +aarusquare;3303 +aavowelsignbengali;09BE +aavowelsigndeva;093E +aavowelsigngujarati;0ABE +abbreviationmarkarmenian;055F +abbreviationsigndeva;0970 +abengali;0985 +abopomofo;311A +abreve;0103 +abreveacute;1EAF +abrevecyrillic;04D1 +abrevedotbelow;1EB7 +abrevegrave;1EB1 +abrevehookabove;1EB3 +abrevetilde;1EB5 +acaron;01CE +acircle;24D0 +acircumflex;00E2 +acircumflexacute;1EA5 +acircumflexdotbelow;1EAD +acircumflexgrave;1EA7 +acircumflexhookabove;1EA9 +acircumflextilde;1EAB +acute;00B4 +acutebelowcmb;0317 +acutecmb;0301 +acutecomb;0301 +acutedeva;0954 +acutelowmod;02CF +acutetonecmb;0341 +acyrillic;0430 +adblgrave;0201 +addakgurmukhi;0A71 +adeva;0905 +adieresis;00E4 +adieresiscyrillic;04D3 +adieresismacron;01DF +adotbelow;1EA1 +adotmacron;01E1 +ae;00E6 +aeacute;01FD +aekorean;3150 +aemacron;01E3 +afii00208;2015 +afii08941;20A4 +afii10017;0410 +afii10018;0411 +afii10019;0412 +afii10020;0413 +afii10021;0414 +afii10022;0415 +afii10023;0401 +afii10024;0416 +afii10025;0417 +afii10026;0418 +afii10027;0419 +afii10028;041A +afii10029;041B +afii10030;041C +afii10031;041D +afii10032;041E +afii10033;041F +afii10034;0420 +afii10035;0421 +afii10036;0422 +afii10037;0423 +afii10038;0424 +afii10039;0425 +afii10040;0426 +afii10041;0427 +afii10042;0428 +afii10043;0429 +afii10044;042A +afii10045;042B +afii10046;042C +afii10047;042D +afii10048;042E +afii10049;042F +afii10050;0490 +afii10051;0402 +afii10052;0403 +afii10053;0404 +afii10054;0405 +afii10055;0406 +afii10056;0407 +afii10057;0408 +afii10058;0409 +afii10059;040A +afii10060;040B +afii10061;040C +afii10062;040E +afii10063;F6C4 +afii10064;F6C5 +afii10065;0430 +afii10066;0431 +afii10067;0432 +afii10068;0433 +afii10069;0434 +afii10070;0435 +afii10071;0451 +afii10072;0436 +afii10073;0437 +afii10074;0438 +afii10075;0439 +afii10076;043A +afii10077;043B +afii10078;043C +afii10079;043D +afii10080;043E +afii10081;043F +afii10082;0440 +afii10083;0441 +afii10084;0442 +afii10085;0443 +afii10086;0444 +afii10087;0445 +afii10088;0446 +afii10089;0447 +afii10090;0448 +afii10091;0449 +afii10092;044A +afii10093;044B +afii10094;044C +afii10095;044D +afii10096;044E +afii10097;044F +afii10098;0491 +afii10099;0452 +afii10100;0453 +afii10101;0454 +afii10102;0455 +afii10103;0456 +afii10104;0457 +afii10105;0458 +afii10106;0459 +afii10107;045A +afii10108;045B +afii10109;045C +afii10110;045E +afii10145;040F +afii10146;0462 +afii10147;0472 +afii10148;0474 +afii10192;F6C6 +afii10193;045F +afii10194;0463 +afii10195;0473 +afii10196;0475 +afii10831;F6C7 +afii10832;F6C8 +afii10846;04D9 +afii299;200E +afii300;200F +afii301;200D +afii57381;066A +afii57388;060C +afii57392;0660 +afii57393;0661 +afii57394;0662 +afii57395;0663 +afii57396;0664 +afii57397;0665 +afii57398;0666 +afii57399;0667 +afii57400;0668 +afii57401;0669 +afii57403;061B +afii57407;061F +afii57409;0621 +afii57410;0622 +afii57411;0623 +afii57412;0624 +afii57413;0625 +afii57414;0626 +afii57415;0627 +afii57416;0628 +afii57417;0629 +afii57418;062A +afii57419;062B +afii57420;062C +afii57421;062D +afii57422;062E +afii57423;062F +afii57424;0630 +afii57425;0631 +afii57426;0632 +afii57427;0633 +afii57428;0634 +afii57429;0635 +afii57430;0636 +afii57431;0637 +afii57432;0638 +afii57433;0639 +afii57434;063A +afii57440;0640 +afii57441;0641 +afii57442;0642 +afii57443;0643 +afii57444;0644 +afii57445;0645 +afii57446;0646 +afii57448;0648 +afii57449;0649 +afii57450;064A +afii57451;064B +afii57452;064C +afii57453;064D +afii57454;064E +afii57455;064F +afii57456;0650 +afii57457;0651 +afii57458;0652 +afii57470;0647 +afii57505;06A4 +afii57506;067E +afii57507;0686 +afii57508;0698 +afii57509;06AF +afii57511;0679 +afii57512;0688 +afii57513;0691 +afii57514;06BA +afii57519;06D2 +afii57534;06D5 +afii57636;20AA +afii57645;05BE +afii57658;05C3 +afii57664;05D0 +afii57665;05D1 +afii57666;05D2 +afii57667;05D3 +afii57668;05D4 +afii57669;05D5 +afii57670;05D6 +afii57671;05D7 +afii57672;05D8 +afii57673;05D9 +afii57674;05DA +afii57675;05DB +afii57676;05DC +afii57677;05DD +afii57678;05DE +afii57679;05DF +afii57680;05E0 +afii57681;05E1 +afii57682;05E2 +afii57683;05E3 +afii57684;05E4 +afii57685;05E5 +afii57686;05E6 +afii57687;05E7 +afii57688;05E8 +afii57689;05E9 +afii57690;05EA +afii57694;FB2A +afii57695;FB2B +afii57700;FB4B +afii57705;FB1F +afii57716;05F0 +afii57717;05F1 +afii57718;05F2 +afii57723;FB35 +afii57793;05B4 +afii57794;05B5 +afii57795;05B6 +afii57796;05BB +afii57797;05B8 +afii57798;05B7 +afii57799;05B0 +afii57800;05B2 +afii57801;05B1 +afii57802;05B3 +afii57803;05C2 +afii57804;05C1 +afii57806;05B9 +afii57807;05BC +afii57839;05BD +afii57841;05BF +afii57842;05C0 +afii57929;02BC +afii61248;2105 +afii61289;2113 +afii61352;2116 +afii61573;202C +afii61574;202D +afii61575;202E +afii61664;200C +afii63167;066D +afii64937;02BD +agrave;00E0 +agujarati;0A85 +agurmukhi;0A05 +ahiragana;3042 +ahookabove;1EA3 +aibengali;0990 +aibopomofo;311E +aideva;0910 +aiecyrillic;04D5 +aigujarati;0A90 +aigurmukhi;0A10 +aimatragurmukhi;0A48 +ainarabic;0639 +ainfinalarabic;FECA +aininitialarabic;FECB +ainmedialarabic;FECC +ainvertedbreve;0203 +aivowelsignbengali;09C8 +aivowelsigndeva;0948 +aivowelsigngujarati;0AC8 +akatakana;30A2 +akatakanahalfwidth;FF71 +akorean;314F +alef;05D0 +alefarabic;0627 +alefdageshhebrew;FB30 +aleffinalarabic;FE8E +alefhamzaabovearabic;0623 +alefhamzaabovefinalarabic;FE84 +alefhamzabelowarabic;0625 +alefhamzabelowfinalarabic;FE88 +alefhebrew;05D0 +aleflamedhebrew;FB4F +alefmaddaabovearabic;0622 +alefmaddaabovefinalarabic;FE82 +alefmaksuraarabic;0649 +alefmaksurafinalarabic;FEF0 +alefmaksurainitialarabic;FEF3 +alefmaksuramedialarabic;FEF4 +alefpatahhebrew;FB2E +alefqamatshebrew;FB2F +aleph;2135 +allequal;224C +alpha;03B1 +alphatonos;03AC +amacron;0101 +amonospace;FF41 +ampersand;0026 +ampersandmonospace;FF06 +ampersandsmall;F726 +amsquare;33C2 +anbopomofo;3122 +angbopomofo;3124 +angkhankhuthai;0E5A +angle;2220 +anglebracketleft;3008 +anglebracketleftvertical;FE3F +anglebracketright;3009 +anglebracketrightvertical;FE40 +angleleft;2329 +angleright;232A +angstrom;212B +anoteleia;0387 +anudattadeva;0952 +anusvarabengali;0982 +anusvaradeva;0902 +anusvaragujarati;0A82 +aogonek;0105 +apaatosquare;3300 +aparen;249C +apostrophearmenian;055A +apostrophemod;02BC +apple;F8FF +approaches;2250 +approxequal;2248 +approxequalorimage;2252 +approximatelyequal;2245 +araeaekorean;318E +araeakorean;318D +arc;2312 +arighthalfring;1E9A +aring;00E5 +aringacute;01FB +aringbelow;1E01 +arrowboth;2194 +arrowdashdown;21E3 +arrowdashleft;21E0 +arrowdashright;21E2 +arrowdashup;21E1 +arrowdblboth;21D4 +arrowdbldown;21D3 +arrowdblleft;21D0 +arrowdblright;21D2 +arrowdblup;21D1 +arrowdown;2193 +arrowdownleft;2199 +arrowdownright;2198 +arrowdownwhite;21E9 +arrowheaddownmod;02C5 +arrowheadleftmod;02C2 +arrowheadrightmod;02C3 +arrowheadupmod;02C4 +arrowhorizex;F8E7 +arrowleft;2190 +arrowleftdbl;21D0 +arrowleftdblstroke;21CD +arrowleftoverright;21C6 +arrowleftwhite;21E6 +arrowright;2192 +arrowrightdblstroke;21CF +arrowrightheavy;279E +arrowrightoverleft;21C4 +arrowrightwhite;21E8 +arrowtableft;21E4 +arrowtabright;21E5 +arrowup;2191 +arrowupdn;2195 +arrowupdnbse;21A8 +arrowupdownbase;21A8 +arrowupleft;2196 +arrowupleftofdown;21C5 +arrowupright;2197 +arrowupwhite;21E7 +arrowvertex;F8E6 +asciicircum;005E +asciicircummonospace;FF3E +asciitilde;007E +asciitildemonospace;FF5E +ascript;0251 +ascriptturned;0252 +asmallhiragana;3041 +asmallkatakana;30A1 +asmallkatakanahalfwidth;FF67 +asterisk;002A +asteriskaltonearabic;066D +asteriskarabic;066D +asteriskmath;2217 +asteriskmonospace;FF0A +asterisksmall;FE61 +asterism;2042 +asuperior;F6E9 +asymptoticallyequal;2243 +at;0040 +atilde;00E3 +atmonospace;FF20 +atsmall;FE6B +aturned;0250 +aubengali;0994 +aubopomofo;3120 +audeva;0914 +augujarati;0A94 +augurmukhi;0A14 +aulengthmarkbengali;09D7 +aumatragurmukhi;0A4C +auvowelsignbengali;09CC +auvowelsigndeva;094C +auvowelsigngujarati;0ACC +avagrahadeva;093D +aybarmenian;0561 +ayin;05E2 +ayinaltonehebrew;FB20 +ayinhebrew;05E2 +b;0062 +babengali;09AC +backslash;005C +backslashmonospace;FF3C +badeva;092C +bagujarati;0AAC +bagurmukhi;0A2C +bahiragana;3070 +bahtthai;0E3F +bakatakana;30D0 +bar;007C +barmonospace;FF5C +bbopomofo;3105 +bcircle;24D1 +bdotaccent;1E03 +bdotbelow;1E05 +beamedsixteenthnotes;266C +because;2235 +becyrillic;0431 +beharabic;0628 +behfinalarabic;FE90 +behinitialarabic;FE91 +behiragana;3079 +behmedialarabic;FE92 +behmeeminitialarabic;FC9F +behmeemisolatedarabic;FC08 +behnoonfinalarabic;FC6D +bekatakana;30D9 +benarmenian;0562 +bet;05D1 +beta;03B2 +betasymbolgreek;03D0 +betdagesh;FB31 +betdageshhebrew;FB31 +bethebrew;05D1 +betrafehebrew;FB4C +bhabengali;09AD +bhadeva;092D +bhagujarati;0AAD +bhagurmukhi;0A2D +bhook;0253 +bihiragana;3073 +bikatakana;30D3 +bilabialclick;0298 +bindigurmukhi;0A02 +birusquare;3331 +blackcircle;25CF +blackdiamond;25C6 +blackdownpointingtriangle;25BC +blackleftpointingpointer;25C4 +blackleftpointingtriangle;25C0 +blacklenticularbracketleft;3010 +blacklenticularbracketleftvertical;FE3B +blacklenticularbracketright;3011 +blacklenticularbracketrightvertical;FE3C +blacklowerlefttriangle;25E3 +blacklowerrighttriangle;25E2 +blackrectangle;25AC +blackrightpointingpointer;25BA +blackrightpointingtriangle;25B6 +blacksmallsquare;25AA +blacksmilingface;263B +blacksquare;25A0 +blackstar;2605 +blackupperlefttriangle;25E4 +blackupperrighttriangle;25E5 +blackuppointingsmalltriangle;25B4 +blackuppointingtriangle;25B2 +blank;2423 +blinebelow;1E07 +block;2588 +bmonospace;FF42 +bobaimaithai;0E1A +bohiragana;307C +bokatakana;30DC +bparen;249D +bqsquare;33C3 +braceex;F8F4 +braceleft;007B +braceleftbt;F8F3 +braceleftmid;F8F2 +braceleftmonospace;FF5B +braceleftsmall;FE5B +bracelefttp;F8F1 +braceleftvertical;FE37 +braceright;007D +bracerightbt;F8FE +bracerightmid;F8FD +bracerightmonospace;FF5D +bracerightsmall;FE5C +bracerighttp;F8FC +bracerightvertical;FE38 +bracketleft;005B +bracketleftbt;F8F0 +bracketleftex;F8EF +bracketleftmonospace;FF3B +bracketlefttp;F8EE +bracketright;005D +bracketrightbt;F8FB +bracketrightex;F8FA +bracketrightmonospace;FF3D +bracketrighttp;F8F9 +breve;02D8 +brevebelowcmb;032E +brevecmb;0306 +breveinvertedbelowcmb;032F +breveinvertedcmb;0311 +breveinverteddoublecmb;0361 +bridgebelowcmb;032A +bridgeinvertedbelowcmb;033A +brokenbar;00A6 +bstroke;0180 +bsuperior;F6EA +btopbar;0183 +buhiragana;3076 +bukatakana;30D6 +bullet;2022 +bulletinverse;25D8 +bulletoperator;2219 +bullseye;25CE +c;0063 +caarmenian;056E +cabengali;099A +cacute;0107 +cadeva;091A +cagujarati;0A9A +cagurmukhi;0A1A +calsquare;3388 +candrabindubengali;0981 +candrabinducmb;0310 +candrabindudeva;0901 +candrabindugujarati;0A81 +capslock;21EA +careof;2105 +caron;02C7 +caronbelowcmb;032C +caroncmb;030C +carriagereturn;21B5 +cbopomofo;3118 +ccaron;010D +ccedilla;00E7 +ccedillaacute;1E09 +ccircle;24D2 +ccircumflex;0109 +ccurl;0255 +cdot;010B +cdotaccent;010B +cdsquare;33C5 +cedilla;00B8 +cedillacmb;0327 +cent;00A2 +centigrade;2103 +centinferior;F6DF +centmonospace;FFE0 +centoldstyle;F7A2 +centsuperior;F6E0 +chaarmenian;0579 +chabengali;099B +chadeva;091B +chagujarati;0A9B +chagurmukhi;0A1B +chbopomofo;3114 +cheabkhasiancyrillic;04BD +checkmark;2713 +checyrillic;0447 +chedescenderabkhasiancyrillic;04BF +chedescendercyrillic;04B7 +chedieresiscyrillic;04F5 +cheharmenian;0573 +chekhakassiancyrillic;04CC +cheverticalstrokecyrillic;04B9 +chi;03C7 +chieuchacirclekorean;3277 +chieuchaparenkorean;3217 +chieuchcirclekorean;3269 +chieuchkorean;314A +chieuchparenkorean;3209 +chochangthai;0E0A +chochanthai;0E08 +chochingthai;0E09 +chochoethai;0E0C +chook;0188 +cieucacirclekorean;3276 +cieucaparenkorean;3216 +cieuccirclekorean;3268 +cieuckorean;3148 +cieucparenkorean;3208 +cieucuparenkorean;321C +circle;25CB +circlemultiply;2297 +circleot;2299 +circleplus;2295 +circlepostalmark;3036 +circlewithlefthalfblack;25D0 +circlewithrighthalfblack;25D1 +circumflex;02C6 +circumflexbelowcmb;032D +circumflexcmb;0302 +clear;2327 +clickalveolar;01C2 +clickdental;01C0 +clicklateral;01C1 +clickretroflex;01C3 +club;2663 +clubsuitblack;2663 +clubsuitwhite;2667 +cmcubedsquare;33A4 +cmonospace;FF43 +cmsquaredsquare;33A0 +coarmenian;0581 +colon;003A +colonmonetary;20A1 +colonmonospace;FF1A +colonsign;20A1 +colonsmall;FE55 +colontriangularhalfmod;02D1 +colontriangularmod;02D0 +comma;002C +commaabovecmb;0313 +commaaboverightcmb;0315 +commaaccent;F6C3 +commaarabic;060C +commaarmenian;055D +commainferior;F6E1 +commamonospace;FF0C +commareversedabovecmb;0314 +commareversedmod;02BD +commasmall;FE50 +commasuperior;F6E2 +commaturnedabovecmb;0312 +commaturnedmod;02BB +compass;263C +congruent;2245 +contourintegral;222E +control;2303 +controlACK;0006 +controlBEL;0007 +controlBS;0008 +controlCAN;0018 +controlCR;000D +controlDC1;0011 +controlDC2;0012 +controlDC3;0013 +controlDC4;0014 +controlDEL;007F +controlDLE;0010 +controlEM;0019 +controlENQ;0005 +controlEOT;0004 +controlESC;001B +controlETB;0017 +controlETX;0003 +controlFF;000C +controlFS;001C +controlGS;001D +controlHT;0009 +controlLF;000A +controlNAK;0015 +controlRS;001E +controlSI;000F +controlSO;000E +controlSOT;0002 +controlSTX;0001 +controlSUB;001A +controlSYN;0016 +controlUS;001F +controlVT;000B +copyright;00A9 +copyrightsans;F8E9 +copyrightserif;F6D9 +cornerbracketleft;300C +cornerbracketlefthalfwidth;FF62 +cornerbracketleftvertical;FE41 +cornerbracketright;300D +cornerbracketrighthalfwidth;FF63 +cornerbracketrightvertical;FE42 +corporationsquare;337F +cosquare;33C7 +coverkgsquare;33C6 +cparen;249E +cruzeiro;20A2 +cstretched;0297 +curlyand;22CF +curlyor;22CE +currency;00A4 +cyrBreve;F6D1 +cyrFlex;F6D2 +cyrbreve;F6D4 +cyrflex;F6D5 +d;0064 +daarmenian;0564 +dabengali;09A6 +dadarabic;0636 +dadeva;0926 +dadfinalarabic;FEBE +dadinitialarabic;FEBF +dadmedialarabic;FEC0 +dagesh;05BC +dageshhebrew;05BC +dagger;2020 +daggerdbl;2021 +dagujarati;0AA6 +dagurmukhi;0A26 +dahiragana;3060 +dakatakana;30C0 +dalarabic;062F +dalet;05D3 +daletdagesh;FB33 +daletdageshhebrew;FB33 +dalethatafpatah;05D3 05B2 +dalethatafpatahhebrew;05D3 05B2 +dalethatafsegol;05D3 05B1 +dalethatafsegolhebrew;05D3 05B1 +dalethebrew;05D3 +dalethiriq;05D3 05B4 +dalethiriqhebrew;05D3 05B4 +daletholam;05D3 05B9 +daletholamhebrew;05D3 05B9 +daletpatah;05D3 05B7 +daletpatahhebrew;05D3 05B7 +daletqamats;05D3 05B8 +daletqamatshebrew;05D3 05B8 +daletqubuts;05D3 05BB +daletqubutshebrew;05D3 05BB +daletsegol;05D3 05B6 +daletsegolhebrew;05D3 05B6 +daletsheva;05D3 05B0 +daletshevahebrew;05D3 05B0 +dalettsere;05D3 05B5 +dalettserehebrew;05D3 05B5 +dalfinalarabic;FEAA +dammaarabic;064F +dammalowarabic;064F +dammatanaltonearabic;064C +dammatanarabic;064C +danda;0964 +dargahebrew;05A7 +dargalefthebrew;05A7 +dasiapneumatacyrilliccmb;0485 +dblGrave;F6D3 +dblanglebracketleft;300A +dblanglebracketleftvertical;FE3D +dblanglebracketright;300B +dblanglebracketrightvertical;FE3E +dblarchinvertedbelowcmb;032B +dblarrowleft;21D4 +dblarrowright;21D2 +dbldanda;0965 +dblgrave;F6D6 +dblgravecmb;030F +dblintegral;222C +dbllowline;2017 +dbllowlinecmb;0333 +dbloverlinecmb;033F +dblprimemod;02BA +dblverticalbar;2016 +dblverticallineabovecmb;030E +dbopomofo;3109 +dbsquare;33C8 +dcaron;010F +dcedilla;1E11 +dcircle;24D3 +dcircumflexbelow;1E13 +dcroat;0111 +ddabengali;09A1 +ddadeva;0921 +ddagujarati;0AA1 +ddagurmukhi;0A21 +ddalarabic;0688 +ddalfinalarabic;FB89 +dddhadeva;095C +ddhabengali;09A2 +ddhadeva;0922 +ddhagujarati;0AA2 +ddhagurmukhi;0A22 +ddotaccent;1E0B +ddotbelow;1E0D +decimalseparatorarabic;066B +decimalseparatorpersian;066B +decyrillic;0434 +degree;00B0 +dehihebrew;05AD +dehiragana;3067 +deicoptic;03EF +dekatakana;30C7 +deleteleft;232B +deleteright;2326 +delta;03B4 +deltaturned;018D +denominatorminusonenumeratorbengali;09F8 +dezh;02A4 +dhabengali;09A7 +dhadeva;0927 +dhagujarati;0AA7 +dhagurmukhi;0A27 +dhook;0257 +dialytikatonos;0385 +dialytikatonoscmb;0344 +diamond;2666 +diamondsuitwhite;2662 +dieresis;00A8 +dieresisacute;F6D7 +dieresisbelowcmb;0324 +dieresiscmb;0308 +dieresisgrave;F6D8 +dieresistonos;0385 +dihiragana;3062 +dikatakana;30C2 +dittomark;3003 +divide;00F7 +divides;2223 +divisionslash;2215 +djecyrillic;0452 +dkshade;2593 +dlinebelow;1E0F +dlsquare;3397 +dmacron;0111 +dmonospace;FF44 +dnblock;2584 +dochadathai;0E0E +dodekthai;0E14 +dohiragana;3069 +dokatakana;30C9 +dollar;0024 +dollarinferior;F6E3 +dollarmonospace;FF04 +dollaroldstyle;F724 +dollarsmall;FE69 +dollarsuperior;F6E4 +dong;20AB +dorusquare;3326 +dotaccent;02D9 +dotaccentcmb;0307 +dotbelowcmb;0323 +dotbelowcomb;0323 +dotkatakana;30FB +dotlessi;0131 +dotlessj;F6BE +dotlessjstrokehook;0284 +dotmath;22C5 +dottedcircle;25CC +doubleyodpatah;FB1F +doubleyodpatahhebrew;FB1F +downtackbelowcmb;031E +downtackmod;02D5 +dparen;249F +dsuperior;F6EB +dtail;0256 +dtopbar;018C +duhiragana;3065 +dukatakana;30C5 +dz;01F3 +dzaltone;02A3 +dzcaron;01C6 +dzcurl;02A5 +dzeabkhasiancyrillic;04E1 +dzecyrillic;0455 +dzhecyrillic;045F +e;0065 +eacute;00E9 +earth;2641 +ebengali;098F +ebopomofo;311C +ebreve;0115 +ecandradeva;090D +ecandragujarati;0A8D +ecandravowelsigndeva;0945 +ecandravowelsigngujarati;0AC5 +ecaron;011B +ecedillabreve;1E1D +echarmenian;0565 +echyiwnarmenian;0587 +ecircle;24D4 +ecircumflex;00EA +ecircumflexacute;1EBF +ecircumflexbelow;1E19 +ecircumflexdotbelow;1EC7 +ecircumflexgrave;1EC1 +ecircumflexhookabove;1EC3 +ecircumflextilde;1EC5 +ecyrillic;0454 +edblgrave;0205 +edeva;090F +edieresis;00EB +edot;0117 +edotaccent;0117 +edotbelow;1EB9 +eegurmukhi;0A0F +eematragurmukhi;0A47 +efcyrillic;0444 +egrave;00E8 +egujarati;0A8F +eharmenian;0567 +ehbopomofo;311D +ehiragana;3048 +ehookabove;1EBB +eibopomofo;311F +eight;0038 +eightarabic;0668 +eightbengali;09EE +eightcircle;2467 +eightcircleinversesansserif;2791 +eightdeva;096E +eighteencircle;2471 +eighteenparen;2485 +eighteenperiod;2499 +eightgujarati;0AEE +eightgurmukhi;0A6E +eighthackarabic;0668 +eighthangzhou;3028 +eighthnotebeamed;266B +eightideographicparen;3227 +eightinferior;2088 +eightmonospace;FF18 +eightoldstyle;F738 +eightparen;247B +eightperiod;248F +eightpersian;06F8 +eightroman;2177 +eightsuperior;2078 +eightthai;0E58 +einvertedbreve;0207 +eiotifiedcyrillic;0465 +ekatakana;30A8 +ekatakanahalfwidth;FF74 +ekonkargurmukhi;0A74 +ekorean;3154 +elcyrillic;043B +element;2208 +elevencircle;246A +elevenparen;247E +elevenperiod;2492 +elevenroman;217A +ellipsis;2026 +ellipsisvertical;22EE +emacron;0113 +emacronacute;1E17 +emacrongrave;1E15 +emcyrillic;043C +emdash;2014 +emdashvertical;FE31 +emonospace;FF45 +emphasismarkarmenian;055B +emptyset;2205 +enbopomofo;3123 +encyrillic;043D +endash;2013 +endashvertical;FE32 +endescendercyrillic;04A3 +eng;014B +engbopomofo;3125 +enghecyrillic;04A5 +enhookcyrillic;04C8 +enspace;2002 +eogonek;0119 +eokorean;3153 +eopen;025B +eopenclosed;029A +eopenreversed;025C +eopenreversedclosed;025E +eopenreversedhook;025D +eparen;24A0 +epsilon;03B5 +epsilontonos;03AD +equal;003D +equalmonospace;FF1D +equalsmall;FE66 +equalsuperior;207C +equivalence;2261 +erbopomofo;3126 +ercyrillic;0440 +ereversed;0258 +ereversedcyrillic;044D +escyrillic;0441 +esdescendercyrillic;04AB +esh;0283 +eshcurl;0286 +eshortdeva;090E +eshortvowelsigndeva;0946 +eshreversedloop;01AA +eshsquatreversed;0285 +esmallhiragana;3047 +esmallkatakana;30A7 +esmallkatakanahalfwidth;FF6A +estimated;212E +esuperior;F6EC +eta;03B7 +etarmenian;0568 +etatonos;03AE +eth;00F0 +etilde;1EBD +etildebelow;1E1B +etnahtafoukhhebrew;0591 +etnahtafoukhlefthebrew;0591 +etnahtahebrew;0591 +etnahtalefthebrew;0591 +eturned;01DD +eukorean;3161 +euro;20AC +evowelsignbengali;09C7 +evowelsigndeva;0947 +evowelsigngujarati;0AC7 +exclam;0021 +exclamarmenian;055C +exclamdbl;203C +exclamdown;00A1 +exclamdownsmall;F7A1 +exclammonospace;FF01 +exclamsmall;F721 +existential;2203 +ezh;0292 +ezhcaron;01EF +ezhcurl;0293 +ezhreversed;01B9 +ezhtail;01BA +f;0066 +fadeva;095E +fagurmukhi;0A5E +fahrenheit;2109 +fathaarabic;064E +fathalowarabic;064E +fathatanarabic;064B +fbopomofo;3108 +fcircle;24D5 +fdotaccent;1E1F +feharabic;0641 +feharmenian;0586 +fehfinalarabic;FED2 +fehinitialarabic;FED3 +fehmedialarabic;FED4 +feicoptic;03E5 +female;2640 +ff;FB00 +ffi;FB03 +ffl;FB04 +fi;FB01 +fifteencircle;246E +fifteenparen;2482 +fifteenperiod;2496 +figuredash;2012 +filledbox;25A0 +filledrect;25AC +finalkaf;05DA +finalkafdagesh;FB3A +finalkafdageshhebrew;FB3A +finalkafhebrew;05DA +finalkafqamats;05DA 05B8 +finalkafqamatshebrew;05DA 05B8 +finalkafsheva;05DA 05B0 +finalkafshevahebrew;05DA 05B0 +finalmem;05DD +finalmemhebrew;05DD +finalnun;05DF +finalnunhebrew;05DF +finalpe;05E3 +finalpehebrew;05E3 +finaltsadi;05E5 +finaltsadihebrew;05E5 +firsttonechinese;02C9 +fisheye;25C9 +fitacyrillic;0473 +five;0035 +fivearabic;0665 +fivebengali;09EB +fivecircle;2464 +fivecircleinversesansserif;278E +fivedeva;096B +fiveeighths;215D +fivegujarati;0AEB +fivegurmukhi;0A6B +fivehackarabic;0665 +fivehangzhou;3025 +fiveideographicparen;3224 +fiveinferior;2085 +fivemonospace;FF15 +fiveoldstyle;F735 +fiveparen;2478 +fiveperiod;248C +fivepersian;06F5 +fiveroman;2174 +fivesuperior;2075 +fivethai;0E55 +fl;FB02 +florin;0192 +fmonospace;FF46 +fmsquare;3399 +fofanthai;0E1F +fofathai;0E1D +fongmanthai;0E4F +forall;2200 +four;0034 +fourarabic;0664 +fourbengali;09EA +fourcircle;2463 +fourcircleinversesansserif;278D +fourdeva;096A +fourgujarati;0AEA +fourgurmukhi;0A6A +fourhackarabic;0664 +fourhangzhou;3024 +fourideographicparen;3223 +fourinferior;2084 +fourmonospace;FF14 +fournumeratorbengali;09F7 +fouroldstyle;F734 +fourparen;2477 +fourperiod;248B +fourpersian;06F4 +fourroman;2173 +foursuperior;2074 +fourteencircle;246D +fourteenparen;2481 +fourteenperiod;2495 +fourthai;0E54 +fourthtonechinese;02CB +fparen;24A1 +fraction;2044 +franc;20A3 +g;0067 +gabengali;0997 +gacute;01F5 +gadeva;0917 +gafarabic;06AF +gaffinalarabic;FB93 +gafinitialarabic;FB94 +gafmedialarabic;FB95 +gagujarati;0A97 +gagurmukhi;0A17 +gahiragana;304C +gakatakana;30AC +gamma;03B3 +gammalatinsmall;0263 +gammasuperior;02E0 +gangiacoptic;03EB +gbopomofo;310D +gbreve;011F +gcaron;01E7 +gcedilla;0123 +gcircle;24D6 +gcircumflex;011D +gcommaaccent;0123 +gdot;0121 +gdotaccent;0121 +gecyrillic;0433 +gehiragana;3052 +gekatakana;30B2 +geometricallyequal;2251 +gereshaccenthebrew;059C +gereshhebrew;05F3 +gereshmuqdamhebrew;059D +germandbls;00DF +gershayimaccenthebrew;059E +gershayimhebrew;05F4 +getamark;3013 +ghabengali;0998 +ghadarmenian;0572 +ghadeva;0918 +ghagujarati;0A98 +ghagurmukhi;0A18 +ghainarabic;063A +ghainfinalarabic;FECE +ghaininitialarabic;FECF +ghainmedialarabic;FED0 +ghemiddlehookcyrillic;0495 +ghestrokecyrillic;0493 +gheupturncyrillic;0491 +ghhadeva;095A +ghhagurmukhi;0A5A +ghook;0260 +ghzsquare;3393 +gihiragana;304E +gikatakana;30AE +gimarmenian;0563 +gimel;05D2 +gimeldagesh;FB32 +gimeldageshhebrew;FB32 +gimelhebrew;05D2 +gjecyrillic;0453 +glottalinvertedstroke;01BE +glottalstop;0294 +glottalstopinverted;0296 +glottalstopmod;02C0 +glottalstopreversed;0295 +glottalstopreversedmod;02C1 +glottalstopreversedsuperior;02E4 +glottalstopstroke;02A1 +glottalstopstrokereversed;02A2 +gmacron;1E21 +gmonospace;FF47 +gohiragana;3054 +gokatakana;30B4 +gparen;24A2 +gpasquare;33AC +gradient;2207 +grave;0060 +gravebelowcmb;0316 +gravecmb;0300 +gravecomb;0300 +gravedeva;0953 +gravelowmod;02CE +gravemonospace;FF40 +gravetonecmb;0340 +greater;003E +greaterequal;2265 +greaterequalorless;22DB +greatermonospace;FF1E +greaterorequivalent;2273 +greaterorless;2277 +greateroverequal;2267 +greatersmall;FE65 +gscript;0261 +gstroke;01E5 +guhiragana;3050 +guillemotleft;00AB +guillemotright;00BB +guilsinglleft;2039 +guilsinglright;203A +gukatakana;30B0 +guramusquare;3318 +gysquare;33C9 +h;0068 +haabkhasiancyrillic;04A9 +haaltonearabic;06C1 +habengali;09B9 +hadescendercyrillic;04B3 +hadeva;0939 +hagujarati;0AB9 +hagurmukhi;0A39 +haharabic;062D +hahfinalarabic;FEA2 +hahinitialarabic;FEA3 +hahiragana;306F +hahmedialarabic;FEA4 +haitusquare;332A +hakatakana;30CF +hakatakanahalfwidth;FF8A +halantgurmukhi;0A4D +hamzaarabic;0621 +hamzadammaarabic;0621 064F +hamzadammatanarabic;0621 064C +hamzafathaarabic;0621 064E +hamzafathatanarabic;0621 064B +hamzalowarabic;0621 +hamzalowkasraarabic;0621 0650 +hamzalowkasratanarabic;0621 064D +hamzasukunarabic;0621 0652 +hangulfiller;3164 +hardsigncyrillic;044A +harpoonleftbarbup;21BC +harpoonrightbarbup;21C0 +hasquare;33CA +hatafpatah;05B2 +hatafpatah16;05B2 +hatafpatah23;05B2 +hatafpatah2f;05B2 +hatafpatahhebrew;05B2 +hatafpatahnarrowhebrew;05B2 +hatafpatahquarterhebrew;05B2 +hatafpatahwidehebrew;05B2 +hatafqamats;05B3 +hatafqamats1b;05B3 +hatafqamats28;05B3 +hatafqamats34;05B3 +hatafqamatshebrew;05B3 +hatafqamatsnarrowhebrew;05B3 +hatafqamatsquarterhebrew;05B3 +hatafqamatswidehebrew;05B3 +hatafsegol;05B1 +hatafsegol17;05B1 +hatafsegol24;05B1 +hatafsegol30;05B1 +hatafsegolhebrew;05B1 +hatafsegolnarrowhebrew;05B1 +hatafsegolquarterhebrew;05B1 +hatafsegolwidehebrew;05B1 +hbar;0127 +hbopomofo;310F +hbrevebelow;1E2B +hcedilla;1E29 +hcircle;24D7 +hcircumflex;0125 +hdieresis;1E27 +hdotaccent;1E23 +hdotbelow;1E25 +he;05D4 +heart;2665 +heartsuitblack;2665 +heartsuitwhite;2661 +hedagesh;FB34 +hedageshhebrew;FB34 +hehaltonearabic;06C1 +heharabic;0647 +hehebrew;05D4 +hehfinalaltonearabic;FBA7 +hehfinalalttwoarabic;FEEA +hehfinalarabic;FEEA +hehhamzaabovefinalarabic;FBA5 +hehhamzaaboveisolatedarabic;FBA4 +hehinitialaltonearabic;FBA8 +hehinitialarabic;FEEB +hehiragana;3078 +hehmedialaltonearabic;FBA9 +hehmedialarabic;FEEC +heiseierasquare;337B +hekatakana;30D8 +hekatakanahalfwidth;FF8D +hekutaarusquare;3336 +henghook;0267 +herutusquare;3339 +het;05D7 +hethebrew;05D7 +hhook;0266 +hhooksuperior;02B1 +hieuhacirclekorean;327B +hieuhaparenkorean;321B +hieuhcirclekorean;326D +hieuhkorean;314E +hieuhparenkorean;320D +hihiragana;3072 +hikatakana;30D2 +hikatakanahalfwidth;FF8B +hiriq;05B4 +hiriq14;05B4 +hiriq21;05B4 +hiriq2d;05B4 +hiriqhebrew;05B4 +hiriqnarrowhebrew;05B4 +hiriqquarterhebrew;05B4 +hiriqwidehebrew;05B4 +hlinebelow;1E96 +hmonospace;FF48 +hoarmenian;0570 +hohipthai;0E2B +hohiragana;307B +hokatakana;30DB +hokatakanahalfwidth;FF8E +holam;05B9 +holam19;05B9 +holam26;05B9 +holam32;05B9 +holamhebrew;05B9 +holamnarrowhebrew;05B9 +holamquarterhebrew;05B9 +holamwidehebrew;05B9 +honokhukthai;0E2E +hookabovecomb;0309 +hookcmb;0309 +hookpalatalizedbelowcmb;0321 +hookretroflexbelowcmb;0322 +hoonsquare;3342 +horicoptic;03E9 +horizontalbar;2015 +horncmb;031B +hotsprings;2668 +house;2302 +hparen;24A3 +hsuperior;02B0 +hturned;0265 +huhiragana;3075 +huiitosquare;3333 +hukatakana;30D5 +hukatakanahalfwidth;FF8C +hungarumlaut;02DD +hungarumlautcmb;030B +hv;0195 +hyphen;002D +hypheninferior;F6E5 +hyphenmonospace;FF0D +hyphensmall;FE63 +hyphensuperior;F6E6 +hyphentwo;2010 +i;0069 +iacute;00ED +iacyrillic;044F +ibengali;0987 +ibopomofo;3127 +ibreve;012D +icaron;01D0 +icircle;24D8 +icircumflex;00EE +icyrillic;0456 +idblgrave;0209 +ideographearthcircle;328F +ideographfirecircle;328B +ideographicallianceparen;323F +ideographiccallparen;323A +ideographiccentrecircle;32A5 +ideographicclose;3006 +ideographiccomma;3001 +ideographiccommaleft;FF64 +ideographiccongratulationparen;3237 +ideographiccorrectcircle;32A3 +ideographicearthparen;322F +ideographicenterpriseparen;323D +ideographicexcellentcircle;329D +ideographicfestivalparen;3240 +ideographicfinancialcircle;3296 +ideographicfinancialparen;3236 +ideographicfireparen;322B +ideographichaveparen;3232 +ideographichighcircle;32A4 +ideographiciterationmark;3005 +ideographiclaborcircle;3298 +ideographiclaborparen;3238 +ideographicleftcircle;32A7 +ideographiclowcircle;32A6 +ideographicmedicinecircle;32A9 +ideographicmetalparen;322E +ideographicmoonparen;322A +ideographicnameparen;3234 +ideographicperiod;3002 +ideographicprintcircle;329E +ideographicreachparen;3243 +ideographicrepresentparen;3239 +ideographicresourceparen;323E +ideographicrightcircle;32A8 +ideographicsecretcircle;3299 +ideographicselfparen;3242 +ideographicsocietyparen;3233 +ideographicspace;3000 +ideographicspecialparen;3235 +ideographicstockparen;3231 +ideographicstudyparen;323B +ideographicsunparen;3230 +ideographicsuperviseparen;323C +ideographicwaterparen;322C +ideographicwoodparen;322D +ideographiczero;3007 +ideographmetalcircle;328E +ideographmooncircle;328A +ideographnamecircle;3294 +ideographsuncircle;3290 +ideographwatercircle;328C +ideographwoodcircle;328D +ideva;0907 +idieresis;00EF +idieresisacute;1E2F +idieresiscyrillic;04E5 +idotbelow;1ECB +iebrevecyrillic;04D7 +iecyrillic;0435 +ieungacirclekorean;3275 +ieungaparenkorean;3215 +ieungcirclekorean;3267 +ieungkorean;3147 +ieungparenkorean;3207 +igrave;00EC +igujarati;0A87 +igurmukhi;0A07 +ihiragana;3044 +ihookabove;1EC9 +iibengali;0988 +iicyrillic;0438 +iideva;0908 +iigujarati;0A88 +iigurmukhi;0A08 +iimatragurmukhi;0A40 +iinvertedbreve;020B +iishortcyrillic;0439 +iivowelsignbengali;09C0 +iivowelsigndeva;0940 +iivowelsigngujarati;0AC0 +ij;0133 +ikatakana;30A4 +ikatakanahalfwidth;FF72 +ikorean;3163 +ilde;02DC +iluyhebrew;05AC +imacron;012B +imacroncyrillic;04E3 +imageorapproximatelyequal;2253 +imatragurmukhi;0A3F +imonospace;FF49 +increment;2206 +infinity;221E +iniarmenian;056B +integral;222B +integralbottom;2321 +integralbt;2321 +integralex;F8F5 +integraltop;2320 +integraltp;2320 +intersection;2229 +intisquare;3305 +invbullet;25D8 +invcircle;25D9 +invsmileface;263B +iocyrillic;0451 +iogonek;012F +iota;03B9 +iotadieresis;03CA +iotadieresistonos;0390 +iotalatin;0269 +iotatonos;03AF +iparen;24A4 +irigurmukhi;0A72 +ismallhiragana;3043 +ismallkatakana;30A3 +ismallkatakanahalfwidth;FF68 +issharbengali;09FA +istroke;0268 +isuperior;F6ED +iterationhiragana;309D +iterationkatakana;30FD +itilde;0129 +itildebelow;1E2D +iubopomofo;3129 +iucyrillic;044E +ivowelsignbengali;09BF +ivowelsigndeva;093F +ivowelsigngujarati;0ABF +izhitsacyrillic;0475 +izhitsadblgravecyrillic;0477 +j;006A +jaarmenian;0571 +jabengali;099C +jadeva;091C +jagujarati;0A9C +jagurmukhi;0A1C +jbopomofo;3110 +jcaron;01F0 +jcircle;24D9 +jcircumflex;0135 +jcrossedtail;029D +jdotlessstroke;025F +jecyrillic;0458 +jeemarabic;062C +jeemfinalarabic;FE9E +jeeminitialarabic;FE9F +jeemmedialarabic;FEA0 +jeharabic;0698 +jehfinalarabic;FB8B +jhabengali;099D +jhadeva;091D +jhagujarati;0A9D +jhagurmukhi;0A1D +jheharmenian;057B +jis;3004 +jmonospace;FF4A +jparen;24A5 +jsuperior;02B2 +k;006B +kabashkircyrillic;04A1 +kabengali;0995 +kacute;1E31 +kacyrillic;043A +kadescendercyrillic;049B +kadeva;0915 +kaf;05DB +kafarabic;0643 +kafdagesh;FB3B +kafdageshhebrew;FB3B +kaffinalarabic;FEDA +kafhebrew;05DB +kafinitialarabic;FEDB +kafmedialarabic;FEDC +kafrafehebrew;FB4D +kagujarati;0A95 +kagurmukhi;0A15 +kahiragana;304B +kahookcyrillic;04C4 +kakatakana;30AB +kakatakanahalfwidth;FF76 +kappa;03BA +kappasymbolgreek;03F0 +kapyeounmieumkorean;3171 +kapyeounphieuphkorean;3184 +kapyeounpieupkorean;3178 +kapyeounssangpieupkorean;3179 +karoriisquare;330D +kashidaautoarabic;0640 +kashidaautonosidebearingarabic;0640 +kasmallkatakana;30F5 +kasquare;3384 +kasraarabic;0650 +kasratanarabic;064D +kastrokecyrillic;049F +katahiraprolongmarkhalfwidth;FF70 +kaverticalstrokecyrillic;049D +kbopomofo;310E +kcalsquare;3389 +kcaron;01E9 +kcedilla;0137 +kcircle;24DA +kcommaaccent;0137 +kdotbelow;1E33 +keharmenian;0584 +kehiragana;3051 +kekatakana;30B1 +kekatakanahalfwidth;FF79 +kenarmenian;056F +kesmallkatakana;30F6 +kgreenlandic;0138 +khabengali;0996 +khacyrillic;0445 +khadeva;0916 +khagujarati;0A96 +khagurmukhi;0A16 +khaharabic;062E +khahfinalarabic;FEA6 +khahinitialarabic;FEA7 +khahmedialarabic;FEA8 +kheicoptic;03E7 +khhadeva;0959 +khhagurmukhi;0A59 +khieukhacirclekorean;3278 +khieukhaparenkorean;3218 +khieukhcirclekorean;326A +khieukhkorean;314B +khieukhparenkorean;320A +khokhaithai;0E02 +khokhonthai;0E05 +khokhuatthai;0E03 +khokhwaithai;0E04 +khomutthai;0E5B +khook;0199 +khorakhangthai;0E06 +khzsquare;3391 +kihiragana;304D +kikatakana;30AD +kikatakanahalfwidth;FF77 +kiroguramusquare;3315 +kiromeetorusquare;3316 +kirosquare;3314 +kiyeokacirclekorean;326E +kiyeokaparenkorean;320E +kiyeokcirclekorean;3260 +kiyeokkorean;3131 +kiyeokparenkorean;3200 +kiyeoksioskorean;3133 +kjecyrillic;045C +klinebelow;1E35 +klsquare;3398 +kmcubedsquare;33A6 +kmonospace;FF4B +kmsquaredsquare;33A2 +kohiragana;3053 +kohmsquare;33C0 +kokaithai;0E01 +kokatakana;30B3 +kokatakanahalfwidth;FF7A +kooposquare;331E +koppacyrillic;0481 +koreanstandardsymbol;327F +koroniscmb;0343 +kparen;24A6 +kpasquare;33AA +ksicyrillic;046F +ktsquare;33CF +kturned;029E +kuhiragana;304F +kukatakana;30AF +kukatakanahalfwidth;FF78 +kvsquare;33B8 +kwsquare;33BE +l;006C +labengali;09B2 +lacute;013A +ladeva;0932 +lagujarati;0AB2 +lagurmukhi;0A32 +lakkhangyaothai;0E45 +lamaleffinalarabic;FEFC +lamalefhamzaabovefinalarabic;FEF8 +lamalefhamzaaboveisolatedarabic;FEF7 +lamalefhamzabelowfinalarabic;FEFA +lamalefhamzabelowisolatedarabic;FEF9 +lamalefisolatedarabic;FEFB +lamalefmaddaabovefinalarabic;FEF6 +lamalefmaddaaboveisolatedarabic;FEF5 +lamarabic;0644 +lambda;03BB +lambdastroke;019B +lamed;05DC +lameddagesh;FB3C +lameddageshhebrew;FB3C +lamedhebrew;05DC +lamedholam;05DC 05B9 +lamedholamdagesh;05DC 05B9 05BC +lamedholamdageshhebrew;05DC 05B9 05BC +lamedholamhebrew;05DC 05B9 +lamfinalarabic;FEDE +lamhahinitialarabic;FCCA +laminitialarabic;FEDF +lamjeeminitialarabic;FCC9 +lamkhahinitialarabic;FCCB +lamlamhehisolatedarabic;FDF2 +lammedialarabic;FEE0 +lammeemhahinitialarabic;FD88 +lammeeminitialarabic;FCCC +lammeemjeeminitialarabic;FEDF FEE4 FEA0 +lammeemkhahinitialarabic;FEDF FEE4 FEA8 +largecircle;25EF +lbar;019A +lbelt;026C +lbopomofo;310C +lcaron;013E +lcedilla;013C +lcircle;24DB +lcircumflexbelow;1E3D +lcommaaccent;013C +ldot;0140 +ldotaccent;0140 +ldotbelow;1E37 +ldotbelowmacron;1E39 +leftangleabovecmb;031A +lefttackbelowcmb;0318 +less;003C +lessequal;2264 +lessequalorgreater;22DA +lessmonospace;FF1C +lessorequivalent;2272 +lessorgreater;2276 +lessoverequal;2266 +lesssmall;FE64 +lezh;026E +lfblock;258C +lhookretroflex;026D +lira;20A4 +liwnarmenian;056C +lj;01C9 +ljecyrillic;0459 +ll;F6C0 +lladeva;0933 +llagujarati;0AB3 +llinebelow;1E3B +llladeva;0934 +llvocalicbengali;09E1 +llvocalicdeva;0961 +llvocalicvowelsignbengali;09E3 +llvocalicvowelsigndeva;0963 +lmiddletilde;026B +lmonospace;FF4C +lmsquare;33D0 +lochulathai;0E2C +logicaland;2227 +logicalnot;00AC +logicalnotreversed;2310 +logicalor;2228 +lolingthai;0E25 +longs;017F +lowlinecenterline;FE4E +lowlinecmb;0332 +lowlinedashed;FE4D +lozenge;25CA +lparen;24A7 +lslash;0142 +lsquare;2113 +lsuperior;F6EE +ltshade;2591 +luthai;0E26 +lvocalicbengali;098C +lvocalicdeva;090C +lvocalicvowelsignbengali;09E2 +lvocalicvowelsigndeva;0962 +lxsquare;33D3 +m;006D +mabengali;09AE +macron;00AF +macronbelowcmb;0331 +macroncmb;0304 +macronlowmod;02CD +macronmonospace;FFE3 +macute;1E3F +madeva;092E +magujarati;0AAE +magurmukhi;0A2E +mahapakhhebrew;05A4 +mahapakhlefthebrew;05A4 +mahiragana;307E +maichattawalowleftthai;F895 +maichattawalowrightthai;F894 +maichattawathai;0E4B +maichattawaupperleftthai;F893 +maieklowleftthai;F88C +maieklowrightthai;F88B +maiekthai;0E48 +maiekupperleftthai;F88A +maihanakatleftthai;F884 +maihanakatthai;0E31 +maitaikhuleftthai;F889 +maitaikhuthai;0E47 +maitholowleftthai;F88F +maitholowrightthai;F88E +maithothai;0E49 +maithoupperleftthai;F88D +maitrilowleftthai;F892 +maitrilowrightthai;F891 +maitrithai;0E4A +maitriupperleftthai;F890 +maiyamokthai;0E46 +makatakana;30DE +makatakanahalfwidth;FF8F +male;2642 +mansyonsquare;3347 +maqafhebrew;05BE +mars;2642 +masoracirclehebrew;05AF +masquare;3383 +mbopomofo;3107 +mbsquare;33D4 +mcircle;24DC +mcubedsquare;33A5 +mdotaccent;1E41 +mdotbelow;1E43 +meemarabic;0645 +meemfinalarabic;FEE2 +meeminitialarabic;FEE3 +meemmedialarabic;FEE4 +meemmeeminitialarabic;FCD1 +meemmeemisolatedarabic;FC48 +meetorusquare;334D +mehiragana;3081 +meizierasquare;337E +mekatakana;30E1 +mekatakanahalfwidth;FF92 +mem;05DE +memdagesh;FB3E +memdageshhebrew;FB3E +memhebrew;05DE +menarmenian;0574 +merkhahebrew;05A5 +merkhakefulahebrew;05A6 +merkhakefulalefthebrew;05A6 +merkhalefthebrew;05A5 +mhook;0271 +mhzsquare;3392 +middledotkatakanahalfwidth;FF65 +middot;00B7 +mieumacirclekorean;3272 +mieumaparenkorean;3212 +mieumcirclekorean;3264 +mieumkorean;3141 +mieumpansioskorean;3170 +mieumparenkorean;3204 +mieumpieupkorean;316E +mieumsioskorean;316F +mihiragana;307F +mikatakana;30DF +mikatakanahalfwidth;FF90 +minus;2212 +minusbelowcmb;0320 +minuscircle;2296 +minusmod;02D7 +minusplus;2213 +minute;2032 +miribaarusquare;334A +mirisquare;3349 +mlonglegturned;0270 +mlsquare;3396 +mmcubedsquare;33A3 +mmonospace;FF4D +mmsquaredsquare;339F +mohiragana;3082 +mohmsquare;33C1 +mokatakana;30E2 +mokatakanahalfwidth;FF93 +molsquare;33D6 +momathai;0E21 +moverssquare;33A7 +moverssquaredsquare;33A8 +mparen;24A8 +mpasquare;33AB +mssquare;33B3 +msuperior;F6EF +mturned;026F +mu;00B5 +mu1;00B5 +muasquare;3382 +muchgreater;226B +muchless;226A +mufsquare;338C +mugreek;03BC +mugsquare;338D +muhiragana;3080 +mukatakana;30E0 +mukatakanahalfwidth;FF91 +mulsquare;3395 +multiply;00D7 +mumsquare;339B +munahhebrew;05A3 +munahlefthebrew;05A3 +musicalnote;266A +musicalnotedbl;266B +musicflatsign;266D +musicsharpsign;266F +mussquare;33B2 +muvsquare;33B6 +muwsquare;33BC +mvmegasquare;33B9 +mvsquare;33B7 +mwmegasquare;33BF +mwsquare;33BD +n;006E +nabengali;09A8 +nabla;2207 +nacute;0144 +nadeva;0928 +nagujarati;0AA8 +nagurmukhi;0A28 +nahiragana;306A +nakatakana;30CA +nakatakanahalfwidth;FF85 +napostrophe;0149 +nasquare;3381 +nbopomofo;310B +nbspace;00A0 +ncaron;0148 +ncedilla;0146 +ncircle;24DD +ncircumflexbelow;1E4B +ncommaaccent;0146 +ndotaccent;1E45 +ndotbelow;1E47 +nehiragana;306D +nekatakana;30CD +nekatakanahalfwidth;FF88 +newsheqelsign;20AA +nfsquare;338B +ngabengali;0999 +ngadeva;0919 +ngagujarati;0A99 +ngagurmukhi;0A19 +ngonguthai;0E07 +nhiragana;3093 +nhookleft;0272 +nhookretroflex;0273 +nieunacirclekorean;326F +nieunaparenkorean;320F +nieuncieuckorean;3135 +nieuncirclekorean;3261 +nieunhieuhkorean;3136 +nieunkorean;3134 +nieunpansioskorean;3168 +nieunparenkorean;3201 +nieunsioskorean;3167 +nieuntikeutkorean;3166 +nihiragana;306B +nikatakana;30CB +nikatakanahalfwidth;FF86 +nikhahitleftthai;F899 +nikhahitthai;0E4D +nine;0039 +ninearabic;0669 +ninebengali;09EF +ninecircle;2468 +ninecircleinversesansserif;2792 +ninedeva;096F +ninegujarati;0AEF +ninegurmukhi;0A6F +ninehackarabic;0669 +ninehangzhou;3029 +nineideographicparen;3228 +nineinferior;2089 +ninemonospace;FF19 +nineoldstyle;F739 +nineparen;247C +nineperiod;2490 +ninepersian;06F9 +nineroman;2178 +ninesuperior;2079 +nineteencircle;2472 +nineteenparen;2486 +nineteenperiod;249A +ninethai;0E59 +nj;01CC +njecyrillic;045A +nkatakana;30F3 +nkatakanahalfwidth;FF9D +nlegrightlong;019E +nlinebelow;1E49 +nmonospace;FF4E +nmsquare;339A +nnabengali;09A3 +nnadeva;0923 +nnagujarati;0AA3 +nnagurmukhi;0A23 +nnnadeva;0929 +nohiragana;306E +nokatakana;30CE +nokatakanahalfwidth;FF89 +nonbreakingspace;00A0 +nonenthai;0E13 +nonuthai;0E19 +noonarabic;0646 +noonfinalarabic;FEE6 +noonghunnaarabic;06BA +noonghunnafinalarabic;FB9F +noonhehinitialarabic;FEE7 FEEC +nooninitialarabic;FEE7 +noonjeeminitialarabic;FCD2 +noonjeemisolatedarabic;FC4B +noonmedialarabic;FEE8 +noonmeeminitialarabic;FCD5 +noonmeemisolatedarabic;FC4E +noonnoonfinalarabic;FC8D +notcontains;220C +notelement;2209 +notelementof;2209 +notequal;2260 +notgreater;226F +notgreaternorequal;2271 +notgreaternorless;2279 +notidentical;2262 +notless;226E +notlessnorequal;2270 +notparallel;2226 +notprecedes;2280 +notsubset;2284 +notsucceeds;2281 +notsuperset;2285 +nowarmenian;0576 +nparen;24A9 +nssquare;33B1 +nsuperior;207F +ntilde;00F1 +nu;03BD +nuhiragana;306C +nukatakana;30CC +nukatakanahalfwidth;FF87 +nuktabengali;09BC +nuktadeva;093C +nuktagujarati;0ABC +nuktagurmukhi;0A3C +numbersign;0023 +numbersignmonospace;FF03 +numbersignsmall;FE5F +numeralsigngreek;0374 +numeralsignlowergreek;0375 +numero;2116 +nun;05E0 +nundagesh;FB40 +nundageshhebrew;FB40 +nunhebrew;05E0 +nvsquare;33B5 +nwsquare;33BB +nyabengali;099E +nyadeva;091E +nyagujarati;0A9E +nyagurmukhi;0A1E +o;006F +oacute;00F3 +oangthai;0E2D +obarred;0275 +obarredcyrillic;04E9 +obarreddieresiscyrillic;04EB +obengali;0993 +obopomofo;311B +obreve;014F +ocandradeva;0911 +ocandragujarati;0A91 +ocandravowelsigndeva;0949 +ocandravowelsigngujarati;0AC9 +ocaron;01D2 +ocircle;24DE +ocircumflex;00F4 +ocircumflexacute;1ED1 +ocircumflexdotbelow;1ED9 +ocircumflexgrave;1ED3 +ocircumflexhookabove;1ED5 +ocircumflextilde;1ED7 +ocyrillic;043E +odblacute;0151 +odblgrave;020D +odeva;0913 +odieresis;00F6 +odieresiscyrillic;04E7 +odotbelow;1ECD +oe;0153 +oekorean;315A +ogonek;02DB +ogonekcmb;0328 +ograve;00F2 +ogujarati;0A93 +oharmenian;0585 +ohiragana;304A +ohookabove;1ECF +ohorn;01A1 +ohornacute;1EDB +ohorndotbelow;1EE3 +ohorngrave;1EDD +ohornhookabove;1EDF +ohorntilde;1EE1 +ohungarumlaut;0151 +oi;01A3 +oinvertedbreve;020F +okatakana;30AA +okatakanahalfwidth;FF75 +okorean;3157 +olehebrew;05AB +omacron;014D +omacronacute;1E53 +omacrongrave;1E51 +omdeva;0950 +omega;03C9 +omega1;03D6 +omegacyrillic;0461 +omegalatinclosed;0277 +omegaroundcyrillic;047B +omegatitlocyrillic;047D +omegatonos;03CE +omgujarati;0AD0 +omicron;03BF +omicrontonos;03CC +omonospace;FF4F +one;0031 +onearabic;0661 +onebengali;09E7 +onecircle;2460 +onecircleinversesansserif;278A +onedeva;0967 +onedotenleader;2024 +oneeighth;215B +onefitted;F6DC +onegujarati;0AE7 +onegurmukhi;0A67 +onehackarabic;0661 +onehalf;00BD +onehangzhou;3021 +oneideographicparen;3220 +oneinferior;2081 +onemonospace;FF11 +onenumeratorbengali;09F4 +oneoldstyle;F731 +oneparen;2474 +oneperiod;2488 +onepersian;06F1 +onequarter;00BC +oneroman;2170 +onesuperior;00B9 +onethai;0E51 +onethird;2153 +oogonek;01EB +oogonekmacron;01ED +oogurmukhi;0A13 +oomatragurmukhi;0A4B +oopen;0254 +oparen;24AA +openbullet;25E6 +option;2325 +ordfeminine;00AA +ordmasculine;00BA +orthogonal;221F +oshortdeva;0912 +oshortvowelsigndeva;094A +oslash;00F8 +oslashacute;01FF +osmallhiragana;3049 +osmallkatakana;30A9 +osmallkatakanahalfwidth;FF6B +ostrokeacute;01FF +osuperior;F6F0 +otcyrillic;047F +otilde;00F5 +otildeacute;1E4D +otildedieresis;1E4F +oubopomofo;3121 +overline;203E +overlinecenterline;FE4A +overlinecmb;0305 +overlinedashed;FE49 +overlinedblwavy;FE4C +overlinewavy;FE4B +overscore;00AF +ovowelsignbengali;09CB +ovowelsigndeva;094B +ovowelsigngujarati;0ACB +p;0070 +paampssquare;3380 +paasentosquare;332B +pabengali;09AA +pacute;1E55 +padeva;092A +pagedown;21DF +pageup;21DE +pagujarati;0AAA +pagurmukhi;0A2A +pahiragana;3071 +paiyannoithai;0E2F +pakatakana;30D1 +palatalizationcyrilliccmb;0484 +palochkacyrillic;04C0 +pansioskorean;317F +paragraph;00B6 +parallel;2225 +parenleft;0028 +parenleftaltonearabic;FD3E +parenleftbt;F8ED +parenleftex;F8EC +parenleftinferior;208D +parenleftmonospace;FF08 +parenleftsmall;FE59 +parenleftsuperior;207D +parenlefttp;F8EB +parenleftvertical;FE35 +parenright;0029 +parenrightaltonearabic;FD3F +parenrightbt;F8F8 +parenrightex;F8F7 +parenrightinferior;208E +parenrightmonospace;FF09 +parenrightsmall;FE5A +parenrightsuperior;207E +parenrighttp;F8F6 +parenrightvertical;FE36 +partialdiff;2202 +paseqhebrew;05C0 +pashtahebrew;0599 +pasquare;33A9 +patah;05B7 +patah11;05B7 +patah1d;05B7 +patah2a;05B7 +patahhebrew;05B7 +patahnarrowhebrew;05B7 +patahquarterhebrew;05B7 +patahwidehebrew;05B7 +pazerhebrew;05A1 +pbopomofo;3106 +pcircle;24DF +pdotaccent;1E57 +pe;05E4 +pecyrillic;043F +pedagesh;FB44 +pedageshhebrew;FB44 +peezisquare;333B +pefinaldageshhebrew;FB43 +peharabic;067E +peharmenian;057A +pehebrew;05E4 +pehfinalarabic;FB57 +pehinitialarabic;FB58 +pehiragana;307A +pehmedialarabic;FB59 +pekatakana;30DA +pemiddlehookcyrillic;04A7 +perafehebrew;FB4E +percent;0025 +percentarabic;066A +percentmonospace;FF05 +percentsmall;FE6A +period;002E +periodarmenian;0589 +periodcentered;00B7 +periodhalfwidth;FF61 +periodinferior;F6E7 +periodmonospace;FF0E +periodsmall;FE52 +periodsuperior;F6E8 +perispomenigreekcmb;0342 +perpendicular;22A5 +perthousand;2030 +peseta;20A7 +pfsquare;338A +phabengali;09AB +phadeva;092B +phagujarati;0AAB +phagurmukhi;0A2B +phi;03C6 +phi1;03D5 +phieuphacirclekorean;327A +phieuphaparenkorean;321A +phieuphcirclekorean;326C +phieuphkorean;314D +phieuphparenkorean;320C +philatin;0278 +phinthuthai;0E3A +phisymbolgreek;03D5 +phook;01A5 +phophanthai;0E1E +phophungthai;0E1C +phosamphaothai;0E20 +pi;03C0 +pieupacirclekorean;3273 +pieupaparenkorean;3213 +pieupcieuckorean;3176 +pieupcirclekorean;3265 +pieupkiyeokkorean;3172 +pieupkorean;3142 +pieupparenkorean;3205 +pieupsioskiyeokkorean;3174 +pieupsioskorean;3144 +pieupsiostikeutkorean;3175 +pieupthieuthkorean;3177 +pieuptikeutkorean;3173 +pihiragana;3074 +pikatakana;30D4 +pisymbolgreek;03D6 +piwrarmenian;0583 +plus;002B +plusbelowcmb;031F +pluscircle;2295 +plusminus;00B1 +plusmod;02D6 +plusmonospace;FF0B +plussmall;FE62 +plussuperior;207A +pmonospace;FF50 +pmsquare;33D8 +pohiragana;307D +pointingindexdownwhite;261F +pointingindexleftwhite;261C +pointingindexrightwhite;261E +pointingindexupwhite;261D +pokatakana;30DD +poplathai;0E1B +postalmark;3012 +postalmarkface;3020 +pparen;24AB +precedes;227A +prescription;211E +primemod;02B9 +primereversed;2035 +product;220F +projective;2305 +prolongedkana;30FC +propellor;2318 +propersubset;2282 +propersuperset;2283 +proportion;2237 +proportional;221D +psi;03C8 +psicyrillic;0471 +psilipneumatacyrilliccmb;0486 +pssquare;33B0 +puhiragana;3077 +pukatakana;30D7 +pvsquare;33B4 +pwsquare;33BA +q;0071 +qadeva;0958 +qadmahebrew;05A8 +qafarabic;0642 +qaffinalarabic;FED6 +qafinitialarabic;FED7 +qafmedialarabic;FED8 +qamats;05B8 +qamats10;05B8 +qamats1a;05B8 +qamats1c;05B8 +qamats27;05B8 +qamats29;05B8 +qamats33;05B8 +qamatsde;05B8 +qamatshebrew;05B8 +qamatsnarrowhebrew;05B8 +qamatsqatanhebrew;05B8 +qamatsqatannarrowhebrew;05B8 +qamatsqatanquarterhebrew;05B8 +qamatsqatanwidehebrew;05B8 +qamatsquarterhebrew;05B8 +qamatswidehebrew;05B8 +qarneyparahebrew;059F +qbopomofo;3111 +qcircle;24E0 +qhook;02A0 +qmonospace;FF51 +qof;05E7 +qofdagesh;FB47 +qofdageshhebrew;FB47 +qofhatafpatah;05E7 05B2 +qofhatafpatahhebrew;05E7 05B2 +qofhatafsegol;05E7 05B1 +qofhatafsegolhebrew;05E7 05B1 +qofhebrew;05E7 +qofhiriq;05E7 05B4 +qofhiriqhebrew;05E7 05B4 +qofholam;05E7 05B9 +qofholamhebrew;05E7 05B9 +qofpatah;05E7 05B7 +qofpatahhebrew;05E7 05B7 +qofqamats;05E7 05B8 +qofqamatshebrew;05E7 05B8 +qofqubuts;05E7 05BB +qofqubutshebrew;05E7 05BB +qofsegol;05E7 05B6 +qofsegolhebrew;05E7 05B6 +qofsheva;05E7 05B0 +qofshevahebrew;05E7 05B0 +qoftsere;05E7 05B5 +qoftserehebrew;05E7 05B5 +qparen;24AC +quarternote;2669 +qubuts;05BB +qubuts18;05BB +qubuts25;05BB +qubuts31;05BB +qubutshebrew;05BB +qubutsnarrowhebrew;05BB +qubutsquarterhebrew;05BB +qubutswidehebrew;05BB +question;003F +questionarabic;061F +questionarmenian;055E +questiondown;00BF +questiondownsmall;F7BF +questiongreek;037E +questionmonospace;FF1F +questionsmall;F73F +quotedbl;0022 +quotedblbase;201E +quotedblleft;201C +quotedblmonospace;FF02 +quotedblprime;301E +quotedblprimereversed;301D +quotedblright;201D +quoteleft;2018 +quoteleftreversed;201B +quotereversed;201B +quoteright;2019 +quoterightn;0149 +quotesinglbase;201A +quotesingle;0027 +quotesinglemonospace;FF07 +r;0072 +raarmenian;057C +rabengali;09B0 +racute;0155 +radeva;0930 +radical;221A +radicalex;F8E5 +radoverssquare;33AE +radoverssquaredsquare;33AF +radsquare;33AD +rafe;05BF +rafehebrew;05BF +ragujarati;0AB0 +ragurmukhi;0A30 +rahiragana;3089 +rakatakana;30E9 +rakatakanahalfwidth;FF97 +ralowerdiagonalbengali;09F1 +ramiddlediagonalbengali;09F0 +ramshorn;0264 +ratio;2236 +rbopomofo;3116 +rcaron;0159 +rcedilla;0157 +rcircle;24E1 +rcommaaccent;0157 +rdblgrave;0211 +rdotaccent;1E59 +rdotbelow;1E5B +rdotbelowmacron;1E5D +referencemark;203B +reflexsubset;2286 +reflexsuperset;2287 +registered;00AE +registersans;F8E8 +registerserif;F6DA +reharabic;0631 +reharmenian;0580 +rehfinalarabic;FEAE +rehiragana;308C +rehyehaleflamarabic;0631 FEF3 FE8E 0644 +rekatakana;30EC +rekatakanahalfwidth;FF9A +resh;05E8 +reshdageshhebrew;FB48 +reshhatafpatah;05E8 05B2 +reshhatafpatahhebrew;05E8 05B2 +reshhatafsegol;05E8 05B1 +reshhatafsegolhebrew;05E8 05B1 +reshhebrew;05E8 +reshhiriq;05E8 05B4 +reshhiriqhebrew;05E8 05B4 +reshholam;05E8 05B9 +reshholamhebrew;05E8 05B9 +reshpatah;05E8 05B7 +reshpatahhebrew;05E8 05B7 +reshqamats;05E8 05B8 +reshqamatshebrew;05E8 05B8 +reshqubuts;05E8 05BB +reshqubutshebrew;05E8 05BB +reshsegol;05E8 05B6 +reshsegolhebrew;05E8 05B6 +reshsheva;05E8 05B0 +reshshevahebrew;05E8 05B0 +reshtsere;05E8 05B5 +reshtserehebrew;05E8 05B5 +reversedtilde;223D +reviahebrew;0597 +reviamugrashhebrew;0597 +revlogicalnot;2310 +rfishhook;027E +rfishhookreversed;027F +rhabengali;09DD +rhadeva;095D +rho;03C1 +rhook;027D +rhookturned;027B +rhookturnedsuperior;02B5 +rhosymbolgreek;03F1 +rhotichookmod;02DE +rieulacirclekorean;3271 +rieulaparenkorean;3211 +rieulcirclekorean;3263 +rieulhieuhkorean;3140 +rieulkiyeokkorean;313A +rieulkiyeoksioskorean;3169 +rieulkorean;3139 +rieulmieumkorean;313B +rieulpansioskorean;316C +rieulparenkorean;3203 +rieulphieuphkorean;313F +rieulpieupkorean;313C +rieulpieupsioskorean;316B +rieulsioskorean;313D +rieulthieuthkorean;313E +rieultikeutkorean;316A +rieulyeorinhieuhkorean;316D +rightangle;221F +righttackbelowcmb;0319 +righttriangle;22BF +rihiragana;308A +rikatakana;30EA +rikatakanahalfwidth;FF98 +ring;02DA +ringbelowcmb;0325 +ringcmb;030A +ringhalfleft;02BF +ringhalfleftarmenian;0559 +ringhalfleftbelowcmb;031C +ringhalfleftcentered;02D3 +ringhalfright;02BE +ringhalfrightbelowcmb;0339 +ringhalfrightcentered;02D2 +rinvertedbreve;0213 +rittorusquare;3351 +rlinebelow;1E5F +rlongleg;027C +rlonglegturned;027A +rmonospace;FF52 +rohiragana;308D +rokatakana;30ED +rokatakanahalfwidth;FF9B +roruathai;0E23 +rparen;24AD +rrabengali;09DC +rradeva;0931 +rragurmukhi;0A5C +rreharabic;0691 +rrehfinalarabic;FB8D +rrvocalicbengali;09E0 +rrvocalicdeva;0960 +rrvocalicgujarati;0AE0 +rrvocalicvowelsignbengali;09C4 +rrvocalicvowelsigndeva;0944 +rrvocalicvowelsigngujarati;0AC4 +rsuperior;F6F1 +rtblock;2590 +rturned;0279 +rturnedsuperior;02B4 +ruhiragana;308B +rukatakana;30EB +rukatakanahalfwidth;FF99 +rupeemarkbengali;09F2 +rupeesignbengali;09F3 +rupiah;F6DD +ruthai;0E24 +rvocalicbengali;098B +rvocalicdeva;090B +rvocalicgujarati;0A8B +rvocalicvowelsignbengali;09C3 +rvocalicvowelsigndeva;0943 +rvocalicvowelsigngujarati;0AC3 +s;0073 +sabengali;09B8 +sacute;015B +sacutedotaccent;1E65 +sadarabic;0635 +sadeva;0938 +sadfinalarabic;FEBA +sadinitialarabic;FEBB +sadmedialarabic;FEBC +sagujarati;0AB8 +sagurmukhi;0A38 +sahiragana;3055 +sakatakana;30B5 +sakatakanahalfwidth;FF7B +sallallahoualayhewasallamarabic;FDFA +samekh;05E1 +samekhdagesh;FB41 +samekhdageshhebrew;FB41 +samekhhebrew;05E1 +saraaathai;0E32 +saraaethai;0E41 +saraaimaimalaithai;0E44 +saraaimaimuanthai;0E43 +saraamthai;0E33 +saraathai;0E30 +saraethai;0E40 +saraiileftthai;F886 +saraiithai;0E35 +saraileftthai;F885 +saraithai;0E34 +saraothai;0E42 +saraueeleftthai;F888 +saraueethai;0E37 +saraueleftthai;F887 +sarauethai;0E36 +sarauthai;0E38 +sarauuthai;0E39 +sbopomofo;3119 +scaron;0161 +scarondotaccent;1E67 +scedilla;015F +schwa;0259 +schwacyrillic;04D9 +schwadieresiscyrillic;04DB +schwahook;025A +scircle;24E2 +scircumflex;015D +scommaaccent;0219 +sdotaccent;1E61 +sdotbelow;1E63 +sdotbelowdotaccent;1E69 +seagullbelowcmb;033C +second;2033 +secondtonechinese;02CA +section;00A7 +seenarabic;0633 +seenfinalarabic;FEB2 +seeninitialarabic;FEB3 +seenmedialarabic;FEB4 +segol;05B6 +segol13;05B6 +segol1f;05B6 +segol2c;05B6 +segolhebrew;05B6 +segolnarrowhebrew;05B6 +segolquarterhebrew;05B6 +segoltahebrew;0592 +segolwidehebrew;05B6 +seharmenian;057D +sehiragana;305B +sekatakana;30BB +sekatakanahalfwidth;FF7E +semicolon;003B +semicolonarabic;061B +semicolonmonospace;FF1B +semicolonsmall;FE54 +semivoicedmarkkana;309C +semivoicedmarkkanahalfwidth;FF9F +sentisquare;3322 +sentosquare;3323 +seven;0037 +sevenarabic;0667 +sevenbengali;09ED +sevencircle;2466 +sevencircleinversesansserif;2790 +sevendeva;096D +seveneighths;215E +sevengujarati;0AED +sevengurmukhi;0A6D +sevenhackarabic;0667 +sevenhangzhou;3027 +sevenideographicparen;3226 +seveninferior;2087 +sevenmonospace;FF17 +sevenoldstyle;F737 +sevenparen;247A +sevenperiod;248E +sevenpersian;06F7 +sevenroman;2176 +sevensuperior;2077 +seventeencircle;2470 +seventeenparen;2484 +seventeenperiod;2498 +seventhai;0E57 +sfthyphen;00AD +shaarmenian;0577 +shabengali;09B6 +shacyrillic;0448 +shaddaarabic;0651 +shaddadammaarabic;FC61 +shaddadammatanarabic;FC5E +shaddafathaarabic;FC60 +shaddafathatanarabic;0651 064B +shaddakasraarabic;FC62 +shaddakasratanarabic;FC5F +shade;2592 +shadedark;2593 +shadelight;2591 +shademedium;2592 +shadeva;0936 +shagujarati;0AB6 +shagurmukhi;0A36 +shalshelethebrew;0593 +shbopomofo;3115 +shchacyrillic;0449 +sheenarabic;0634 +sheenfinalarabic;FEB6 +sheeninitialarabic;FEB7 +sheenmedialarabic;FEB8 +sheicoptic;03E3 +sheqel;20AA +sheqelhebrew;20AA +sheva;05B0 +sheva115;05B0 +sheva15;05B0 +sheva22;05B0 +sheva2e;05B0 +shevahebrew;05B0 +shevanarrowhebrew;05B0 +shevaquarterhebrew;05B0 +shevawidehebrew;05B0 +shhacyrillic;04BB +shimacoptic;03ED +shin;05E9 +shindagesh;FB49 +shindageshhebrew;FB49 +shindageshshindot;FB2C +shindageshshindothebrew;FB2C +shindageshsindot;FB2D +shindageshsindothebrew;FB2D +shindothebrew;05C1 +shinhebrew;05E9 +shinshindot;FB2A +shinshindothebrew;FB2A +shinsindot;FB2B +shinsindothebrew;FB2B +shook;0282 +sigma;03C3 +sigma1;03C2 +sigmafinal;03C2 +sigmalunatesymbolgreek;03F2 +sihiragana;3057 +sikatakana;30B7 +sikatakanahalfwidth;FF7C +siluqhebrew;05BD +siluqlefthebrew;05BD +similar;223C +sindothebrew;05C2 +siosacirclekorean;3274 +siosaparenkorean;3214 +sioscieuckorean;317E +sioscirclekorean;3266 +sioskiyeokkorean;317A +sioskorean;3145 +siosnieunkorean;317B +siosparenkorean;3206 +siospieupkorean;317D +siostikeutkorean;317C +six;0036 +sixarabic;0666 +sixbengali;09EC +sixcircle;2465 +sixcircleinversesansserif;278F +sixdeva;096C +sixgujarati;0AEC +sixgurmukhi;0A6C +sixhackarabic;0666 +sixhangzhou;3026 +sixideographicparen;3225 +sixinferior;2086 +sixmonospace;FF16 +sixoldstyle;F736 +sixparen;2479 +sixperiod;248D +sixpersian;06F6 +sixroman;2175 +sixsuperior;2076 +sixteencircle;246F +sixteencurrencydenominatorbengali;09F9 +sixteenparen;2483 +sixteenperiod;2497 +sixthai;0E56 +slash;002F +slashmonospace;FF0F +slong;017F +slongdotaccent;1E9B +smileface;263A +smonospace;FF53 +sofpasuqhebrew;05C3 +softhyphen;00AD +softsigncyrillic;044C +sohiragana;305D +sokatakana;30BD +sokatakanahalfwidth;FF7F +soliduslongoverlaycmb;0338 +solidusshortoverlaycmb;0337 +sorusithai;0E29 +sosalathai;0E28 +sosothai;0E0B +sosuathai;0E2A +space;0020 +spacehackarabic;0020 +spade;2660 +spadesuitblack;2660 +spadesuitwhite;2664 +sparen;24AE +squarebelowcmb;033B +squarecc;33C4 +squarecm;339D +squarediagonalcrosshatchfill;25A9 +squarehorizontalfill;25A4 +squarekg;338F +squarekm;339E +squarekmcapital;33CE +squareln;33D1 +squarelog;33D2 +squaremg;338E +squaremil;33D5 +squaremm;339C +squaremsquared;33A1 +squareorthogonalcrosshatchfill;25A6 +squareupperlefttolowerrightfill;25A7 +squareupperrighttolowerleftfill;25A8 +squareverticalfill;25A5 +squarewhitewithsmallblack;25A3 +srsquare;33DB +ssabengali;09B7 +ssadeva;0937 +ssagujarati;0AB7 +ssangcieuckorean;3149 +ssanghieuhkorean;3185 +ssangieungkorean;3180 +ssangkiyeokkorean;3132 +ssangnieunkorean;3165 +ssangpieupkorean;3143 +ssangsioskorean;3146 +ssangtikeutkorean;3138 +ssuperior;F6F2 +sterling;00A3 +sterlingmonospace;FFE1 +strokelongoverlaycmb;0336 +strokeshortoverlaycmb;0335 +subset;2282 +subsetnotequal;228A +subsetorequal;2286 +succeeds;227B +suchthat;220B +suhiragana;3059 +sukatakana;30B9 +sukatakanahalfwidth;FF7D +sukunarabic;0652 +summation;2211 +sun;263C +superset;2283 +supersetnotequal;228B +supersetorequal;2287 +svsquare;33DC +syouwaerasquare;337C +t;0074 +tabengali;09A4 +tackdown;22A4 +tackleft;22A3 +tadeva;0924 +tagujarati;0AA4 +tagurmukhi;0A24 +taharabic;0637 +tahfinalarabic;FEC2 +tahinitialarabic;FEC3 +tahiragana;305F +tahmedialarabic;FEC4 +taisyouerasquare;337D +takatakana;30BF +takatakanahalfwidth;FF80 +tatweelarabic;0640 +tau;03C4 +tav;05EA +tavdages;FB4A +tavdagesh;FB4A +tavdageshhebrew;FB4A +tavhebrew;05EA +tbar;0167 +tbopomofo;310A +tcaron;0165 +tccurl;02A8 +tcedilla;0163 +tcheharabic;0686 +tchehfinalarabic;FB7B +tchehinitialarabic;FB7C +tchehmedialarabic;FB7D +tchehmeeminitialarabic;FB7C FEE4 +tcircle;24E3 +tcircumflexbelow;1E71 +tcommaaccent;0163 +tdieresis;1E97 +tdotaccent;1E6B +tdotbelow;1E6D +tecyrillic;0442 +tedescendercyrillic;04AD +teharabic;062A +tehfinalarabic;FE96 +tehhahinitialarabic;FCA2 +tehhahisolatedarabic;FC0C +tehinitialarabic;FE97 +tehiragana;3066 +tehjeeminitialarabic;FCA1 +tehjeemisolatedarabic;FC0B +tehmarbutaarabic;0629 +tehmarbutafinalarabic;FE94 +tehmedialarabic;FE98 +tehmeeminitialarabic;FCA4 +tehmeemisolatedarabic;FC0E +tehnoonfinalarabic;FC73 +tekatakana;30C6 +tekatakanahalfwidth;FF83 +telephone;2121 +telephoneblack;260E +telishagedolahebrew;05A0 +telishaqetanahebrew;05A9 +tencircle;2469 +tenideographicparen;3229 +tenparen;247D +tenperiod;2491 +tenroman;2179 +tesh;02A7 +tet;05D8 +tetdagesh;FB38 +tetdageshhebrew;FB38 +tethebrew;05D8 +tetsecyrillic;04B5 +tevirhebrew;059B +tevirlefthebrew;059B +thabengali;09A5 +thadeva;0925 +thagujarati;0AA5 +thagurmukhi;0A25 +thalarabic;0630 +thalfinalarabic;FEAC +thanthakhatlowleftthai;F898 +thanthakhatlowrightthai;F897 +thanthakhatthai;0E4C +thanthakhatupperleftthai;F896 +theharabic;062B +thehfinalarabic;FE9A +thehinitialarabic;FE9B +thehmedialarabic;FE9C +thereexists;2203 +therefore;2234 +theta;03B8 +theta1;03D1 +thetasymbolgreek;03D1 +thieuthacirclekorean;3279 +thieuthaparenkorean;3219 +thieuthcirclekorean;326B +thieuthkorean;314C +thieuthparenkorean;320B +thirteencircle;246C +thirteenparen;2480 +thirteenperiod;2494 +thonangmonthothai;0E11 +thook;01AD +thophuthaothai;0E12 +thorn;00FE +thothahanthai;0E17 +thothanthai;0E10 +thothongthai;0E18 +thothungthai;0E16 +thousandcyrillic;0482 +thousandsseparatorarabic;066C +thousandsseparatorpersian;066C +three;0033 +threearabic;0663 +threebengali;09E9 +threecircle;2462 +threecircleinversesansserif;278C +threedeva;0969 +threeeighths;215C +threegujarati;0AE9 +threegurmukhi;0A69 +threehackarabic;0663 +threehangzhou;3023 +threeideographicparen;3222 +threeinferior;2083 +threemonospace;FF13 +threenumeratorbengali;09F6 +threeoldstyle;F733 +threeparen;2476 +threeperiod;248A +threepersian;06F3 +threequarters;00BE +threequartersemdash;F6DE +threeroman;2172 +threesuperior;00B3 +threethai;0E53 +thzsquare;3394 +tihiragana;3061 +tikatakana;30C1 +tikatakanahalfwidth;FF81 +tikeutacirclekorean;3270 +tikeutaparenkorean;3210 +tikeutcirclekorean;3262 +tikeutkorean;3137 +tikeutparenkorean;3202 +tilde;02DC +tildebelowcmb;0330 +tildecmb;0303 +tildecomb;0303 +tildedoublecmb;0360 +tildeoperator;223C +tildeoverlaycmb;0334 +tildeverticalcmb;033E +timescircle;2297 +tipehahebrew;0596 +tipehalefthebrew;0596 +tippigurmukhi;0A70 +titlocyrilliccmb;0483 +tiwnarmenian;057F +tlinebelow;1E6F +tmonospace;FF54 +toarmenian;0569 +tohiragana;3068 +tokatakana;30C8 +tokatakanahalfwidth;FF84 +tonebarextrahighmod;02E5 +tonebarextralowmod;02E9 +tonebarhighmod;02E6 +tonebarlowmod;02E8 +tonebarmidmod;02E7 +tonefive;01BD +tonesix;0185 +tonetwo;01A8 +tonos;0384 +tonsquare;3327 +topatakthai;0E0F +tortoiseshellbracketleft;3014 +tortoiseshellbracketleftsmall;FE5D +tortoiseshellbracketleftvertical;FE39 +tortoiseshellbracketright;3015 +tortoiseshellbracketrightsmall;FE5E +tortoiseshellbracketrightvertical;FE3A +totaothai;0E15 +tpalatalhook;01AB +tparen;24AF +trademark;2122 +trademarksans;F8EA +trademarkserif;F6DB +tretroflexhook;0288 +triagdn;25BC +triaglf;25C4 +triagrt;25BA +triagup;25B2 +ts;02A6 +tsadi;05E6 +tsadidagesh;FB46 +tsadidageshhebrew;FB46 +tsadihebrew;05E6 +tsecyrillic;0446 +tsere;05B5 +tsere12;05B5 +tsere1e;05B5 +tsere2b;05B5 +tserehebrew;05B5 +tserenarrowhebrew;05B5 +tserequarterhebrew;05B5 +tserewidehebrew;05B5 +tshecyrillic;045B +tsuperior;F6F3 +ttabengali;099F +ttadeva;091F +ttagujarati;0A9F +ttagurmukhi;0A1F +tteharabic;0679 +ttehfinalarabic;FB67 +ttehinitialarabic;FB68 +ttehmedialarabic;FB69 +tthabengali;09A0 +tthadeva;0920 +tthagujarati;0AA0 +tthagurmukhi;0A20 +tturned;0287 +tuhiragana;3064 +tukatakana;30C4 +tukatakanahalfwidth;FF82 +tusmallhiragana;3063 +tusmallkatakana;30C3 +tusmallkatakanahalfwidth;FF6F +twelvecircle;246B +twelveparen;247F +twelveperiod;2493 +twelveroman;217B +twentycircle;2473 +twentyhangzhou;5344 +twentyparen;2487 +twentyperiod;249B +two;0032 +twoarabic;0662 +twobengali;09E8 +twocircle;2461 +twocircleinversesansserif;278B +twodeva;0968 +twodotenleader;2025 +twodotleader;2025 +twodotleadervertical;FE30 +twogujarati;0AE8 +twogurmukhi;0A68 +twohackarabic;0662 +twohangzhou;3022 +twoideographicparen;3221 +twoinferior;2082 +twomonospace;FF12 +twonumeratorbengali;09F5 +twooldstyle;F732 +twoparen;2475 +twoperiod;2489 +twopersian;06F2 +tworoman;2171 +twostroke;01BB +twosuperior;00B2 +twothai;0E52 +twothirds;2154 +u;0075 +uacute;00FA +ubar;0289 +ubengali;0989 +ubopomofo;3128 +ubreve;016D +ucaron;01D4 +ucircle;24E4 +ucircumflex;00FB +ucircumflexbelow;1E77 +ucyrillic;0443 +udattadeva;0951 +udblacute;0171 +udblgrave;0215 +udeva;0909 +udieresis;00FC +udieresisacute;01D8 +udieresisbelow;1E73 +udieresiscaron;01DA +udieresiscyrillic;04F1 +udieresisgrave;01DC +udieresismacron;01D6 +udotbelow;1EE5 +ugrave;00F9 +ugujarati;0A89 +ugurmukhi;0A09 +uhiragana;3046 +uhookabove;1EE7 +uhorn;01B0 +uhornacute;1EE9 +uhorndotbelow;1EF1 +uhorngrave;1EEB +uhornhookabove;1EED +uhorntilde;1EEF +uhungarumlaut;0171 +uhungarumlautcyrillic;04F3 +uinvertedbreve;0217 +ukatakana;30A6 +ukatakanahalfwidth;FF73 +ukcyrillic;0479 +ukorean;315C +umacron;016B +umacroncyrillic;04EF +umacrondieresis;1E7B +umatragurmukhi;0A41 +umonospace;FF55 +underscore;005F +underscoredbl;2017 +underscoremonospace;FF3F +underscorevertical;FE33 +underscorewavy;FE4F +union;222A +universal;2200 +uogonek;0173 +uparen;24B0 +upblock;2580 +upperdothebrew;05C4 +upsilon;03C5 +upsilondieresis;03CB +upsilondieresistonos;03B0 +upsilonlatin;028A +upsilontonos;03CD +uptackbelowcmb;031D +uptackmod;02D4 +uragurmukhi;0A73 +uring;016F +ushortcyrillic;045E +usmallhiragana;3045 +usmallkatakana;30A5 +usmallkatakanahalfwidth;FF69 +ustraightcyrillic;04AF +ustraightstrokecyrillic;04B1 +utilde;0169 +utildeacute;1E79 +utildebelow;1E75 +uubengali;098A +uudeva;090A +uugujarati;0A8A +uugurmukhi;0A0A +uumatragurmukhi;0A42 +uuvowelsignbengali;09C2 +uuvowelsigndeva;0942 +uuvowelsigngujarati;0AC2 +uvowelsignbengali;09C1 +uvowelsigndeva;0941 +uvowelsigngujarati;0AC1 +v;0076 +vadeva;0935 +vagujarati;0AB5 +vagurmukhi;0A35 +vakatakana;30F7 +vav;05D5 +vavdagesh;FB35 +vavdagesh65;FB35 +vavdageshhebrew;FB35 +vavhebrew;05D5 +vavholam;FB4B +vavholamhebrew;FB4B +vavvavhebrew;05F0 +vavyodhebrew;05F1 +vcircle;24E5 +vdotbelow;1E7F +vecyrillic;0432 +veharabic;06A4 +vehfinalarabic;FB6B +vehinitialarabic;FB6C +vehmedialarabic;FB6D +vekatakana;30F9 +venus;2640 +verticalbar;007C +verticallineabovecmb;030D +verticallinebelowcmb;0329 +verticallinelowmod;02CC +verticallinemod;02C8 +vewarmenian;057E +vhook;028B +vikatakana;30F8 +viramabengali;09CD +viramadeva;094D +viramagujarati;0ACD +visargabengali;0983 +visargadeva;0903 +visargagujarati;0A83 +vmonospace;FF56 +voarmenian;0578 +voicediterationhiragana;309E +voicediterationkatakana;30FE +voicedmarkkana;309B +voicedmarkkanahalfwidth;FF9E +vokatakana;30FA +vparen;24B1 +vtilde;1E7D +vturned;028C +vuhiragana;3094 +vukatakana;30F4 +w;0077 +wacute;1E83 +waekorean;3159 +wahiragana;308F +wakatakana;30EF +wakatakanahalfwidth;FF9C +wakorean;3158 +wasmallhiragana;308E +wasmallkatakana;30EE +wattosquare;3357 +wavedash;301C +wavyunderscorevertical;FE34 +wawarabic;0648 +wawfinalarabic;FEEE +wawhamzaabovearabic;0624 +wawhamzaabovefinalarabic;FE86 +wbsquare;33DD +wcircle;24E6 +wcircumflex;0175 +wdieresis;1E85 +wdotaccent;1E87 +wdotbelow;1E89 +wehiragana;3091 +weierstrass;2118 +wekatakana;30F1 +wekorean;315E +weokorean;315D +wgrave;1E81 +whitebullet;25E6 +whitecircle;25CB +whitecircleinverse;25D9 +whitecornerbracketleft;300E +whitecornerbracketleftvertical;FE43 +whitecornerbracketright;300F +whitecornerbracketrightvertical;FE44 +whitediamond;25C7 +whitediamondcontainingblacksmalldiamond;25C8 +whitedownpointingsmalltriangle;25BF +whitedownpointingtriangle;25BD +whiteleftpointingsmalltriangle;25C3 +whiteleftpointingtriangle;25C1 +whitelenticularbracketleft;3016 +whitelenticularbracketright;3017 +whiterightpointingsmalltriangle;25B9 +whiterightpointingtriangle;25B7 +whitesmallsquare;25AB +whitesmilingface;263A +whitesquare;25A1 +whitestar;2606 +whitetelephone;260F +whitetortoiseshellbracketleft;3018 +whitetortoiseshellbracketright;3019 +whiteuppointingsmalltriangle;25B5 +whiteuppointingtriangle;25B3 +wihiragana;3090 +wikatakana;30F0 +wikorean;315F +wmonospace;FF57 +wohiragana;3092 +wokatakana;30F2 +wokatakanahalfwidth;FF66 +won;20A9 +wonmonospace;FFE6 +wowaenthai;0E27 +wparen;24B2 +wring;1E98 +wsuperior;02B7 +wturned;028D +wynn;01BF +x;0078 +xabovecmb;033D +xbopomofo;3112 +xcircle;24E7 +xdieresis;1E8D +xdotaccent;1E8B +xeharmenian;056D +xi;03BE +xmonospace;FF58 +xparen;24B3 +xsuperior;02E3 +y;0079 +yaadosquare;334E +yabengali;09AF +yacute;00FD +yadeva;092F +yaekorean;3152 +yagujarati;0AAF +yagurmukhi;0A2F +yahiragana;3084 +yakatakana;30E4 +yakatakanahalfwidth;FF94 +yakorean;3151 +yamakkanthai;0E4E +yasmallhiragana;3083 +yasmallkatakana;30E3 +yasmallkatakanahalfwidth;FF6C +yatcyrillic;0463 +ycircle;24E8 +ycircumflex;0177 +ydieresis;00FF +ydotaccent;1E8F +ydotbelow;1EF5 +yeharabic;064A +yehbarreearabic;06D2 +yehbarreefinalarabic;FBAF +yehfinalarabic;FEF2 +yehhamzaabovearabic;0626 +yehhamzaabovefinalarabic;FE8A +yehhamzaaboveinitialarabic;FE8B +yehhamzaabovemedialarabic;FE8C +yehinitialarabic;FEF3 +yehmedialarabic;FEF4 +yehmeeminitialarabic;FCDD +yehmeemisolatedarabic;FC58 +yehnoonfinalarabic;FC94 +yehthreedotsbelowarabic;06D1 +yekorean;3156 +yen;00A5 +yenmonospace;FFE5 +yeokorean;3155 +yeorinhieuhkorean;3186 +yerahbenyomohebrew;05AA +yerahbenyomolefthebrew;05AA +yericyrillic;044B +yerudieresiscyrillic;04F9 +yesieungkorean;3181 +yesieungpansioskorean;3183 +yesieungsioskorean;3182 +yetivhebrew;059A +ygrave;1EF3 +yhook;01B4 +yhookabove;1EF7 +yiarmenian;0575 +yicyrillic;0457 +yikorean;3162 +yinyang;262F +yiwnarmenian;0582 +ymonospace;FF59 +yod;05D9 +yoddagesh;FB39 +yoddageshhebrew;FB39 +yodhebrew;05D9 +yodyodhebrew;05F2 +yodyodpatahhebrew;FB1F +yohiragana;3088 +yoikorean;3189 +yokatakana;30E8 +yokatakanahalfwidth;FF96 +yokorean;315B +yosmallhiragana;3087 +yosmallkatakana;30E7 +yosmallkatakanahalfwidth;FF6E +yotgreek;03F3 +yoyaekorean;3188 +yoyakorean;3187 +yoyakthai;0E22 +yoyingthai;0E0D +yparen;24B4 +ypogegrammeni;037A +ypogegrammenigreekcmb;0345 +yr;01A6 +yring;1E99 +ysuperior;02B8 +ytilde;1EF9 +yturned;028E +yuhiragana;3086 +yuikorean;318C +yukatakana;30E6 +yukatakanahalfwidth;FF95 +yukorean;3160 +yusbigcyrillic;046B +yusbigiotifiedcyrillic;046D +yuslittlecyrillic;0467 +yuslittleiotifiedcyrillic;0469 +yusmallhiragana;3085 +yusmallkatakana;30E5 +yusmallkatakanahalfwidth;FF6D +yuyekorean;318B +yuyeokorean;318A +yyabengali;09DF +yyadeva;095F +z;007A +zaarmenian;0566 +zacute;017A +zadeva;095B +zagurmukhi;0A5B +zaharabic;0638 +zahfinalarabic;FEC6 +zahinitialarabic;FEC7 +zahiragana;3056 +zahmedialarabic;FEC8 +zainarabic;0632 +zainfinalarabic;FEB0 +zakatakana;30B6 +zaqefgadolhebrew;0595 +zaqefqatanhebrew;0594 +zarqahebrew;0598 +zayin;05D6 +zayindagesh;FB36 +zayindageshhebrew;FB36 +zayinhebrew;05D6 +zbopomofo;3117 +zcaron;017E +zcircle;24E9 +zcircumflex;1E91 +zcurl;0291 +zdot;017C +zdotaccent;017C +zdotbelow;1E93 +zecyrillic;0437 +zedescendercyrillic;0499 +zedieresiscyrillic;04DF +zehiragana;305C +zekatakana;30BC +zero;0030 +zeroarabic;0660 +zerobengali;09E6 +zerodeva;0966 +zerogujarati;0AE6 +zerogurmukhi;0A66 +zerohackarabic;0660 +zeroinferior;2080 +zeromonospace;FF10 +zerooldstyle;F730 +zeropersian;06F0 +zerosuperior;2070 +zerothai;0E50 +zerowidthjoiner;FEFF +zerowidthnonjoiner;200C +zerowidthspace;200B +zeta;03B6 +zhbopomofo;3113 +zhearmenian;056A +zhebrevecyrillic;04C2 +zhecyrillic;0436 +zhedescendercyrillic;0497 +zhedieresiscyrillic;04DD +zihiragana;3058 +zikatakana;30B8 +zinorhebrew;05AE +zlinebelow;1E95 +zmonospace;FF5A +zohiragana;305E +zokatakana;30BE +zparen;24B5 +zretroflexhook;0290 +zstroke;01B6 +zuhiragana;305A +zukatakana;30BA +#END +"; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphListForNewFont.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphListForNewFont.cs new file mode 100644 index 00000000..536cdd5e --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/AdobeGlyphListForNewFont.cs @@ -0,0 +1,713 @@ +//BSD, 2015, Adobe Systems Incorporated. +//from https://raw.githubusercontent.com/adobe-type-tools/agl-aglfn/master/aglfn.txt +//# ----------------------------------------------------------- +//# Copyright 2002, 2003, 2005, 2006, 2008, 2010, 2015 Adobe Systems +//# Incorporated. All rights reserved. +//# +//# Redistribution and use in source and binary forms, with or +//# without modification, are permitted provided that the +//# following conditions are met: +//# +//# Redistributions of source code must retain the above +//# copyright notice, this list of conditions and the following +//# disclaimer. +//# +//# Redistributions in binary form must reproduce the above +//# copyright notice, this list of conditions and the following +//# disclaimer in the documentation and/or other materials +//# provided with the distribution. +//# +//# Neither the name of Adobe Systems Incorporated nor the names +//# of its contributors may be used to endorse or promote +//# products derived from this software without specific prior +//# written permission. +//# +//# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +//# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +//# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +//# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +//# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +//# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +//# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +//# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +//# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +//# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//# ----------------------------------------------------------- +//# Name: Adobe Glyph List For New Fonts +//# Table version: 1.7 +//# Date: November 6, 2008 +//# URL: https://github.com/adobe-type-tools/agl-aglfn +//# +//# Description: +//# +//# AGLFN (Adobe Glyph List For New Fonts) provides a list of base glyph +//# names that are recommended for new fonts, which are compatible with +//# the AGL (Adobe Glyph List) Specification, and which should be used +//# as described in Section 6 of that document. AGLFN comprises the set +//# of glyph names from AGL that map via the AGL Specification rules to +//# the semantically correct UV (Unicode Value). For example, "Asmall" +//# is omitted because AGL maps this glyph name to the PUA (Private Use +//# Area) value U+F761, rather than to the UV that maps from the glyph +//# name "A." Also omitted is "ffi," because AGL maps this to the +//# Alphabetic Presentation Forms value U+FB03, rather than decomposing +//# it into the following sequence of three UVs: U+0066, U+0066, and +//# U+0069. The name "arrowvertex" has been omitted because this glyph +//# now has a real UV, and AGL is now incorrect in mapping it to the PUA +//# value U+F8E6. If you do not find an appropriate name for your glyph +//# in this list, then please refer to Section 6 of the AGL +//# Specification. +//# +//# Format: three semicolon-delimited fields: +//# (1) Standard UV or CUS UV--four uppercase hexadecimal digits +//# (2) Glyph name--upper/lowercase letters and digits +//# (3) Character names: Unicode character names for standard UVs, and +//# descriptive names for CUS UVs--uppercase letters, hyphen, and +//# space +//# +//# The records are sorted by glyph name in increasing ASCII order, +//# entries with the same glyph name are sorted in decreasing priority +//# order, the UVs and Unicode character names are provided for +//# convenience, lines starting with "#" are comments, and blank lines +//# should be ignored. +//# +//# Revision History: +//# +//# 1.7 [6 November 2008] +//# - Reverted to the original 1.4 and earlier mappings for Delta, +//# Omega, and mu. +//# - Removed mappings for "afii" names. These should now be assigned +//# "uni" names. +//# - Removed mappings for "commaaccent" names. These should now be +//# assigned "uni" names. +//# +//# 1.6 [30 January 2006] +//# - Completed work intended in 1.5. +//# +//# 1.5 [23 November 2005] +//# - Removed duplicated block at end of file. +//# - Changed mappings: +//# 2206;Delta;INCREMENT changed to 0394;Delta;GREEK CAPITAL LETTER DELTA +//# 2126;Omega;OHM SIGN changed to 03A9;Omega;GREEK CAPITAL LETTER OMEGA +//# 03BC;mu;MICRO SIGN changed to 03BC;mu;GREEK SMALL LETTER MU +//# - Corrected statement above about why "ffi" is omitted. +//# +//# 1.4 [24 September 2003] +//# - Changed version to 1.4, to avoid confusion with the AGL 1.3. +//# - Fixed spelling errors in the header. +//# - Fully removed "arrowvertex," as it is mapped only to a PUA Unicode +//# value in some fonts. +//# +//# 1.1 [17 April 2003] +//# - Renamed [Tt]cedilla back to [Tt]commaaccent. +//# +//# 1.0 [31 January 2003] +//# - Original version. +//# - Derived from the AGLv1.2 by: +//# removing the PUA area codes; +//# removing duplicate Unicode mappings; and +//# renaming "tcommaaccent" to "tcedilla" and "Tcommaaccent" to "Tcedilla" +//# +using System.Text; +using System.IO; +using System.Collections.Generic; +namespace Typography.OpenFont +{ + static class AdobeGlyphListForNewFont + { + const string aglfn = + @"# +0041; A;LATIN CAPITAL LETTER A +00C6;AE;LATIN CAPITAL LETTER AE +01FC;AEacute;LATIN CAPITAL LETTER AE WITH ACUTE +00C1;Aacute;LATIN CAPITAL LETTER A WITH ACUTE +0102; Abreve;LATIN CAPITAL LETTER A WITH BREVE +00C2;Acircumflex;LATIN CAPITAL LETTER A WITH CIRCUMFLEX +00C4;Adieresis;LATIN CAPITAL LETTER A WITH DIAERESIS +00C0;Agrave;LATIN CAPITAL LETTER A WITH GRAVE +0391; Alpha;GREEK CAPITAL LETTER ALPHA +0386; Alphatonos;GREEK CAPITAL LETTER ALPHA WITH TONOS +0100; Amacron;LATIN CAPITAL LETTER A WITH MACRON +0104; Aogonek;LATIN CAPITAL LETTER A WITH OGONEK +00C5;Aring;LATIN CAPITAL LETTER A WITH RING ABOVE +01FA;Aringacute;LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +00C3;Atilde;LATIN CAPITAL LETTER A WITH TILDE +0042; B;LATIN CAPITAL LETTER B +0392; Beta;GREEK CAPITAL LETTER BETA +0043; C;LATIN CAPITAL LETTER C +0106; Cacute;LATIN CAPITAL LETTER C WITH ACUTE +010C;Ccaron;LATIN CAPITAL LETTER C WITH CARON +00C7;Ccedilla;LATIN CAPITAL LETTER C WITH CEDILLA +0108; Ccircumflex;LATIN CAPITAL LETTER C WITH CIRCUMFLEX +010A;Cdotaccent;LATIN CAPITAL LETTER C WITH DOT ABOVE +03A7;Chi;GREEK CAPITAL LETTER CHI +0044; D;LATIN CAPITAL LETTER D +010E; Dcaron;LATIN CAPITAL LETTER D WITH CARON +0110; Dcroat;LATIN CAPITAL LETTER D WITH STROKE +2206; Delta;INCREMENT +0045;E;LATIN CAPITAL LETTER E +00C9;Eacute;LATIN CAPITAL LETTER E WITH ACUTE +0114; Ebreve;LATIN CAPITAL LETTER E WITH BREVE +011A;Ecaron;LATIN CAPITAL LETTER E WITH CARON +00CA;Ecircumflex;LATIN CAPITAL LETTER E WITH CIRCUMFLEX +00CB;Edieresis;LATIN CAPITAL LETTER E WITH DIAERESIS +0116; Edotaccent;LATIN CAPITAL LETTER E WITH DOT ABOVE +00C8;Egrave;LATIN CAPITAL LETTER E WITH GRAVE +0112; Emacron;LATIN CAPITAL LETTER E WITH MACRON +014A;Eng;LATIN CAPITAL LETTER ENG +0118; Eogonek;LATIN CAPITAL LETTER E WITH OGONEK +0395; Epsilon;GREEK CAPITAL LETTER EPSILON +0388; Epsilontonos;GREEK CAPITAL LETTER EPSILON WITH TONOS +0397; Eta;GREEK CAPITAL LETTER ETA +0389; Etatonos;GREEK CAPITAL LETTER ETA WITH TONOS +00D0; Eth;LATIN CAPITAL LETTER ETH +20AC;Euro;EURO SIGN +0046; F;LATIN CAPITAL LETTER F +0047; G;LATIN CAPITAL LETTER G +0393; Gamma;GREEK CAPITAL LETTER GAMMA +011E; Gbreve;LATIN CAPITAL LETTER G WITH BREVE +01E6; Gcaron;LATIN CAPITAL LETTER G WITH CARON +011C;Gcircumflex;LATIN CAPITAL LETTER G WITH CIRCUMFLEX +0120; Gdotaccent;LATIN CAPITAL LETTER G WITH DOT ABOVE +0048;H;LATIN CAPITAL LETTER H +25CF;H18533;BLACK CIRCLE +25AA;H18543;BLACK SMALL SQUARE +25AB;H18551;WHITE SMALL SQUARE +25A1;H22073;WHITE SQUARE +0126; Hbar;LATIN CAPITAL LETTER H WITH STROKE +0124; Hcircumflex;LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0049; I;LATIN CAPITAL LETTER I +0132; IJ;LATIN CAPITAL LIGATURE IJ +00CD;Iacute;LATIN CAPITAL LETTER I WITH ACUTE +012C;Ibreve;LATIN CAPITAL LETTER I WITH BREVE +00CE;Icircumflex;LATIN CAPITAL LETTER I WITH CIRCUMFLEX +00CF;Idieresis;LATIN CAPITAL LETTER I WITH DIAERESIS +0130; Idotaccent;LATIN CAPITAL LETTER I WITH DOT ABOVE +2111;Ifraktur;BLACK-LETTER CAPITAL I +00CC;Igrave;LATIN CAPITAL LETTER I WITH GRAVE +012A;Imacron;LATIN CAPITAL LETTER I WITH MACRON +012E; Iogonek;LATIN CAPITAL LETTER I WITH OGONEK +0399; Iota;GREEK CAPITAL LETTER IOTA +03AA;Iotadieresis;GREEK CAPITAL LETTER IOTA WITH DIALYTIKA +038A;Iotatonos;GREEK CAPITAL LETTER IOTA WITH TONOS +0128; Itilde;LATIN CAPITAL LETTER I WITH TILDE +004A;J;LATIN CAPITAL LETTER J +0134; Jcircumflex;LATIN CAPITAL LETTER J WITH CIRCUMFLEX +004B;K;LATIN CAPITAL LETTER K +039A;Kappa;GREEK CAPITAL LETTER KAPPA +004C;L;LATIN CAPITAL LETTER L +0139; Lacute;LATIN CAPITAL LETTER L WITH ACUTE +039B;Lambda;GREEK CAPITAL LETTER LAMDA +013D; Lcaron;LATIN CAPITAL LETTER L WITH CARON +013F; Ldot;LATIN CAPITAL LETTER L WITH MIDDLE DOT +0141;Lslash;LATIN CAPITAL LETTER L WITH STROKE +004D; M;LATIN CAPITAL LETTER M +039C;Mu;GREEK CAPITAL LETTER MU +004E; N;LATIN CAPITAL LETTER N +0143; Nacute;LATIN CAPITAL LETTER N WITH ACUTE +0147; Ncaron;LATIN CAPITAL LETTER N WITH CARON +00D1; Ntilde;LATIN CAPITAL LETTER N WITH TILDE +039D; Nu;GREEK CAPITAL LETTER NU +004F; O;LATIN CAPITAL LETTER O +0152; OE;LATIN CAPITAL LIGATURE OE +00D3; Oacute;LATIN CAPITAL LETTER O WITH ACUTE +014E; Obreve;LATIN CAPITAL LETTER O WITH BREVE +00D4; Ocircumflex;LATIN CAPITAL LETTER O WITH CIRCUMFLEX +00D6; Odieresis;LATIN CAPITAL LETTER O WITH DIAERESIS +00D2; Ograve;LATIN CAPITAL LETTER O WITH GRAVE +01A0;Ohorn;LATIN CAPITAL LETTER O WITH HORN +0150; Ohungarumlaut;LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +014C;Omacron;LATIN CAPITAL LETTER O WITH MACRON +2126; Omega;OHM SIGN +038F; Omegatonos;GREEK CAPITAL LETTER OMEGA WITH TONOS +039F; Omicron;GREEK CAPITAL LETTER OMICRON +038C;Omicrontonos;GREEK CAPITAL LETTER OMICRON WITH TONOS +00D8; Oslash;LATIN CAPITAL LETTER O WITH STROKE +01FE;Oslashacute;LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +00D5; Otilde;LATIN CAPITAL LETTER O WITH TILDE +0050; P;LATIN CAPITAL LETTER P +03A6;Phi;GREEK CAPITAL LETTER PHI +03A0;Pi;GREEK CAPITAL LETTER PI +03A8;Psi;GREEK CAPITAL LETTER PSI +0051; Q;LATIN CAPITAL LETTER Q +0052; R;LATIN CAPITAL LETTER R +0154; Racute;LATIN CAPITAL LETTER R WITH ACUTE +0158; Rcaron;LATIN CAPITAL LETTER R WITH CARON +211C;Rfraktur;BLACK-LETTER CAPITAL R +03A1;Rho;GREEK CAPITAL LETTER RHO +0053; S;LATIN CAPITAL LETTER S +250C;SF010000;BOX DRAWINGS LIGHT DOWN AND RIGHT +2514; SF020000;BOX DRAWINGS LIGHT UP AND RIGHT +2510; SF030000;BOX DRAWINGS LIGHT DOWN AND LEFT +2518; SF040000;BOX DRAWINGS LIGHT UP AND LEFT +253C;SF050000;BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL +252C;SF060000;BOX DRAWINGS LIGHT DOWN AND HORIZONTAL +2534; SF070000;BOX DRAWINGS LIGHT UP AND HORIZONTAL +251C;SF080000;BOX DRAWINGS LIGHT VERTICAL AND RIGHT +2524; SF090000;BOX DRAWINGS LIGHT VERTICAL AND LEFT +2500; SF100000;BOX DRAWINGS LIGHT HORIZONTAL +2502; SF110000;BOX DRAWINGS LIGHT VERTICAL +2561; SF190000;BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE +2562;SF200000;BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE +2556;SF210000;BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE +2555;SF220000;BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE +2563;SF230000;BOX DRAWINGS DOUBLE VERTICAL AND LEFT +2551; SF240000;BOX DRAWINGS DOUBLE VERTICAL +2557; SF250000;BOX DRAWINGS DOUBLE DOWN AND LEFT +255D; SF260000;BOX DRAWINGS DOUBLE UP AND LEFT +255C;SF270000;BOX DRAWINGS UP DOUBLE AND LEFT SINGLE +255B;SF280000;BOX DRAWINGS UP SINGLE AND LEFT DOUBLE +255E;SF360000;BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE +255F;SF370000;BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE +255A;SF380000;BOX DRAWINGS DOUBLE UP AND RIGHT +2554; SF390000;BOX DRAWINGS DOUBLE DOWN AND RIGHT +2569; SF400000;BOX DRAWINGS DOUBLE UP AND HORIZONTAL +2566; SF410000;BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL +2560; SF420000;BOX DRAWINGS DOUBLE VERTICAL AND RIGHT +2550; SF430000;BOX DRAWINGS DOUBLE HORIZONTAL +256C;SF440000;BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL +2567; SF450000;BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE +2568;SF460000;BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE +2564;SF470000;BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE +2565;SF480000;BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE +2559;SF490000;BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE +2558;SF500000;BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE +2552;SF510000;BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE +2553;SF520000;BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE +256B;SF530000;BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE +256A;SF540000;BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE +015A;Sacute;LATIN CAPITAL LETTER S WITH ACUTE +0160; Scaron;LATIN CAPITAL LETTER S WITH CARON +015E; Scedilla;LATIN CAPITAL LETTER S WITH CEDILLA +015C;Scircumflex;LATIN CAPITAL LETTER S WITH CIRCUMFLEX +03A3;Sigma;GREEK CAPITAL LETTER SIGMA +0054; T;LATIN CAPITAL LETTER T +03A4;Tau;GREEK CAPITAL LETTER TAU +0166; Tbar;LATIN CAPITAL LETTER T WITH STROKE +0164; Tcaron;LATIN CAPITAL LETTER T WITH CARON +0398; Theta;GREEK CAPITAL LETTER THETA +00DE;Thorn;LATIN CAPITAL LETTER THORN +0055; U;LATIN CAPITAL LETTER U +00DA;Uacute;LATIN CAPITAL LETTER U WITH ACUTE +016C;Ubreve;LATIN CAPITAL LETTER U WITH BREVE +00DB;Ucircumflex;LATIN CAPITAL LETTER U WITH CIRCUMFLEX +00DC;Udieresis;LATIN CAPITAL LETTER U WITH DIAERESIS +00D9; Ugrave;LATIN CAPITAL LETTER U WITH GRAVE +01AF;Uhorn;LATIN CAPITAL LETTER U WITH HORN +0170; Uhungarumlaut;LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +016A;Umacron;LATIN CAPITAL LETTER U WITH MACRON +0172; Uogonek;LATIN CAPITAL LETTER U WITH OGONEK +03A5;Upsilon;GREEK CAPITAL LETTER UPSILON +03D2; Upsilon1;GREEK UPSILON WITH HOOK SYMBOL +03AB;Upsilondieresis;GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +038E; Upsilontonos;GREEK CAPITAL LETTER UPSILON WITH TONOS +016E; Uring;LATIN CAPITAL LETTER U WITH RING ABOVE +0168;Utilde;LATIN CAPITAL LETTER U WITH TILDE +0056; V;LATIN CAPITAL LETTER V +0057; W;LATIN CAPITAL LETTER W +1E82; Wacute;LATIN CAPITAL LETTER W WITH ACUTE +0174; Wcircumflex;LATIN CAPITAL LETTER W WITH CIRCUMFLEX +1E84; Wdieresis;LATIN CAPITAL LETTER W WITH DIAERESIS +1E80; Wgrave;LATIN CAPITAL LETTER W WITH GRAVE +0058; X;LATIN CAPITAL LETTER X +039E; Xi;GREEK CAPITAL LETTER XI +0059; Y;LATIN CAPITAL LETTER Y +00DD;Yacute;LATIN CAPITAL LETTER Y WITH ACUTE +0176; Ycircumflex;LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0178; Ydieresis;LATIN CAPITAL LETTER Y WITH DIAERESIS +1EF2; Ygrave;LATIN CAPITAL LETTER Y WITH GRAVE +005A;Z;LATIN CAPITAL LETTER Z +0179; Zacute;LATIN CAPITAL LETTER Z WITH ACUTE +017D; Zcaron;LATIN CAPITAL LETTER Z WITH CARON +017B;Zdotaccent;LATIN CAPITAL LETTER Z WITH DOT ABOVE +0396;Zeta;GREEK CAPITAL LETTER ZETA +0061; a;LATIN SMALL LETTER A +00E1; aacute;LATIN SMALL LETTER A WITH ACUTE +0103; abreve;LATIN SMALL LETTER A WITH BREVE +00E2; acircumflex;LATIN SMALL LETTER A WITH CIRCUMFLEX +00B4;acute;ACUTE ACCENT +0301; acutecomb;COMBINING ACUTE ACCENT +00E4;adieresis;LATIN SMALL LETTER A WITH DIAERESIS +00E6; ae;LATIN SMALL LETTER AE +01FD;aeacute;LATIN SMALL LETTER AE WITH ACUTE +00E0; agrave;LATIN SMALL LETTER A WITH GRAVE +2135; aleph;ALEF SYMBOL +03B1;alpha;GREEK SMALL LETTER ALPHA +03AC;alphatonos;GREEK SMALL LETTER ALPHA WITH TONOS +0101; amacron;LATIN SMALL LETTER A WITH MACRON +0026; ampersand;AMPERSAND +2220;angle;ANGLE +2329;angleleft;LEFT-POINTING ANGLE BRACKET +232A;angleright;RIGHT-POINTING ANGLE BRACKET +0387;anoteleia;GREEK ANO TELEIA +0105;aogonek;LATIN SMALL LETTER A WITH OGONEK +2248; approxequal;ALMOST EQUAL TO +00E5;aring;LATIN SMALL LETTER A WITH RING ABOVE +01FB;aringacute;LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +2194;arrowboth;LEFT RIGHT ARROW +21D4;arrowdblboth;LEFT RIGHT DOUBLE ARROW +21D3; arrowdbldown;DOWNWARDS DOUBLE ARROW +21D0;arrowdblleft;LEFTWARDS DOUBLE ARROW +21D2;arrowdblright;RIGHTWARDS DOUBLE ARROW +21D1;arrowdblup;UPWARDS DOUBLE ARROW +2193;arrowdown;DOWNWARDS ARROW +2190; arrowleft;LEFTWARDS ARROW +2192; arrowright;RIGHTWARDS ARROW +2191; arrowup;UPWARDS ARROW +2195; arrowupdn;UP DOWN ARROW +21A8;arrowupdnbse;UP DOWN ARROW WITH BASE +005E;asciicircum;CIRCUMFLEX ACCENT +007E; asciitilde;TILDE +002A;asterisk;ASTERISK +2217;asteriskmath;ASTERISK OPERATOR +0040; at;COMMERCIAL AT +00E3; atilde;LATIN SMALL LETTER A WITH TILDE +0062; b;LATIN SMALL LETTER B +005C;backslash;REVERSE SOLIDUS +007C;bar;VERTICAL LINE +03B2;beta;GREEK SMALL LETTER BETA +2588; block;FULL BLOCK +007B;braceleft;LEFT CURLY BRACKET +007D;braceright;RIGHT CURLY BRACKET +005B;bracketleft;LEFT SQUARE BRACKET +005D;bracketright;RIGHT SQUARE BRACKET +02D8;breve;BREVE +00A6;brokenbar;BROKEN BAR +2022; bullet;BULLET +0063;c;LATIN SMALL LETTER C +0107; cacute;LATIN SMALL LETTER C WITH ACUTE +02C7;caron;CARON +21B5;carriagereturn;DOWNWARDS ARROW WITH CORNER LEFTWARDS +010D;ccaron;LATIN SMALL LETTER C WITH CARON +00E7; ccedilla;LATIN SMALL LETTER C WITH CEDILLA +0109; ccircumflex;LATIN SMALL LETTER C WITH CIRCUMFLEX +010B;cdotaccent;LATIN SMALL LETTER C WITH DOT ABOVE +00B8;cedilla;CEDILLA +00A2;cent;CENT SIGN +03C7;chi;GREEK SMALL LETTER CHI +25CB;circle;WHITE CIRCLE +2297; circlemultiply;CIRCLED TIMES +2295; circleplus;CIRCLED PLUS +02C6;circumflex;MODIFIER LETTER CIRCUMFLEX ACCENT +2663; club;BLACK CLUB SUIT +003A;colon;COLON +20A1;colonmonetary;COLON SIGN +002C;comma;COMMA +2245;congruent;APPROXIMATELY EQUAL TO +00A9;copyright;COPYRIGHT SIGN +00A4;currency;CURRENCY SIGN +0064; d;LATIN SMALL LETTER D +2020; dagger;DAGGER +2021;daggerdbl;DOUBLE DAGGER +010F; dcaron;LATIN SMALL LETTER D WITH CARON +0111; dcroat;LATIN SMALL LETTER D WITH STROKE +00B0;degree;DEGREE SIGN +03B4;delta;GREEK SMALL LETTER DELTA +2666; diamond;BLACK DIAMOND SUIT +00A8;dieresis;DIAERESIS +0385;dieresistonos;GREEK DIALYTIKA TONOS +00F7;divide;DIVISION SIGN +2593; dkshade;DARK SHADE +2584; dnblock;LOWER HALF BLOCK +0024;dollar;DOLLAR SIGN +20AB;dong;DONG SIGN +02D9; dotaccent;DOT ABOVE +0323; dotbelowcomb;COMBINING DOT BELOW +0131;dotlessi;LATIN SMALL LETTER DOTLESS I +22C5;dotmath;DOT OPERATOR +0065; e;LATIN SMALL LETTER E +00E9; eacute;LATIN SMALL LETTER E WITH ACUTE +0115; ebreve;LATIN SMALL LETTER E WITH BREVE +011B;ecaron;LATIN SMALL LETTER E WITH CARON +00EA;ecircumflex;LATIN SMALL LETTER E WITH CIRCUMFLEX +00EB;edieresis;LATIN SMALL LETTER E WITH DIAERESIS +0117; edotaccent;LATIN SMALL LETTER E WITH DOT ABOVE +00E8;egrave;LATIN SMALL LETTER E WITH GRAVE +0038; eight;DIGIT EIGHT +2208; element;ELEMENT OF +2026; ellipsis;HORIZONTAL ELLIPSIS +0113; emacron;LATIN SMALL LETTER E WITH MACRON +2014; emdash;EM DASH +2205; emptyset;EMPTY SET +2013; endash;EN DASH +014B;eng;LATIN SMALL LETTER ENG +0119; eogonek;LATIN SMALL LETTER E WITH OGONEK +03B5;epsilon;GREEK SMALL LETTER EPSILON +03AD;epsilontonos;GREEK SMALL LETTER EPSILON WITH TONOS +003D; equal;EQUALS SIGN +2261; equivalence;IDENTICAL TO +212E; estimated;ESTIMATED SYMBOL +03B7;eta;GREEK SMALL LETTER ETA +03AE;etatonos;GREEK SMALL LETTER ETA WITH TONOS +00F0; eth;LATIN SMALL LETTER ETH +0021; exclam;EXCLAMATION MARK +203C;exclamdbl;DOUBLE EXCLAMATION MARK +00A1;exclamdown;INVERTED EXCLAMATION MARK +2203;existential;THERE EXISTS +0066; f;LATIN SMALL LETTER F +2640; female;FEMALE SIGN +2012; figuredash;FIGURE DASH +25A0;filledbox;BLACK SQUARE +25AC;filledrect;BLACK RECTANGLE +0035; five;DIGIT FIVE +215D; fiveeighths;VULGAR FRACTION FIVE EIGHTHS +0192; florin;LATIN SMALL LETTER F WITH HOOK +0034; four;DIGIT FOUR +2044; fraction;FRACTION SLASH +20A3;franc;FRENCH FRANC SIGN +0067;g;LATIN SMALL LETTER G +03B3;gamma;GREEK SMALL LETTER GAMMA +011F; gbreve;LATIN SMALL LETTER G WITH BREVE +01E7; gcaron;LATIN SMALL LETTER G WITH CARON +011D; gcircumflex;LATIN SMALL LETTER G WITH CIRCUMFLEX +0121; gdotaccent;LATIN SMALL LETTER G WITH DOT ABOVE +00DF;germandbls;LATIN SMALL LETTER SHARP S +2207;gradient;NABLA +0060;grave;GRAVE ACCENT +0300; gravecomb;COMBINING GRAVE ACCENT +003E;greater;GREATER-THAN SIGN +2265; greaterequal;GREATER-THAN OR EQUAL TO +00AB;guillemotleft;LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00BB;guillemotright;RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +2039;guilsinglleft;SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A;guilsinglright;SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +0068; h;LATIN SMALL LETTER H +0127; hbar;LATIN SMALL LETTER H WITH STROKE +0125; hcircumflex;LATIN SMALL LETTER H WITH CIRCUMFLEX +2665; heart;BLACK HEART SUIT +0309;hookabovecomb;COMBINING HOOK ABOVE +2302;house;HOUSE +02DD;hungarumlaut;DOUBLE ACUTE ACCENT +002D;hyphen;HYPHEN-MINUS +0069;i;LATIN SMALL LETTER I +00ED; iacute;LATIN SMALL LETTER I WITH ACUTE +012D; ibreve;LATIN SMALL LETTER I WITH BREVE +00EE;icircumflex;LATIN SMALL LETTER I WITH CIRCUMFLEX +00EF; idieresis;LATIN SMALL LETTER I WITH DIAERESIS +00EC;igrave;LATIN SMALL LETTER I WITH GRAVE +0133; ij;LATIN SMALL LIGATURE IJ +012B;imacron;LATIN SMALL LETTER I WITH MACRON +221E; infinity;INFINITY +222B;integral;INTEGRAL +2321;integralbt;BOTTOM HALF INTEGRAL +2320;integraltp;TOP HALF INTEGRAL +2229;intersection;INTERSECTION +25D8;invbullet;INVERSE BULLET +25D9; invcircle;INVERSE WHITE CIRCLE +263B;invsmileface;BLACK SMILING FACE +012F;iogonek;LATIN SMALL LETTER I WITH OGONEK +03B9;iota;GREEK SMALL LETTER IOTA +03CA;iotadieresis;GREEK SMALL LETTER IOTA WITH DIALYTIKA +0390; iotadieresistonos;GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +03AF;iotatonos;GREEK SMALL LETTER IOTA WITH TONOS +0129; itilde;LATIN SMALL LETTER I WITH TILDE +006A;j;LATIN SMALL LETTER J +0135; jcircumflex;LATIN SMALL LETTER J WITH CIRCUMFLEX +006B;k;LATIN SMALL LETTER K +03BA;kappa;GREEK SMALL LETTER KAPPA +0138; kgreenlandic;LATIN SMALL LETTER KRA +006C;l;LATIN SMALL LETTER L +013A;lacute;LATIN SMALL LETTER L WITH ACUTE +03BB;lambda;GREEK SMALL LETTER LAMDA +013E; lcaron;LATIN SMALL LETTER L WITH CARON +0140; ldot;LATIN SMALL LETTER L WITH MIDDLE DOT +003C;less;LESS-THAN SIGN +2264; lessequal;LESS-THAN OR EQUAL TO +258C;lfblock;LEFT HALF BLOCK +20A4;lira;LIRA SIGN +2227; logicaland;LOGICAL AND +00AC;logicalnot;NOT SIGN +2228; logicalor;LOGICAL OR +017F; longs;LATIN SMALL LETTER LONG S +25CA;lozenge;LOZENGE +0142;lslash;LATIN SMALL LETTER L WITH STROKE +2591; ltshade;LIGHT SHADE +006D; m;LATIN SMALL LETTER M +00AF;macron;MACRON +2642;male;MALE SIGN +2212; minus;MINUS SIGN +2032; minute;PRIME +00B5;mu;MICRO SIGN +00D7; multiply;MULTIPLICATION SIGN +266A;musicalnote;EIGHTH NOTE +266B;musicalnotedbl;BEAMED EIGHTH NOTES +006E;n;LATIN SMALL LETTER N +0144; nacute;LATIN SMALL LETTER N WITH ACUTE +0149; napostrophe;LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +0148;ncaron;LATIN SMALL LETTER N WITH CARON +0039; nine;DIGIT NINE +2209; notelement;NOT AN ELEMENT OF +2260; notequal;NOT EQUAL TO +2284;notsubset;NOT A SUBSET OF +00F1; ntilde;LATIN SMALL LETTER N WITH TILDE +03BD;nu;GREEK SMALL LETTER NU +0023; numbersign;NUMBER SIGN +006F; o;LATIN SMALL LETTER O +00F3; oacute;LATIN SMALL LETTER O WITH ACUTE +014F; obreve;LATIN SMALL LETTER O WITH BREVE +00F4; ocircumflex;LATIN SMALL LETTER O WITH CIRCUMFLEX +00F6; odieresis;LATIN SMALL LETTER O WITH DIAERESIS +0153; oe;LATIN SMALL LIGATURE OE +02DB;ogonek;OGONEK +00F2;ograve;LATIN SMALL LETTER O WITH GRAVE +01A1;ohorn;LATIN SMALL LETTER O WITH HORN +0151; ohungarumlaut;LATIN SMALL LETTER O WITH DOUBLE ACUTE +014D;omacron;LATIN SMALL LETTER O WITH MACRON +03C9;omega;GREEK SMALL LETTER OMEGA +03D6; omega1;GREEK PI SYMBOL +03CE;omegatonos;GREEK SMALL LETTER OMEGA WITH TONOS +03BF;omicron;GREEK SMALL LETTER OMICRON +03CC;omicrontonos;GREEK SMALL LETTER OMICRON WITH TONOS +0031; one;DIGIT ONE +2024; onedotenleader;ONE DOT LEADER +215B;oneeighth;VULGAR FRACTION ONE EIGHTH +00BD;onehalf;VULGAR FRACTION ONE HALF +00BC;onequarter;VULGAR FRACTION ONE QUARTER +2153; onethird;VULGAR FRACTION ONE THIRD +25E6; openbullet;WHITE BULLET +00AA;ordfeminine;FEMININE ORDINAL INDICATOR +00BA;ordmasculine;MASCULINE ORDINAL INDICATOR +221F;orthogonal;RIGHT ANGLE +00F8; oslash;LATIN SMALL LETTER O WITH STROKE +01FF;oslashacute;LATIN SMALL LETTER O WITH STROKE AND ACUTE +00F5; otilde;LATIN SMALL LETTER O WITH TILDE +0070; p;LATIN SMALL LETTER P +00B6;paragraph;PILCROW SIGN +0028; parenleft;LEFT PARENTHESIS +0029; parenright;RIGHT PARENTHESIS +2202; partialdiff;PARTIAL DIFFERENTIAL +0025; percent;PERCENT SIGN +002E; period;FULL STOP +00B7;periodcentered;MIDDLE DOT +22A5;perpendicular;UP TACK +2030; perthousand;PER MILLE SIGN +20A7;peseta;PESETA SIGN +03C6;phi;GREEK SMALL LETTER PHI +03D5; phi1;GREEK PHI SYMBOL +03C0;pi;GREEK SMALL LETTER PI +002B;plus;PLUS SIGN +00B1;plusminus;PLUS-MINUS SIGN +211E; prescription;PRESCRIPTION TAKE +220F; product;N-ARY PRODUCT +2282; propersubset;SUBSET OF +2283; propersuperset;SUPERSET OF +221D; proportional;PROPORTIONAL TO +03C8;psi;GREEK SMALL LETTER PSI +0071; q;LATIN SMALL LETTER Q +003F; question;QUESTION MARK +00BF;questiondown;INVERTED QUESTION MARK +0022;quotedbl;QUOTATION MARK +201E; quotedblbase;DOUBLE LOW-9 QUOTATION MARK +201C;quotedblleft;LEFT DOUBLE QUOTATION MARK +201D; quotedblright;RIGHT DOUBLE QUOTATION MARK +2018; quoteleft;LEFT SINGLE QUOTATION MARK +201B;quotereversed;SINGLE HIGH-REVERSED-9 QUOTATION MARK +2019; quoteright;RIGHT SINGLE QUOTATION MARK +201A;quotesinglbase;SINGLE LOW-9 QUOTATION MARK +0027; quotesingle;APOSTROPHE +0072;r;LATIN SMALL LETTER R +0155; racute;LATIN SMALL LETTER R WITH ACUTE +221A;radical;SQUARE ROOT +0159; rcaron;LATIN SMALL LETTER R WITH CARON +2286; reflexsubset;SUBSET OF OR EQUAL TO +2287;reflexsuperset;SUPERSET OF OR EQUAL TO +00AE;registered;REGISTERED SIGN +2310; revlogicalnot;REVERSED NOT SIGN +03C1;rho;GREEK SMALL LETTER RHO +02DA;ring;RING ABOVE +2590; rtblock;RIGHT HALF BLOCK +0073;s;LATIN SMALL LETTER S +015B;sacute;LATIN SMALL LETTER S WITH ACUTE +0161; scaron;LATIN SMALL LETTER S WITH CARON +015F; scedilla;LATIN SMALL LETTER S WITH CEDILLA +015D; scircumflex;LATIN SMALL LETTER S WITH CIRCUMFLEX +2033; second;DOUBLE PRIME +00A7;section;SECTION SIGN +003B;semicolon;SEMICOLON +0037;seven;DIGIT SEVEN +215E; seveneighths;VULGAR FRACTION SEVEN EIGHTHS +2592; shade;MEDIUM SHADE +03C3;sigma;GREEK SMALL LETTER SIGMA +03C2;sigma1;GREEK SMALL LETTER FINAL SIGMA +223C;similar;TILDE OPERATOR +0036; six;DIGIT SIX +002F; slash;SOLIDUS +263A;smileface;WHITE SMILING FACE +0020;space;SPACE +2660;spade;BLACK SPADE SUIT +00A3;sterling;POUND SIGN +220B;suchthat;CONTAINS AS MEMBER +2211;summation;N-ARY SUMMATION +263C;sun;WHITE SUN WITH RAYS +0074; t;LATIN SMALL LETTER T +03C4;tau;GREEK SMALL LETTER TAU +0167; tbar;LATIN SMALL LETTER T WITH STROKE +0165; tcaron;LATIN SMALL LETTER T WITH CARON +2234; therefore;THEREFORE +03B8;theta;GREEK SMALL LETTER THETA +03D1; theta1;GREEK THETA SYMBOL +00FE;thorn;LATIN SMALL LETTER THORN +0033; three;DIGIT THREE +215C;threeeighths;VULGAR FRACTION THREE EIGHTHS +00BE;threequarters;VULGAR FRACTION THREE QUARTERS +02DC;tilde;SMALL TILDE +0303; tildecomb;COMBINING TILDE +0384; tonos;GREEK TONOS +2122; trademark;TRADE MARK SIGN +25BC;triagdn;BLACK DOWN-POINTING TRIANGLE +25C4;triaglf;BLACK LEFT-POINTING POINTER +25BA;triagrt;BLACK RIGHT-POINTING POINTER +25B2;triagup;BLACK UP-POINTING TRIANGLE +0032; two;DIGIT TWO +2025; twodotenleader;TWO DOT LEADER +2154;twothirds;VULGAR FRACTION TWO THIRDS +0075; u;LATIN SMALL LETTER U +00FA;uacute;LATIN SMALL LETTER U WITH ACUTE +016D; ubreve;LATIN SMALL LETTER U WITH BREVE +00FB;ucircumflex;LATIN SMALL LETTER U WITH CIRCUMFLEX +00FC;udieresis;LATIN SMALL LETTER U WITH DIAERESIS +00F9; ugrave;LATIN SMALL LETTER U WITH GRAVE +01B0;uhorn;LATIN SMALL LETTER U WITH HORN +0171; uhungarumlaut;LATIN SMALL LETTER U WITH DOUBLE ACUTE +016B;umacron;LATIN SMALL LETTER U WITH MACRON +005F; underscore;LOW LINE +2017; underscoredbl;DOUBLE LOW LINE +222A;union;UNION +2200;universal;FOR ALL +0173; uogonek;LATIN SMALL LETTER U WITH OGONEK +2580; upblock;UPPER HALF BLOCK +03C5;upsilon;GREEK SMALL LETTER UPSILON +03CB;upsilondieresis;GREEK SMALL LETTER UPSILON WITH DIALYTIKA +03B0;upsilondieresistonos;GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +03CD;upsilontonos;GREEK SMALL LETTER UPSILON WITH TONOS +016F; uring;LATIN SMALL LETTER U WITH RING ABOVE +0169;utilde;LATIN SMALL LETTER U WITH TILDE +0076; v;LATIN SMALL LETTER V +0077; w;LATIN SMALL LETTER W +1E83; wacute;LATIN SMALL LETTER W WITH ACUTE +0175; wcircumflex;LATIN SMALL LETTER W WITH CIRCUMFLEX +1E85; wdieresis;LATIN SMALL LETTER W WITH DIAERESIS +2118; weierstrass;SCRIPT CAPITAL P +1E81;wgrave;LATIN SMALL LETTER W WITH GRAVE +0078; x;LATIN SMALL LETTER X +03BE;xi;GREEK SMALL LETTER XI +0079; y;LATIN SMALL LETTER Y +00FD;yacute;LATIN SMALL LETTER Y WITH ACUTE +0177; ycircumflex;LATIN SMALL LETTER Y WITH CIRCUMFLEX +00FF;ydieresis;LATIN SMALL LETTER Y WITH DIAERESIS +00A5;yen;YEN SIGN +1EF3; ygrave;LATIN SMALL LETTER Y WITH GRAVE +007A;z;LATIN SMALL LETTER Z +017A;zacute;LATIN SMALL LETTER Z WITH ACUTE +017E; zcaron;LATIN SMALL LETTER Z WITH CARON +017C;zdotaccent;LATIN SMALL LETTER Z WITH DOT ABOVE +0030;zero;DIGIT ZERO +03B6;zeta;GREEK SMALL LETTER ZETA +#END +"; + + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/Languages.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/Languages.cs new file mode 100644 index 00000000..a44648d9 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/Languages.cs @@ -0,0 +1,157 @@ +//MIT, 2020-present, WinterDev +using System; +using System.Collections.Generic; +using Typography.OpenFont.Tables; + +namespace Typography.OpenFont +{ + /// + /// supported langs, designed langs + /// + public class Languages + { + /// + /// meta table's supported langs + /// + public string[] SupportedLangs { get; private set; } + /// + /// meta table's design langs + /// + public string[] DesignLangs { get; private set; } + + public ushort OS2Version { get; private set; }//for translate unicode ranges + public uint UnicodeRange1 { get; private set; } + public uint UnicodeRange2 { get; private set; } + public uint UnicodeRange3 { get; private set; } + public uint UnicodeRange4 { get; private set; } + + /// + /// actual GSUB script list + /// + public ScriptList GSUBScriptList { get; private set; } + /// + /// actual GPOS script list + /// + public ScriptList GPOSScriptList { get; private set; } + + Cmap _cmap; + + internal void Update(OS2Table os2Tabble, Meta meta, Cmap cmap, GSUB gsub, GPOS gpos) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ur + + + //This field is used to specify the Unicode blocks or ranges encompassed by the font file in 'cmap' subtables for platform 3, + //encoding ID 1 (Microsoft platform, Unicode BMP) and platform 3, + //encoding ID 10 (Microsoft platform, Unicode full repertoire). + //If a bit is set (1), then the Unicode ranges assigned to that bit are considered functional. + //If the bit is clear (0), then the range is not considered functional. + + //unicode BMP (Basic Multilingual Plane),OR plane0 (see https://unicode.org/roadmaps/bmp/) + + //Each of the bits is treated as an independent flag and the bits can be set in any combination. + //The determination of “functional” is left up to the font designer, + //although character set selection should attempt to be functional by ranges if at all possible. + + //-------------- + //Different versions of the OS/2 table were created when different Unicode versions were current, + //and the initial specification for a given version defined fewer bit assignments than for later versions. + //Some applications may not support all assignments for fonts that have earlier OS/2 versions. + + //All of the bit assignments listed above are valid for any version of the OS/2 table, + //though OS/2 versions 1 and 2 were specified with some assignments that did not correspond to well-defined Unicode ranges and + //that conflict with later assignments — see the details below. + //If a font has a version 1 or version 2 OS/2 table with one of these bits set, + //the obsolete assignment may be the intended interpretation. + //Because these assignments do not correspond to well-defined ranges, + //however, the implied character coverage is unclear. + + //Version 0: When version 0 was first specified, no bit assignments were defined. + //Some applications may ignore these fields in a version 0 OS/2 table. + + //Version 1: + //Version 1 was first specified concurrent with Unicode 1.1, + //and bit assigments were defined for bits 0 to 69 only. With fonts that have a version 1 table, + //some applications might recognize only bits 0 to 69. + + //Also, version 1 was specified with some bit assignments that did not correspond to a well-defined Unicode range: + + // Bit 8: “Greek Symbols and Coptic” (bit 7 was specified as “Basic Greek”) + // Bit 12: “Hebrew Extended” (bit 11 was specified as “Basic Hebrew”) + // Bit 14: “Arabic Extended” (bit 13 was specified as “Basic Arabic”) + // Bit 27: “Georgian Extended” (bit 26 was specified as “Basic Georgian”) + + //These assignments were discontinued as of version 2. + + //In addition, versions 1 and 2 were defined with bit 53 specified as “CJK Miscellaneous”, + //which also does not correspond to any well-defined Unicode range. + //This assignment was discontinued as of version 3. + + //Version 2: + //Version 2 was defined in OpenType 1.1, which was concurrent with Unicode 2.1. + //At that time, bit assignments were defined for bits 0 to 69 only. + //Bit assignments for version 2 were updated in OpenType 1.3, + //adding assignments for bits 70 to 83 corresponding to new blocks assigned in Unicode 2.0 and Unicode 3.0. + //With fonts that have a version 2 table, + //some applications might recognize only those bits assigned in OpenType 1.2 or OpenType 1.3. + + //Also, the specification for version 2 continued to use a problematic assignment for bit 53 — + //see details for version 1. This assignment was discontinued as of version 3. + + //Version 3: Version 3 was defined in OpenType 1.4 with assignments for bits 84 to 91 corresponding to additional + //ranges in Unicode 3.2. + //In addition, some already-assigned bits were extended to cover additional Unicode ranges for related characters; s + //ee details in the table above. + + //Version 4: Version 4 was defined in OpenType 1.5 with assignments for bit 58 and bits 92 to 122 corresponding to additional ranges in Unicode 5.1. + //Also, bits 8, 12, 14, 27 and 53 were re-assigned (see version 1 for previous assignments). + //In addition, some already-assigned bits were extended to cover additional Unicode ranges for related characters; + //see details in the table above. + + OS2Version = os2Tabble.version; + UnicodeRange1 = os2Tabble.ulUnicodeRange1; + UnicodeRange2 = os2Tabble.ulUnicodeRange2; + UnicodeRange3 = os2Tabble.ulUnicodeRange3; + UnicodeRange4 = os2Tabble.ulUnicodeRange4; + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + + + + //------- + //IMPORTANT:*** + //All available bits were exhausted as of Unicode 5.1. *** + //The bit assignments were last updated for OS/2 version 4 in OpenType 1.5. + //There are many additional ranges supported in the current version of Unicode that are not supported by these fields in the OS/2 table. + // + //See the 'dlng' and 'slng' tags in the 'meta' table for an alternate mechanism to declare + //what scripts or languages that a font can support or is designed for. + //------- + + + if (meta != null) + { + SupportedLangs = meta.SupportedLanguageTags; + DesignLangs = meta.DesignLanguageTags; + } + + //---- + //gsub and gpos contains actual script_list that are in the typeface + GSUBScriptList = gsub?.ScriptList; + GPOSScriptList = gpos?.ScriptList; + + _cmap = cmap; + } + + /// + /// contains glyph for the given unicode code point or not + /// + /// + /// + public bool ContainGlyphForUnicode(int codepoint) => (_cmap != null) ? (_cmap.GetGlyphIndex(codepoint, 0, out bool skipNextCodePoint) > 0) : false; + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/MacPostFormat1.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/MacPostFormat1.cs new file mode 100644 index 00000000..bacca7d1 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/MacPostFormat1.cs @@ -0,0 +1,333 @@ +//Apache2, 2017-present, WinterDev + + +using System.IO; +namespace Typography.OpenFont +{ + static class MacPostFormat1 + { + + + static string[] s_stdMacGlyphNames; + + public static string[] GetStdMacGlyphNames() + { + if (s_stdMacGlyphNames == null) + { + s_stdMacGlyphNames = new string[260]; + using (StringReader strReader = new StringReader(orgGlyphNames)) + { + string[] seps = new string[] { " " }; + + string line = strReader.ReadLine(); + + while (line != null) + { + line = line.Trim(); + if (line != "") + { + + string[] key_value = line.Split(seps, System.StringSplitOptions.RemoveEmptyEntries); + if (key_value.Length != 2) + { + throw new System.NotSupportedException(); + } + if (int.TryParse(key_value[0], System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out int index)) + { +#if DEBUG + if (index < 0 || index > 258) + { + + } +#endif + s_stdMacGlyphNames[index] = key_value[1].Trim(); + } + else + { + throw new System.NotSupportedException(); + } + + } + line = strReader.ReadLine(); + } + } + } + return s_stdMacGlyphNames; + } + + + //'post' Format 1 + //The order in which glyphs are placed in a font is at the convenience of the font developer To use format 1, + //a font must contain exactly the 258 glyphs in the standard Macintosh ordering. + //For such fonts, the glyph names are taken from the system. + //As a result, this format does not require a special subtable. + //The names for these 258 glyphs are, in order: + + //this is a copy from + //from https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html + + //Glyph ID Name + const string orgGlyphNames = +@" +0 .notdef +1 .null +2 nonmarkingreturn +3 space +4 exclam +5 quotedbl +6 numbersign +7 dollar +8 percent +9 ampersand +10 quotesingle +11 parenleft +12 parenright +13 asterisk +14 plus +15 comma +16 hyphen +17 period +18 slash +19 zero +20 one +21 two +22 three +23 four +24 five +25 six +26 seven +27 eight +28 nine +29 colon +30 semicolon +31 less +32 equal +33 greater +34 question +35 at +36 A +37 B +38 C +39 D +40 E +41 F +42 G +43 H +44 I +45 J +46 K +47 L +48 M +49 N +50 O +51 P +52 Q +53 R +54 S +55 T +56 U +57 V +58 W +59 X +60 Y +61 Z +62 bracketleft +63 backslash +64 bracketright +65 asciicircum +66 underscore +67 grave +68 a +69 b +70 c +71 d +72 e +73 f +74 g +75 h +76 i +77 j +78 k +79 l +80 m +81 n +82 o +83 p +84 q +85 r +86 s +87 t +88 u +89 v +90 w +91 x +92 y +93 z +94 braceleft +95 bar +96 braceright +97 asciitilde +98 Adieresis +99 Aring +100 Ccedilla +101 Eacute +102 Ntilde +103 Odieresis +104 Udieresis +105 aacute +106 agrave +107 acircumflex +108 adieresis +109 atilde +110 aring +111 ccedilla +112 eacute +113 egrave +114 ecircumflex +115 edieresis +116 iacute +117 igrave +118 icircumflex +119 idieresis +120 ntilde +121 oacute +122 ograve +123 ocircumflex +124 odieresis +125 otilde +126 uacute +127 ugrave +128 ucircumflex +129 udieresis +130 dagger +131 degree +132 cent +133 sterling +134 section +135 bullet +136 paragraph +137 germandbls +138 registered +139 copyright +140 trademark +141 acute +142 dieresis +143 notequal +144 AE +145 Oslash +146 infinity +147 plusminus +148 lessequal +149 greaterequal +150 yen +151 mu +152 partialdiff +153 summation +154 product +155 pi +156 integral +157 ordfeminine +158 ordmasculine +159 Omega +160 ae +161 oslash +162 questiondown +163 exclamdown +164 logicalnot +165 radical +166 florin +167 approxequal +168 Delta +169 guillemotleft +170 guillemotright +171 ellipsis +172 nonbreakingspace +173 Agrave +174 Atilde +175 Otilde +176 OE +177 oe +178 endash +179 emdash +180 quotedblleft +181 quotedblright +182 quoteleft +183 quoteright +184 divide +185 lozenge +186 ydieresis +187 Ydieresis +188 fraction +189 currency +190 guilsinglleft +191 guilsinglright +192 fi +193 fl +194 daggerdbl +195 periodcentered +196 quotesinglbase +197 quotedblbase +198 perthousand +199 Acircumflex +200 Ecircumflex +201 Aacute +202 Edieresis +203 Egrave +204 Iacute +205 Icircumflex +206 Idieresis +207 Igrave +208 Oacute +209 Ocircumflex +210 apple +211 Ograve +212 Uacute +213 Ucircumflex +214 Ugrave +215 dotlessi +216 circumflex +217 tilde +218 macron +219 breve +220 dotaccent +221 ring +222 cedilla +223 hungarumlaut +224 ogonek +225 caron +226 Lslash +227 lslash +228 Scaron +229 scaron +230 Zcaron +231 zcaron +232 brokenbar +233 Eth +234 eth +235 Yacute +236 yacute +237 Thorn +238 thorn +239 minus +240 multiply +241 onesuperior +242 twosuperior +243 threesuperior +244 onehalf +245 onequarter +246 threequarters +247 franc +248 Gbreve +249 gbreve +250 Idotaccent +251 Scedilla +252 scedilla +253 Cacute +254 cacute +255 Ccaron +256 ccaron +257 dcroat"; + + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/OS2_IBMFontClassParameters.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/OS2_IBMFontClassParameters.cs new file mode 100644 index 00000000..4d472464 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/AdditionalInfo/OS2_IBMFontClassParameters.cs @@ -0,0 +1,51 @@ +//Apache2, 2017-present, WinterDev +//https://www.microsoft.com/typography/otspec/ibmfc.htm + +namespace Typography.OpenFont +{ + + //This section defines the IBM Font Class + //and the IBM Font Subclass parameter values + //to be used in the classification of font designs by the font designer or supplier. + // + //This information is stored in the sFamilyClass field of a font's OS/2 table. + + + [System.Flags] + enum IBMFontClassParametersKind + { + No_Classification = 0 << 8, + // + //class id 1, OldStyle Serifs + // + Class1 = 1 << 8, + OldStyle_Serifs = Class1, + Class1_No_Classification = Class1 | 0, + Class1_IBM_Rounded_Legibility = Class1 | 1, + Class1_Garalde = Class1 | 2, + Class1_Venetian = Class1 | 3, + Class1_Modified_Venetian = Class1 | 4, + Class1_Dutch_Modern = Class1 | 5, + Class1_Dutch_Traditional = Class1 | 6, + Class1_Comtemporary = Class1 | 7, + Class1_Calligraphic = Class1 | 8, + //subclass 9-14 -> (reserved for future use) + Class1_Miscellaneous = Class1 | 15, + // + //class id 2, Transitional Serifs + // + Class2 = 2 << 8, + Class2_No_Classification = Class2 | 0, + Class2_Direct_Line = Class2 | 1, + Class2_Script = Class2 | 2, + //subclass 3-14 -> (reserved for future use) + Class2_Miscellaneous = Class2 | 15, + // + //class id 3, Modern Serifs + // + //TODO... add more... + + + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/BinaryReaderExtensions.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/BinaryReaderExtensions.cs new file mode 100644 index 00000000..50e28ad2 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/BinaryReaderExtensions.cs @@ -0,0 +1,18 @@ +using System.IO; + +namespace Typography.OpenFont; + +internal static class BinaryReaderExtensions +{ + public static ushort ReadUInt16BE(this BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(2); + return (ushort)((bytes[0] << 8) | bytes[1]); + } + + public static uint ReadUInt32BE(this BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(4); + return (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Bounds.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Bounds.cs new file mode 100644 index 00000000..c7705d69 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Bounds.cs @@ -0,0 +1,34 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +namespace Typography.OpenFont +{ + /// + /// original glyph bounds + /// + public readonly struct Bounds + { + + //TODO: will be changed to => public readonly struct Bounds + + public static readonly Bounds Zero = new Bounds(0, 0, 0, 0); + public Bounds(short xmin, short ymin, short xmax, short ymax) + { + XMin = xmin; + YMin = ymin; + XMax = xmax; + YMax = ymax; + } + + public short XMin { get; } + public short YMin { get; } + public short XMax { get; } + public short YMax { get; } +#if DEBUG + public override string ToString() + { + return "(" + XMin + "," + YMin + "," + XMax + "," + YMax + ")"; + } +#endif + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/ByteOrderSwappingBinaryReader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/ByteOrderSwappingBinaryReader.cs new file mode 100644 index 00000000..b45ef08d --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/ByteOrderSwappingBinaryReader.cs @@ -0,0 +1,70 @@ +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.IO; + +namespace Typography.OpenFont +{ + class ByteOrderSwappingBinaryReader : BinaryReader + { + //All OpenType fonts use Motorola-style byte ordering (Big Endian) + // + public ByteOrderSwappingBinaryReader(Stream input) + : base(input) + { + } + protected override void Dispose(bool disposing) + { + GC.SuppressFinalize(this); + base.Dispose(disposing); + } + // + //as original + // + //public override byte ReadByte() { return base.ReadByte(); } + // + //we override the 4 methods here + // + public override short ReadInt16() => BitConverter.ToInt16(RR(2), 8 - 2); + public override ushort ReadUInt16() => BitConverter.ToUInt16(RR(2), 8 - 2); + public override uint ReadUInt32() => BitConverter.ToUInt32(RR(4), 8 - 4); + public override ulong ReadUInt64() => BitConverter.ToUInt64(RR(8), 8 - 8); + + + //used in CFF font + public override double ReadDouble() => BitConverter.ToDouble(RR(8), 8 - 8); + //used in CFF font + public override int ReadInt32() => BitConverter.ToInt32(RR(4), 8 - 4); + + // + readonly byte[] _reusable_buffer = new byte[8]; //fix buffer size to 8 bytes + /// + /// read and reverse + /// + /// + /// + private byte[] RR(int count) + { + base.Read(_reusable_buffer, 0, count); + Array.Reverse(_reusable_buffer); + return _reusable_buffer; + } + + //we don't use these methods in our OpenFont, so => throw the exception + public override int PeekChar() { throw new NotImplementedException(); } + public override int Read() { throw new NotImplementedException(); } + public override int Read(byte[] buffer, int index, int count) => base.Read(buffer, index, count); + public override int Read(char[] buffer, int index, int count) { throw new NotImplementedException(); } + public override bool ReadBoolean() { throw new NotImplementedException(); } + public override char ReadChar() { throw new NotImplementedException(); } + public override char[] ReadChars(int count) { throw new NotImplementedException(); } + public override decimal ReadDecimal() { throw new NotImplementedException(); } + + public override long ReadInt64() { throw new NotImplementedException(); } + public override sbyte ReadSByte() { throw new NotImplementedException(); } + public override float ReadSingle() { throw new NotImplementedException(); } + public override string ReadString() { throw new NotImplementedException(); } + // + + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Geometry.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Geometry.cs new file mode 100644 index 00000000..612b4798 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Geometry.cs @@ -0,0 +1,76 @@ +//MIT, 2015, Michael Popoloski's SharpFont, +//MIT, 2016-present, WinterDev + + +using System.Numerics; +namespace Typography.OpenFont +{ + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct GlyphPointF + { + //from https://docs.microsoft.com/en-us/typography/opentype/spec/glyf + //'point' of the glyph contour. + //eg. ... In the glyf table, the position of a point ... + // ... the point is on the curve; otherwise, it is off the curve.... + + internal Vector2 P; + internal bool onCurve; + + public GlyphPointF(float x, float y, bool onCurve) + { + P = new Vector2(x, y); + this.onCurve = onCurve; + } + public GlyphPointF(Vector2 position, bool onCurve) + { + P = position; + this.onCurve = onCurve; + } + public float X => this.P.X; + public float Y => this.P.Y; + + public static GlyphPointF operator *(GlyphPointF p, float n) + { + return new GlyphPointF(p.P * n, p.onCurve); + } + + //----------------------------------------- + + internal GlyphPointF Offset(short dx, short dy) { return new GlyphPointF(new Vector2(P.X + dx, P.Y + dy), onCurve); } + + internal void ApplyScale(float scale) + { + P *= scale; + } + internal void ApplyScaleOnlyOnXAxis(float scale) + { + P = new Vector2(P.X * scale, P.Y); + } + + internal void UpdateX(float x) + { + this.P.X = x; + } + internal void UpdateY(float y) + { + this.P.Y = y; + } + internal void OffsetY(float dy) + { + this.P.Y += dy; + } + internal void OffsetX(float dx) + { + this.P.X += dx; + } +#if DEBUG + internal bool dbugIsEqualsWith(GlyphPointF another) + { + return this.P == another.P && this.onCurve == another.onCurve; + } + public override string ToString() { return P.ToString() + " " + onCurve.ToString(); } +#endif + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Glyph.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Glyph.cs new file mode 100644 index 00000000..d45ddce2 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Glyph.cs @@ -0,0 +1,359 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.Text; +namespace Typography.OpenFont +{ + + public class Glyph + { + + /// + /// glyph info has only essential layout detail (this is our extension) + /// + readonly bool _onlyLayoutEssMode; + bool _hasOrgAdvWidth; //FOUND in all mode + + internal Glyph( + GlyphPointF[] glyphPoints, + ushort[] contourEndPoints, + Bounds bounds, + byte[] glyphInstructions, + ushort index) + { + //create from TTF + +#if DEBUG + this.dbugId = s_debugTotalId++; + if (this.dbugId == 444) + { + + } +#endif + this.GlyphPoints = glyphPoints; + EndPoints = contourEndPoints; + Bounds = bounds; + GlyphInstructions = glyphInstructions; + GlyphIndex = index; + + } + + public ushort GlyphIndex { get; } //FOUND in all mode + public Bounds Bounds { get; internal set; } //FOUND in all mode + public ushort OriginalAdvanceWidth { get; private set; } //FOUND in all mode + internal ushort BitmapGlyphAdvanceWidth { get; set; } //FOUND in all mode + + //TrueTypeFont + public ushort[] EndPoints { get; private set; } //NULL in _onlyLayoutEssMode + public GlyphPointF[] GlyphPoints { get; private set; } //NULL in _onlyLayoutEssMode + internal byte[] GlyphInstructions { get; set; } //NULL in _onlyLayoutEssMode + public bool HasGlyphInstructions => this.GlyphInstructions != null; //FALSE n _onlyLayoutEssMode + + // + public GlyphClassKind GlyphClass { get; internal set; } //FOUND in all mode + internal ushort MarkClassDef { get; set; } //FOUND in all mode + + public short MinX => Bounds.XMin; + public short MaxX => Bounds.XMax; + public short MinY => Bounds.YMin; + public short MaxY => Bounds.YMax; + + + public static bool HasOriginalAdvancedWidth(Glyph glyph) => glyph._hasOrgAdvWidth; + public static void SetOriginalAdvancedWidth(Glyph glyph, ushort advW) + { + glyph.OriginalAdvanceWidth = advW; + glyph._hasOrgAdvWidth = true; + } + + /// + /// TrueType outline, offset glyph points + /// + /// + /// + /// + internal static void TtfOffsetXY(Glyph glyph, short dx, short dy) + { + + //change data on current glyph + GlyphPointF[] glyphPoints = glyph.GlyphPoints; + for (int i = glyphPoints.Length - 1; i >= 0; --i) + { + glyphPoints[i] = glyphPoints[i].Offset(dx, dy); + } + //------------------------- + Bounds orgBounds = glyph.Bounds; + glyph.Bounds = new Bounds( + (short)(orgBounds.XMin + dx), + (short)(orgBounds.YMin + dy), + (short)(orgBounds.XMax + dx), + (short)(orgBounds.YMax + dy)); + + } + + /// + ///TrueType outline, transform normal + /// + /// + /// + /// + /// + /// + internal static void TtfTransformWith2x2Matrix(Glyph glyph, float m00, float m01, float m10, float m11) + { + + //http://stackoverflow.com/questions/13188156/whats-the-different-between-vector2-transform-and-vector2-transformnormal-i + //http://www.technologicalutopia.com/sourcecode/xnageometry/vector2.cs.htm + + //change data on current glyph + float new_xmin = 0; + float new_ymin = 0; + float new_xmax = 0; + float new_ymax = 0; + + GlyphPointF[] glyphPoints = glyph.GlyphPoints; + for (int i = 0; i < glyphPoints.Length; ++i) + { + GlyphPointF p = glyphPoints[i]; + float x = p.P.X; + float y = p.P.Y; + + float newX, newY; + //please note that this is transform normal*** + glyphPoints[i] = new GlyphPointF( + newX = (float)Math.Round((x * m00) + (y * m10)), + newY = (float)Math.Round((x * m01) + (y * m11)), + p.onCurve); + + //short newX = xs[i] = (short)Math.Round((x * m00) + (y * m10)); + //short newY = ys[i] = (short)Math.Round((x * m01) + (y * m11)); + //------ + if (newX < new_xmin) + { + new_xmin = newX; + } + if (newX > new_xmax) + { + new_xmax = newX; + } + //------ + if (newY < new_ymin) + { + new_ymin = newY; + } + if (newY > new_ymax) + { + new_ymax = newY; + } + } + //TODO: review here + glyph.Bounds = new Bounds( + (short)new_xmin, (short)new_ymin, + (short)new_xmax, (short)new_ymax); + } + + /// + /// TrueType outline glyph clone + /// + /// + /// + /// + internal static Glyph TtfOutlineGlyphClone(Glyph original, ushort newGlyphIndex) + { + //for true type instruction glyph*** + return new Glyph( + Utils.CloneArray(original.GlyphPoints), + Utils.CloneArray(original.EndPoints), + original.Bounds, + original.GlyphInstructions != null ? Utils.CloneArray(original.GlyphInstructions) : null, + newGlyphIndex); + } + + /// + /// append data from src to dest, dest data will changed*** + /// + /// + /// + internal static void TtfAppendGlyph(Glyph dest, Glyph src) + { + int org_dest_len = dest.EndPoints.Length; +#if DEBUG + int src_contour_count = src.EndPoints.Length; +#endif + if (org_dest_len == 0) + { + //org is empty glyph + + dest.GlyphPoints = Utils.ConcatArray(dest.GlyphPoints, src.GlyphPoints); + dest.EndPoints = Utils.ConcatArray(dest.EndPoints, src.EndPoints); + + } + else + { + ushort org_last_point = (ushort)(dest.EndPoints[org_dest_len - 1] + 1); //since start at 0 + + dest.GlyphPoints = Utils.ConcatArray(dest.GlyphPoints, src.GlyphPoints); + dest.EndPoints = Utils.ConcatArray(dest.EndPoints, src.EndPoints); + //offset latest append contour end points + int newlen = dest.EndPoints.Length; + for (int i = org_dest_len; i < newlen; ++i) + { + dest.EndPoints[i] += (ushort)org_last_point; + } + } + + + + //calculate new bounds + Bounds destBound = dest.Bounds; + Bounds srcBound = src.Bounds; + short newXmin = (short)Math.Min(destBound.XMin, srcBound.XMin); + short newYMin = (short)Math.Min(destBound.YMin, srcBound.YMin); + short newXMax = (short)Math.Max(destBound.XMax, srcBound.XMax); + short newYMax = (short)Math.Max(destBound.YMax, srcBound.YMax); + + dest.Bounds = new Bounds(newXmin, newYMin, newXMax, newYMax); + } + +#if DEBUG + public readonly int dbugId; + static int s_debugTotalId; + public override string ToString() + { + var stbuilder = new StringBuilder(); + if (IsCffGlyph) + { + stbuilder.Append("cff"); + stbuilder.Append(",index=" + GlyphIndex); + stbuilder.Append(",name=" + _cff1GlyphData.Name); + } + else + { + stbuilder.Append("ttf"); + stbuilder.Append(",index=" + GlyphIndex); + stbuilder.Append(",class=" + GlyphClass.ToString()); + if (MarkClassDef != 0) + { + stbuilder.Append(",mark_class=" + MarkClassDef); + } + } + return stbuilder.ToString(); + } +#endif + + //-------------------- + + //cff + + internal readonly CFF.Cff1GlyphData _cff1GlyphData; //NULL in _onlyLayoutEssMode + + internal Glyph(CFF.Cff1GlyphData cff1Glyph, ushort glyphIndex) + { +#if DEBUG + this.dbugId = s_debugTotalId++; + cff1Glyph.dbugGlyphIndex = glyphIndex; +#endif + //create from CFF + _cff1GlyphData = cff1Glyph; + this.GlyphIndex = glyphIndex; + } + + public bool IsCffGlyph => _cff1GlyphData != null; + public CFF.Cff1GlyphData GetCff1GlyphData() => _cff1GlyphData; + + //TODO: review here again + public MathGlyphs.MathGlyphInfo MathGlyphInfo { get; internal set; } //FOUND in all mode (if font has this data) + + uint _streamLen; //FOUND in all mode (if font has this data) + ushort _imgFormat; //FOUND in all mode (if font has this data) + internal Glyph(ushort glyphIndex, uint streamOffset, uint streamLen, ushort imgFormat) + { + //_bmpGlyphSource = bmpGlyphSource; + BitmapStreamOffset = streamOffset; + _streamLen = streamLen; + _imgFormat = imgFormat; + this.GlyphIndex = glyphIndex; + } + internal uint BitmapStreamOffset { get; private set; } + internal uint BitmapFormat => _imgFormat; + + private Glyph(ushort glyphIndex) + { + //for Clone_NO_BuildingInstructions() + _onlyLayoutEssMode = true; + GlyphIndex = glyphIndex; + } + + internal static void CopyExistingGlyphInfo(Glyph src, Glyph dst) + { + dst.Bounds = src.Bounds; + dst._hasOrgAdvWidth = src._hasOrgAdvWidth; + dst.OriginalAdvanceWidth = src.OriginalAdvanceWidth; + dst.BitmapGlyphAdvanceWidth = src.BitmapGlyphAdvanceWidth; + dst.GlyphClass = src.GlyphClass; + dst.MarkClassDef = src.MarkClassDef; + + //ttf: NO EndPoints, GlyphPoints, HasGlyphInstructions + + //cff: NO _cff1GlyphData + + //math-font: + dst.MathGlyphInfo = src.MathGlyphInfo; + dst.BitmapStreamOffset = src.BitmapStreamOffset; + dst._streamLen = src._streamLen; + dst._imgFormat = src._imgFormat; + } + + internal static Glyph Clone_NO_BuildingInstructions(Glyph src) + { + //a new glyph has only detail about glyph layout + //NO information about glyph building instructions + //1. if src if ttf + //2. if src is cff + //3. if src is svg + //4. if src is bitmap + + Glyph newclone = new Glyph(src.GlyphIndex); + CopyExistingGlyphInfo(src, newclone); + return newclone; + } + } + + + //https://docs.microsoft.com/en-us/typography/opentype/spec/gdef + public enum GlyphClassKind : byte + { + //1 Base glyph (single character, spacing glyph) + //2 Ligature glyph (multiple character, spacing glyph) + //3 Mark glyph (non-spacing combining glyph) + //4 Component glyph (part of single character, spacing glyph) + // + // The font developer does not have to classify every glyph in the font, + //but any glyph not assigned a class value falls into Class zero (0). + //For instance, class values might be useful for the Arabic glyphs in a font, but not for the Latin glyphs. + //Then the GlyphClassDef table will list only Arabic glyphs, and-by default-the Latin glyphs will be assigned to Class 0. + //Component glyphs can be put together to generate ligatures. + //A ligature can be generated by creating a glyph in the font that references the component glyphs, + //or outputting the component glyphs in the desired sequence. + //Component glyphs are not used in defining any GSUB or GPOS formats. + // + Zero = 0,//class0, classZero + /// + /// Base glyph (single character, spacing glyph) + /// + Base, + /// + /// Ligature glyph (multiple character, spacing glyph) + /// + Ligature, + /// + /// Mark glyph (non-spacing combining glyph) + /// + Mark, + /// + /// Component glyph (part of single character, spacing glyph) + /// + Component + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/IGlyphTranslator.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/IGlyphTranslator.cs new file mode 100644 index 00000000..67f5ff2f --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/IGlyphTranslator.cs @@ -0,0 +1,466 @@ +//MIT, 2016-present, WinterDev +//MIT, 2015, Michael Popoloski +//FTL, 3-clauses BSD, FreeType project +//----------------------------------------------------- + +using System; +using System.Numerics; + +namespace Typography.OpenFont +{ + //https://en.wikipedia.org/wiki/B%C3%A9zier_curve + //-------------------- + //Line, has 2 points.. + // (x0,y0) begin point + // (x1,y1) end point + //-------------------- + //Curve3 (Quadratic Bézier curves), has 3 points + // (x0,y0) begin point + // (x1,y1) 1st control point + // (x2,y2) end point + //-------------------- + //Curve4 (Cubic Bézier curves), has 4 points + // (x0,y0) begin point + // (x1,y1) 1st control point + // (x2,y2) 2nd control point + // (x3,y3) end point + //-------------------- + //please note that TrueType font + //compose of Quadractic Bezier Curve *** + //--------------------- + public interface IGlyphTranslator + { + /// + /// begin read a glyph + /// + /// + void BeginRead(int contourCount); + /// + /// end read a glyph + /// + void EndRead(); + /// + /// set CURRENT pen position to (x0,y0) And set the position as latest MOVETO position + /// + /// + /// + void MoveTo(float x0, float y0); + /// + /// add line,begin from CURRENT pen position to (x1,y1) then set (x1,y1) as CURRENT pen position + /// + /// end point x + /// end point y + void LineTo(float x1, float y1); + /// + /// add Quadratic Bézier curve,begin from CURRENT pen pos, to (x2,y2), then set (x2,y2) as CURRENT pen pos + /// + /// x of 1st control point + /// y of 1st control point + /// end point x + /// end point y + void Curve3(float x1, float y1, float x2, float y2); + /// + /// add Cubic Bézier curve,begin from CURRENT pen pos, to (x3,y3), then set (x3,y3) as CURRENT pen pos + /// + /// x of 1st control point + /// y of 1st control point + /// x of 2nd control point + /// y of 2dn control point + /// end point x + /// end point y + void Curve4(float x1, float y1, float x2, float y2, float x3, float y3); + + /// + /// close current contour, create line from CURRENT pen position to latest MOVETO position + /// + void CloseContour(); + } + + public static class IGlyphReaderExtensions + { + //for TrueType Font + public static void Read(this IGlyphTranslator tx, GlyphPointF[] glyphPoints, ushort[] contourEndPoints, float scale = 1) + { + if (glyphPoints == null || contourEndPoints == null) + { + return;//? + } + + int startContour = 0; + int cpoint_index = 0;//current point index + + int todoContourCount = contourEndPoints.Length; + //----------------------------------- + //1. start read data from a glyph + tx.BeginRead(todoContourCount); + //----------------------------------- + float latest_moveto_x = 0; + float latest_moveto_y = 0; + int curveControlPointCount = 0; // 1 curve control point => Quadratic, 2 curve control points => Cubic + + + while (todoContourCount > 0) + { + //reset + curveControlPointCount = 0; + + //foreach contour... + //next contour will begin at... + int nextCntBeginAtIndex = contourEndPoints[startContour] + 1; + + //reset ... + + bool has_c_begin = false; //see below [A] + Vector2 c_begin = new Vector2(); //special point if the glyph starts with 'off-curve' control point + Vector2 c1 = new Vector2(); //control point of quadratic curve + //------------------------------------------------------------------- + bool offCurveMode = false; + bool foundFirstOnCurvePoint = false; + bool startWithOffCurve = false; + int cnt_point_count = 0; + //------------------------------------------------------------------- + //[A] + //first point may start with 'ON CURVE" or 'OFF-CURVE' + //1. if first point is 'ON-CURVE' => we just set moveto command to it + // + //2. if first point is 'OFF-CURVE' => we store it into c_begin and set has_c_begin= true + // the c_begin will be use when we close the contour + // + // + //eg. glyph '2' in Century font starts with 'OFF-CURVE' point, and ends with 'OFF-CURVE' + //------------------------------------------------------------------- + +#if DEBUG + int dbug_cmdcount = 0; +#endif + for (; cpoint_index < nextCntBeginAtIndex; ++cpoint_index) + { + +#if DEBUG + dbug_cmdcount++; + +#endif + //for each point in this contour + + //point p is an on-curve point (on outline). (not curve control point) + //possible ways.. + //1. if we are in curve mode, then p is end point + // we must decide which curve to create (Curve3 or Curve4) + // easy, ... + // if curveControlPointCount == 1 , then create Curve3 + // else curveControlPointCount ==2 , then create Curve4 + //2. if we are NOT in curve mode, + // if p is first point then set this to x0,y0 + // else then p is end point of a line. + + GlyphPointF p = glyphPoints[cpoint_index]; + cnt_point_count++; + + float p_x = p.X * scale; + float p_y = p.Y * scale; + + //int vtag = (int)flags[cpoint_index] & 0x1; + //bool has_dropout = (((vtag >> 2) & 0x1) != 0); + //int dropoutMode = vtag >> 3; + + + if (p.onCurve) + { + //------------------------------------------------------------------- + //[B] + //point p is an 'on-curve' point (on outline). + //(not curve control point)*** + //the point touch the outline. + + //possible ways.. + //1. if we are in offCurveMode, then p is a curve end point. + // we must decide which curve to create (Curve3 or Curve4) + // easy, ... + // if curveControlPointCount == 1 , then create Curve3 + // else curveControlPointCount ==2 , then create Curve4 (BUT SHOULD NOT BE FOUND IN TRUE TYPEFONT'( + //2. if we are NOT in offCurveMode, + // if p is first point then set this to =>moveto(x0,y0) + // else then p is end point of a line => lineto(x1,y1) + //------------------------------------------------------------------- + + if (offCurveMode) + { + //as describe above [B.1] ,... + + switch (curveControlPointCount) + { + case 1: + + tx.Curve3( + c1.X, c1.Y, + p_x, p_y); + + break; + default: + + //for TrueType font + //we should not be here? + throw new OpenFontNotSupportedException(); + + } + + //reset curve control point count + curveControlPointCount = 0; + //we touch the curve, set offCurveMode= false + offCurveMode = false; + } + else + { + // p is ON CURVE, but now we are in OFF-CURVE mode. + // + //as describe above [B.2] ,... + if (!foundFirstOnCurvePoint) + { + //special treament for first point + foundFirstOnCurvePoint = true; + switch (curveControlPointCount) + { + case 0: + //describe above, see [A.1] + tx.MoveTo(latest_moveto_x = p_x, latest_moveto_y = p_y); + break; + case 1: + + //describe above, see [A.2] + c_begin = c1; + has_c_begin = true; + //since c1 is off curve + //we skip the c1 for and use it when we close the curve + + tx.MoveTo(latest_moveto_x = p_x, latest_moveto_y = p_y); + curveControlPointCount--; + break; + default: throw new OpenFontNotSupportedException(); + } + } + else + { + tx.LineTo(p_x, p_y); + } + + //if (has_dropout) + //{ + // //printf("[%d] on,dropoutMode=%d: %d,y:%d \n", mm, dropoutMode, vpoint.x, vpoint.y); + //} + //else + //{ + // //printf("[%d] on,x: %d,y:%d \n", mm, vpoint.x, vpoint.y); + //} + } + } + else + { + + + //p is OFF-CURVE point (this is curve control point) + // + if (cnt_point_count == 1) + { + //1st point + startWithOffCurve = true; + } + switch (curveControlPointCount) + { + + case 0: + c1 = new Vector2(p_x, p_y); + if (foundFirstOnCurvePoint) + { + //this point is curve control point*** + //so set curve mode = true + //check number if existing curve control + offCurveMode = true; + } + else + { + //describe above, see [A.2] + } + break; + case 1: + { + if (!foundFirstOnCurvePoint) + { + Vector2 mid2 = GetMidPoint(c1, p_x, p_y); + //---------- + //2. generate curve3 *** + c_begin = c1; + has_c_begin = true; + + + tx.MoveTo(latest_moveto_x = mid2.X, latest_moveto_y = mid2.Y); + + offCurveMode = true; + foundFirstOnCurvePoint = true; + + c1 = new Vector2(p_x, p_y); + continue; + + } + + //we already have previous 1st control point (c1) + //------------------------------------- + //please note that TrueType font + //compose of Quadractic Bezier Curve (Curve3) *** + //------------------------------------- + //in this case, this is NOT Cubic, + //this is 2 CONNECTED Quadractic Bezier Curves*** + // + //we must create 'end point' of the first curve + //and set it as 'begin point of the second curve. + // + //this is done by ... + //1. calculate mid point between c1 and the latest point (p_x,p_y) + Vector2 mid = GetMidPoint(c1, p_x, p_y); + //---------- + //2. generate curve3 *** + tx.Curve3( + c1.X, c1.Y, + mid.X, mid.Y); + //------------------------ + //3. so curve control point number is reduce by 1*** + curveControlPointCount--; + //------------------------ + //4. and set (p_x,p_y) as 1st control point for the new curve + c1 = new Vector2(p_x, p_y); + offCurveMode = true; + // + //printf("[%d] bzc2nd, x: %d,y:%d \n", mm, vpoint.x, vpoint.y); + } + break; + default: + throw new OpenFontNotSupportedException(); + } + //count + curveControlPointCount++; + } + } + //-------- + //when finish, + //ensure that the contour is closed. + + if (offCurveMode) + { + switch (curveControlPointCount) + { + case 0: break; + case 1: + { + + if (has_c_begin) + { + Vector2 mid = GetMidPoint(c1, c_begin.X, c_begin.Y); + //---------- + //2. generate curve3 *** + tx.Curve3( + c1.X, c1.Y, + mid.X, mid.Y); + //------------------------ + //3. so curve control point number is reduce by 1*** + curveControlPointCount--; + //------------------------ + tx.Curve3( + c_begin.X, c_begin.Y, + latest_moveto_x, latest_moveto_y); + } + else + { + tx.Curve3( + c1.X, c1.Y, + latest_moveto_x, latest_moveto_y); + } + } + break; + default: + //for TrueType font + //we should not be here? + throw new OpenFontNotSupportedException(); + + } + } + else + { + //end with touch curve + //but if this start with off curve + //then we must close it properly + if (startWithOffCurve) + { + //start with off-curve and end with touch curve + tx.Curve3( + c_begin.X, c_begin.Y, + latest_moveto_x, latest_moveto_y); + } + } + + //-------- + tx.CloseContour(); //*** + startContour++; + //-------- + todoContourCount--; + //-------- + } + //finish + tx.EndRead(); + } + + static Vector2 GetMidPoint(Vector2 v0, float x1, float y1) + { + //mid point between v0 and (x1,y1) + return new Vector2( + ((v0.X + x1) / 2f), + ((v0.Y + y1) / 2f)); + } + //----------- + //for CFF1 + public static void Read(this IGlyphTranslator tx, CFF.Cff1Font cff1Font, CFF.Cff1GlyphData glyphData, float scale = 1) + { + CFF.CffEvaluationEngine evalEngine = new CFF.CffEvaluationEngine(); + evalEngine.Run(tx, glyphData.GlyphInstructions, scale); + } + } + + //static int s_POINTS_PER_INCH = 72; //default value, + //static int s_PIXELS_PER_INCH = 96; //default value + //public static float ConvEmSizeInPointsToPixels(float emsizeInPoint) + //{ + // return (int)(((float)emsizeInPoint / (float)s_POINTS_PER_INCH) * (float)s_PIXELS_PER_INCH); + //} + + ////from http://www.w3schools.com/tags/ref_pxtoemconversion.asp + ////set default + //// 16px = 1 em + ////------------------- + ////1. conv font design unit to em + //// em = designUnit / unit_per_Em + ////2. conv font design unit to pixels + //// float scale = (float)(size * resolution) / (pointsPerInch * _typeface.UnitsPerEm); + + ////------------------- + ////https://www.microsoft.com/typography/otspec/TTCH01.htm + ////Converting FUnits to pixels + ////Values in the em square are converted to values in the pixel coordinate system by multiplying them by a scale. This scale is: + ////pointSize * resolution / ( 72 points per inch * units_per_em ) + ////where pointSize is the size at which the glyph is to be displayed, and resolution is the resolution of the output device. + ////The 72 in the denominator reflects the number of points per inch. + ////For example, assume that a glyph feature is 550 FUnits in length on a 72 dpi screen at 18 point. + ////There are 2048 units per em. The following calculation reveals that the feature is 4.83 pixels long. + ////550 * 18 * 72 / ( 72 * 2048 ) = 4.83 + ////------------------- + //public static float ConvFUnitToPixels(ushort reqFUnit, float fontSizeInPoint, ushort unitPerEm) + //{ + // //reqFUnit * scale + // return reqFUnit * GetFUnitToPixelsScale(fontSizeInPoint, unitPerEm); + //} + //public static float GetFUnitToPixelsScale(float fontSizeInPoint, ushort unitPerEm) + //{ + // //reqFUnit * scale + // return ((fontSizeInPoint * s_PIXELS_PER_INCH) / (s_POINTS_PER_INCH * unitPerEm)); + //} + + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/OpenFontReader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/OpenFontReader.cs new file mode 100644 index 00000000..73b94a86 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/OpenFontReader.cs @@ -0,0 +1,629 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.IO; + +using Typography.OpenFont.Extensions; +using Typography.OpenFont.Tables; +using Typography.OpenFont.Trimmable; + +namespace Typography.OpenFont +{ + [Flags] + public enum ReadFlags + { + Full = 0, + Name = 1, + Metrics = 1 << 2, + AdvancedLayout = 1 << 3, + Variation = 1 << 4 + } + + public class OpenFontException : Exception + { + public OpenFontException() { } + public OpenFontException(string msg) : base(msg) { } + } + public class OpenFontNotSupportedException : Exception + { + public OpenFontNotSupportedException() { } + public OpenFontNotSupportedException(string msg) : base(msg) { } + } + + static class KnownFontFiles + { + public static bool IsTtcf(ushort u1, ushort u2) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header + //check if 1st 4 bytes is ttcf or not + return (((u1 >> 8) & 0xff) == (byte)'t') && + (((u1) & 0xff) == (byte)'t') && + (((u2 >> 8) & 0xff) == (byte)'c') && + (((u2) & 0xff) == (byte)'f'); + } + public static bool IsWoff(ushort u1, ushort u2) + { + return (((u1 >> 8) & 0xff) == (byte)'w') && //0x77 + (((u1) & 0xff) == (byte)'O') && //0x4f + (((u2 >> 8) & 0xff) == (byte)'F') && // 0x46 + (((u2) & 0xff) == (byte)'F'); //0x46 + } + public static bool IsWoff2(ushort u1, ushort u2) + { + return (((u1 >> 8) & 0xff) == (byte)'w') &&//0x77 + (((u1) & 0xff) == (byte)'O') && //0x4f + (((u2 >> 8) & 0xff) == (byte)'F') && //0x46 + (((u2) & 0xff) == (byte)'2'); //0x32 + } + } + + public class OpenFontReader + { + + class FontCollectionHeader + { + public ushort majorVersion; + public ushort minorVersion; + public uint numFonts; + public int[] offsetTables; + // + //if version 2 + public uint dsigTag; + public uint dsigLength; + public uint dsigOffset; + } + + static string BuildTtcfName(PreviewFontInfo[] members) + { + //THIS IS MY CONVENTION for TrueType collection font name + //you can change this to fit your need. + + var stbuilder = new System.Text.StringBuilder(); + stbuilder.Append("TTCF: " + members.Length); + var uniqueNames = new System.Collections.Generic.Dictionary(); + for (uint i = 0; i < members.Length; ++i) + { + PreviewFontInfo member = members[i]; + if (!uniqueNames.ContainsKey(member.Name)) + { + uniqueNames.Add(member.Name, true); + stbuilder.Append("," + member.Name); + } + } + return stbuilder.ToString(); + } + + + /// + /// read only name entry + /// + /// + /// + public PreviewFontInfo ReadPreview(Stream stream) + { + //var little = BitConverter.IsLittleEndian; + using (var input = new ByteOrderSwappingBinaryReader(stream)) + { + ushort majorVersion = input.ReadUInt16(); + ushort minorVersion = input.ReadUInt16(); + + if (KnownFontFiles.IsTtcf(majorVersion, minorVersion)) + { + //this font stream is 'The Font Collection' + FontCollectionHeader ttcHeader = ReadTTCHeader(input); + PreviewFontInfo[] members = new PreviewFontInfo[ttcHeader.numFonts]; + for (uint i = 0; i < ttcHeader.numFonts; ++i) + { + input.BaseStream.Seek(ttcHeader.offsetTables[i], SeekOrigin.Begin); + PreviewFontInfo member = members[i] = ReadActualFontPreview(input, false); + member.ActualStreamOffset = ttcHeader.offsetTables[i]; + } + return new PreviewFontInfo(BuildTtcfName(members), members); + } + else if (KnownFontFiles.IsWoff(majorVersion, minorVersion)) + { + //check if we enable woff or not + WebFont.WoffReader woffReader = new WebFont.WoffReader(); + input.BaseStream.Position = 0; + return woffReader.ReadPreview(input); + } + else if (KnownFontFiles.IsWoff2(majorVersion, minorVersion)) + { + //check if we enable woff2 or not + WebFont.Woff2Reader woffReader = new WebFont.Woff2Reader(); + input.BaseStream.Position = 0; + return woffReader.ReadPreview(input); + } + else + { + return ReadActualFontPreview(input, true);//skip version data (majorVersion, minorVersion) + } + } + } + FontCollectionHeader ReadTTCHeader(ByteOrderSwappingBinaryReader input) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header + //TTC Header Version 1.0: + //Type Name Description + //TAG ttcTag Font Collection ID string: 'ttcf' (used for fonts with CFF or CFF2 outlines as well as TrueType outlines) + //uint16 majorVersion Major version of the TTC Header, = 1. + //uint16 minorVersion Minor version of the TTC Header, = 0. + //uint32 numFonts Number of fonts in TTC + //Offset32 offsetTable[numFonts] Array of offsets to the OffsetTable for each font from the beginning of the file + + //TTC Header Version 2.0: + //Type Name Description + //TAG ttcTag Font Collection ID string: 'ttcf' + //uint16 majorVersion Major version of the TTC Header, = 2. + //uint16 minorVersion Minor version of the TTC Header, = 0. + //uint32 numFonts Number of fonts in TTC + //Offset32 offsetTable[numFonts] Array of offsets to the OffsetTable for each font from the beginning of the file + //uint32 dsigTag Tag indicating that a DSIG table exists, 0x44534947 ('DSIG') (null if no signature) + //uint32 dsigLength The length (in bytes) of the DSIG table (null if no signature) + //uint32 dsigOffset The offset (in bytes) of the DSIG table from the beginning of the TTC file (null if no signature) + + var ttcHeader = new FontCollectionHeader(); + + ttcHeader.majorVersion = input.ReadUInt16(); + ttcHeader.minorVersion = input.ReadUInt16(); + uint numFonts = input.ReadUInt32(); + int[] offsetTables = new int[numFonts]; + for (uint i = 0; i < numFonts; ++i) + { + offsetTables[i] = input.ReadInt32(); + } + + ttcHeader.numFonts = numFonts; + ttcHeader.offsetTables = offsetTables; + // + if (ttcHeader.majorVersion == 2) + { + ttcHeader.dsigTag = input.ReadUInt32(); + ttcHeader.dsigLength = input.ReadUInt32(); + ttcHeader.dsigOffset = input.ReadUInt32(); + + if (ttcHeader.dsigTag == 0x44534947) + { + //Tag indicating that a DSIG table exists + //TODO: goto DSIG add read signature + } + } + return ttcHeader; + } + PreviewFontInfo ReadActualFontPreview(ByteOrderSwappingBinaryReader input, bool skipVersionData) + { + if (!skipVersionData) + { + ushort majorVersion = input.ReadUInt16(); + ushort minorVersion = input.ReadUInt16(); + } + + ushort tableCount = input.ReadUInt16(); + ushort searchRange = input.ReadUInt16(); + ushort entrySelector = input.ReadUInt16(); + ushort rangeShift = input.ReadUInt16(); + + var tables = new TableEntryCollection(); + for (int i = 0; i < tableCount; i++) + { + tables.AddEntry(new UnreadTableEntry(ReadTableHeader(input))); + } + return ReadPreviewFontInfo(tables, input); + } + public Typeface Read(Stream stream, int streamStartOffset = 0, ReadFlags readFlags = ReadFlags.Full) + { + Typeface typeface = new Typeface(); + if (Read(typeface, null, stream, streamStartOffset, readFlags)) + { + return typeface; + } + return null; + } + + internal bool Read(Typeface typeface, RestoreTicket ticket, Stream stream, int streamStartOffset = 0, ReadFlags readFlags = ReadFlags.Full) + { + + if (streamStartOffset > 0) + { + //eg. for ttc + stream.Seek(streamStartOffset, SeekOrigin.Begin); + } + using (var input = new ByteOrderSwappingBinaryReader(stream)) + { + ushort majorVersion = input.ReadUInt16(); + ushort minorVersion = input.ReadUInt16(); + + if (KnownFontFiles.IsTtcf(majorVersion, minorVersion)) + { + //this font stream is 'The Font Collection' + //To read content of ttc=> one must specific the offset + //so use read preview first=> you will know that what are inside the ttc. + return false; + } + else if (KnownFontFiles.IsWoff(majorVersion, minorVersion)) + { + //check if we enable woff or not + WebFont.WoffReader woffReader = new WebFont.WoffReader(); + input.BaseStream.Position = 0; + return woffReader.Read(typeface, input, ticket); + } + else if (KnownFontFiles.IsWoff2(majorVersion, minorVersion)) + { + //check if we enable woff2 or not + WebFont.Woff2Reader woffReader = new WebFont.Woff2Reader(); + input.BaseStream.Position = 0; + return woffReader.Read(typeface, input, ticket); + } + //----------------------------------------------------------------- + + + ushort tableCount = input.ReadUInt16(); + ushort searchRange = input.ReadUInt16(); + ushort entrySelector = input.ReadUInt16(); + ushort rangeShift = input.ReadUInt16(); + //------------------------------------------------------------------ + var tables = new TableEntryCollection(); + for (int i = 0; i < tableCount; i++) + { + tables.AddEntry(new UnreadTableEntry(ReadTableHeader(input))); + } + //------------------------------------------------------------------ + + return ReadTableEntryCollection(typeface, ticket, tables, input); + } + } + + + readonly struct EntriesReaderHelper + { + //a simple helper class + readonly TableEntryCollection _tables; + readonly BinaryReader _input; + public EntriesReaderHelper(TableEntryCollection tables, BinaryReader input) + { + _tables = tables; + _input = input; + } + /// + /// read table if exists + /// + /// + /// + /// + /// + /// + /// + public T Read(T resultTable) where T : TableEntry + { + if (_tables.TryGetTable(resultTable.Name, out TableEntry found)) + { + //found table name + //check if we have read this table or not + if (found is UnreadTableEntry unreadTableEntry) + { + //set header before actal read + resultTable.Header = found.Header; + if (unreadTableEntry.HasCustomContentReader) + { + resultTable = unreadTableEntry.CreateTableEntry(_input, resultTable); + } + else + { + resultTable.LoadDataFrom(_input); + } + //then replace + _tables.ReplaceTable(resultTable); + return resultTable; + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("this table is already loaded"); + if (!(found is T)) + { + throw new OpenFontNotSupportedException(); + } +#endif + return found as T; + } + } + //not found + return null; + } + } + + + internal PreviewFontInfo ReadPreviewFontInfo(TableEntryCollection tables, BinaryReader input) + { + var rd = new EntriesReaderHelper(tables, input); + + NameEntry nameEntry = rd.Read(new NameEntry()); + OS2Table os2Table = rd.Read(new OS2Table()); + //for preview, read ONLY script list from gsub and gpos (set OnlyScriptList). + Meta metaTable = rd.Read(new Meta()); + + GSUB gsub = rd.Read(new GSUB() { OnlyScriptList = true }); + GPOS gpos = rd.Read(new GPOS() { OnlyScriptList = true }); + Cmap cmap = rd.Read(new Cmap()); + //gsub and gpos contains actual script_list that are in the typeface + + Languages langs = new Languages(); + langs.Update(os2Table, metaTable, cmap, gsub, gpos); + + return new PreviewFontInfo( + nameEntry, + os2Table, + langs); + } + + bool ReadTableEntryCollectionOnRestoreMode(Typeface typeface, RestoreTicket ticket, TableEntryCollection tables, BinaryReader input) + { + //RESTORE MODE + //check header matches + + if (!typeface.IsTrimmed() || + !typeface.CompareOriginalHeadersWithNewlyLoadOne(tables.CloneTableHeaders())) + { + return false; + } + + + var rd = new EntriesReaderHelper(tables, input); + //PART 1: basic information + //.. + //------------------------------------ + //PART 2: glyphs detail + //2.1 True type font + GlyphLocations glyphLocations = ticket.HasTtf ? rd.Read(new GlyphLocations(typeface.MaxProfile.GlyphCount, typeface.Head.WideGlyphLocations)) : null; + Glyf glyf = ticket.HasTtf ? rd.Read(new Glyf(glyphLocations)) : null; + + typeface.GaspTable = ticket.GaspTable ? rd.Read(new Gasp()) : null; + + typeface.SetColorAndPalTable( + ticket.COLRTable ? rd.Read(new COLR()) : null, + ticket.CPALTable ? rd.Read(new CPAL()) : null); + + + //2.2 Cff font + CFFTable cff = ticket.HasCff ? rd.Read(new CFFTable()) : null; + + bool isPostScriptOutline = false; + bool isBitmapFont = false; + + if (glyf == null) + { + //check if this is cff table ? + if (cff == null) + { + //check cbdt/cblc ? + if (ticket.HasBitmapSource) + { + //reload only CBDT (embeded bitmap) + CBDT cbdt = rd.Read(new CBDT()); + typeface._bitmapFontGlyphSource.LoadCBDT(cbdt); + //just clone existing glyph + isBitmapFont = true; + } + else + { + //? + throw new OpenFontNotSupportedException(); + } + } + else + { + isPostScriptOutline = true; + typeface.SetCffFontSet(cff.Cff1FontSet); + } + } + else + { + typeface.SetTtfGlyphs(glyf.Glyphs); + } + + if (!isPostScriptOutline && !isBitmapFont) + { + //for true-type font outline + FpgmTable fpgmTable = rd.Read(new FpgmTable()); + //control values table + CvtTable cvtTable = rd.Read(new CvtTable()); + PrepTable propProgramTable = rd.Read(new PrepTable()); + + typeface.ControlValues = cvtTable?._controlValues; + typeface.FpgmProgramBuffer = fpgmTable?._programBuffer; + typeface.PrepProgramBuffer = propProgramTable?._programBuffer; + } + + if (ticket.HasSvg) + { + typeface.SetSvgTable(rd.Read(new SvgTable())); + } + + +#if DEBUG + //test + //int found = typeface.GetGlyphIndexByName("Uacute"); + if (typeface.IsCffFont) + { + //optional??? + typeface.UpdateAllCffGlyphBounds(); + } +#endif + typeface._typefaceTrimMode = TrimMode.Restored; + return true; + } + internal bool ReadTableEntryCollection(Typeface typeface, RestoreTicket ticket, TableEntryCollection tables, BinaryReader input) + { + if (ticket != null) + { + return ReadTableEntryCollectionOnRestoreMode(typeface, ticket, tables, input); + } + + typeface.SetTableEntryCollection(tables.CloneTableHeaders()); + + var rd = new EntriesReaderHelper(tables, input); + //PART 1: basic information + OS2Table os2Table = rd.Read(new OS2Table()); + Meta meta = rd.Read(new Meta()); + NameEntry nameEntry = rd.Read(new NameEntry()); + Head head = rd.Read(new Head()); + MaxProfile maxProfile = rd.Read(new MaxProfile()); + HorizontalHeader horizontalHeader = rd.Read(new HorizontalHeader()); + HorizontalMetrics horizontalMetrics = rd.Read(new HorizontalMetrics(horizontalHeader.NumberOfHMetrics, maxProfile.GlyphCount)); + VerticalHeader vhea = rd.Read(new VerticalHeader()); + if (vhea != null) + { + VerticalMetrics vmtx = rd.Read(new VerticalMetrics(vhea.NumOfLongVerMetrics)); + } + + OS2FsSelection os2Select = new OS2FsSelection(os2Table.fsSelection); + typeface._useTypographicMertic = os2Select.USE_TYPO_METRICS; + + Cmap cmaps = rd.Read(new Cmap()); + VerticalDeviceMetrics vdmx = rd.Read(new VerticalDeviceMetrics()); + Kern kern = rd.Read(new Kern()); + //------------------------------------ + //PART 2: glyphs detail + //2.1 True type font + + GlyphLocations glyphLocations = rd.Read(new GlyphLocations(maxProfile.GlyphCount, head.WideGlyphLocations)); + Glyf glyf = rd.Read(new Glyf(glyphLocations)); + Gasp gaspTable = rd.Read(new Gasp()); + COLR colr = rd.Read(new COLR()); + CPAL cpal = rd.Read(new CPAL()); + + //2.2 Cff font + PostTable postTable = rd.Read(new PostTable()); + CFFTable cff = rd.Read(new CFFTable()); + + //additional math table (if available) + MathTable mathtable = rd.Read(new MathTable()); + //------------------------------------ + + //PART 3: advanced typography + GDEF gdef = rd.Read(new GDEF()); + GSUB gsub = rd.Read(new GSUB()); + GPOS gpos = rd.Read(new GPOS()); + BASE baseTable = rd.Read(new BASE()); + JSTF jstf = rd.Read(new JSTF()); + + STAT stat = rd.Read(new STAT()); + if (stat != null) + { + //variable font + FVar fvar = rd.Read(new FVar()); + if (fvar != null) + { + GVar gvar = rd.Read(new GVar()); + CVar cvar = rd.Read(new CVar()); + HVar hvar = rd.Read(new HVar()); + MVar mvar = rd.Read(new MVar()); + AVar avar = rd.Read(new AVar()); + } + } + + bool isPostScriptOutline = false; + bool isBitmapFont = false; + + typeface.SetBasicTypefaceTables(os2Table, nameEntry, head, horizontalMetrics); + if (glyf == null) + { + //check if this is cff table ? + if (cff == null) + { + //check cbdt/cblc ? + CBLC cblcTable = rd.Read(new CBLC()); + if (cblcTable != null) + { + CBDT cbdtTable = rd.Read(new CBDT()); + //read cbdt + //bitmap font + + BitmapFontGlyphSource bmpFontGlyphSrc = new BitmapFontGlyphSource(cblcTable); + bmpFontGlyphSrc.LoadCBDT(cbdtTable); + Glyph[] glyphs = bmpFontGlyphSrc.BuildGlyphList(); + typeface.SetBitmapGlyphs(glyphs, bmpFontGlyphSrc); + isBitmapFont = true; + } + else + { + //TODO: + EBLC fontBmpTable = rd.Read(new EBLC()); + throw new OpenFontNotSupportedException(); + } + } + else + { + isPostScriptOutline = true; + typeface.SetCffFontSet(cff.Cff1FontSet); + } + } + else + { + typeface.SetTtfGlyphs(glyf.Glyphs); + } + + //---------------------------- + typeface.CmapTable = cmaps; + typeface.KernTable = kern; + typeface.MaxProfile = maxProfile; + typeface.HheaTable = horizontalHeader; + //---------------------------- + typeface.GaspTable = gaspTable; + + if (!isPostScriptOutline && !isBitmapFont) + { + //for true-type font outline + FpgmTable fpgmTable = rd.Read(new FpgmTable()); + //control values table + CvtTable cvtTable = rd.Read(new CvtTable()); + PrepTable propProgramTable = rd.Read(new PrepTable()); + + typeface.ControlValues = cvtTable?._controlValues; + typeface.FpgmProgramBuffer = fpgmTable?._programBuffer; + typeface.PrepProgramBuffer = propProgramTable?._programBuffer; + } + + //------------------------- + typeface.LoadOpenFontLayoutInfo( + gdef, + gsub, + gpos, + baseTable, + colr, + cpal); + //------------ + + typeface.SetSvgTable(rd.Read(new SvgTable())); + typeface.PostTable = postTable; + + if (mathtable != null) + { + MathGlyphLoader.LoadMathGlyph(typeface, mathtable); + } +#if DEBUG + //test + //int found = typeface.GetGlyphIndexByName("Uacute"); + if (typeface.IsCffFont) + { + //optional + typeface.UpdateAllCffGlyphBounds(); + } +#endif + typeface.UpdateLangs(meta); + typeface.UpdateFrequentlyUsedValues(); + return true; + } + + static TableHeader ReadTableHeader(BinaryReader input) + { + return new TableHeader( + input.ReadUInt32(), + input.ReadUInt32(), + input.ReadUInt32(), + input.ReadUInt32()); + } + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/PreviewFontInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/PreviewFontInfo.cs new file mode 100644 index 00000000..b364821c --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/PreviewFontInfo.cs @@ -0,0 +1,75 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + + +using Typography.OpenFont.Tables; + +namespace Typography.OpenFont +{ + public class PreviewFontInfo + { + public readonly string Name; + public readonly string SubFamilyName; + public readonly Extensions.TranslatedOS2FontStyle OS2TranslatedStyle; + public readonly Extensions.OS2FsSelection OS2FsSelection; + + readonly PreviewFontInfo[] _ttcfMembers; + + public Languages Languages { get; } + public NameEntry NameEntry { get; } + public OS2Table OS2Table { get; } + + internal PreviewFontInfo( + NameEntry nameEntry, + OS2Table os2Table, + Languages langs) + { + NameEntry = nameEntry; + OS2Table = os2Table; + Languages = langs; + + Name = nameEntry.FontName; + SubFamilyName = nameEntry.FontSubFamily; + OS2TranslatedStyle = Extensions.TypefaceExtensions.TranslateOS2FontStyle(os2Table); + OS2FsSelection = Extensions.TypefaceExtensions.TranslateOS2FsSelection(os2Table); + } + internal PreviewFontInfo(string fontName, PreviewFontInfo[] ttcfMembers) + { + Name = fontName; + SubFamilyName = ""; + _ttcfMembers = ttcfMembers; + Languages = new Languages(); + } + + public string TypographicFamilyName => (NameEntry?.TypographicFamilyName) ?? string.Empty; + public string TypographicSubFamilyName => (NameEntry?.TypographyicSubfamilyName) ?? string.Empty; + public string PostScriptName => (NameEntry?.PostScriptName) ?? string.Empty; + public string UniqueFontIden => (NameEntry?.UniqueFontIden) ?? string.Empty; + public string VersionString => (NameEntry?.VersionString) ?? string.Empty; + public ushort WeightClass => (OS2Table != null) ? OS2Table.usWeightClass : ushort.MinValue; + public ushort WidthClass => (OS2Table != null) ? OS2Table.usWidthClass : ushort.MinValue; + + + public int ActualStreamOffset { get; internal set; } + public bool IsWebFont { get; internal set; } + public bool IsFontCollection => _ttcfMembers != null; + /// + /// get font collection's member count + /// + public int MemberCount => _ttcfMembers.Length; + /// + /// get font collection's member + /// + /// + /// + public PreviewFontInfo GetMember(int index) => _ttcfMembers[index]; +#if DEBUG + public override string ToString() + { + return (IsFontCollection) ? Name : Name + ", " + SubFamilyName + ", " + OS2TranslatedStyle; + } +#endif + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/README.MD b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/README.MD new file mode 100644 index 00000000..2c53ba83 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/README.MD @@ -0,0 +1,23 @@ +Typography.OpenFont +--- + +This module is about reading +1. Open Font Format (.ttf, .otf, .ttc, .otc) +2. Web Open Font Format (.woff, .woff2) + + +References: + +ISO/IEC 14496-22:2015, Open Font Format, http://www.iso.org/iso/home/store/catalogue_ics/catalogue_detail_ics.htm?csnumber=66391 + +Microsoft OpenType Specification, https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx + +The Compact Font Format Specification, Technical Note #5176, Version 1, 4 December 2003, https://typekit.files.wordpress.com/2010/12/5176.cff.pdf + +The Type2 Charstring Format, Technical Note #517716 March 2000, Adobe, https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + +Type1 Font Format, Adobe, https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/T1_SPEC.pdf + +WOFF 1.0, https://www.w3.org/TR/2012/REC-WOFF-20121213/ + +WOFF 2.0, https://www.w3.org/TR/WOFF2/ \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/AttachmentListTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/AttachmentListTable.cs new file mode 100644 index 00000000..786a7d2e --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/AttachmentListTable.cs @@ -0,0 +1,63 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + //Attachment List Table + + //The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps. + + //The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, + //a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). + //The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index. + //AttachList table + //Type Name Description + //Offset16 Coverage Offset to Coverage table - from beginning of AttachList table + //unint16 GlyphCount Number of glyphs with attachment points + //Offset16 AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order + + //An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and + //an array of contour indices of those points (PointIndex), listed in increasing numerical order. + + //Example 3 at the end of the chapter demonstrates an AttachList table that defines attachment points for two glyphs. + //AttachPoint table + //Type Name Description + //uint16 PointCount Number of attachment points on this glyph + //uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order + + class AttachmentListTable + { + AttachPoint[] _attachPoints; + public CoverageTable CoverageTable { get; private set; } + public static AttachmentListTable CreateFrom(BinaryReader reader, long beginAt) + { + AttachmentListTable attachmentListTable = new AttachmentListTable(); + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + ushort coverageOffset = reader.ReadUInt16(); + ushort glyphCount = reader.ReadUInt16(); + ushort[] attachPointOffsets = Utils.ReadUInt16Array(reader, glyphCount); + //----------------------- + attachmentListTable.CoverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset); + attachmentListTable._attachPoints = new AttachPoint[glyphCount]; + for (int i = 0; i < glyphCount; ++i) + { + reader.BaseStream.Seek(beginAt + attachPointOffsets[i], SeekOrigin.Begin); + ushort pointCount = reader.ReadUInt16(); + attachmentListTable._attachPoints[i] = new AttachPoint() + { + pointIndices = Utils.ReadUInt16Array(reader, pointCount) + }; + } + + return attachmentListTable; + } + struct AttachPoint + { + public ushort[] pointIndices; + } + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/Base.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/Base.cs new file mode 100644 index 00000000..0478eb69 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/Base.cs @@ -0,0 +1,637 @@ +//Apache2, 2016-present, WinterDev +//https://docs.microsoft.com/en-us/typography/opentype/spec/base +//BASE - Baseline Table +//The Baseline table (BASE) provides information used to align glyphs of different scripts and sizes in a line of text, +//whether the glyphs are in the same font or in different fonts. +//To improve text layout, the Baseline table also provides minimum (min) and maximum (max) glyph extent values for each script, +//language system, or feature in a font. + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + public class BASE : TableEntry + { + public const string _N = "BASE"; + public override string Name => _N; + + public AxisTable _horizontalAxis; + public AxisTable _verticalAxis; + + protected override void ReadContentFrom(BinaryReader reader) + { + //BASE Header + + //The BASE table begins with a header that starts with a version number. + //Two versions are defined. + //Version 1.0 contains offsets to horizontal and vertical Axis tables(HorizAxis and VertAxis). + //Version 1.1 also includes an offset to an Item Variation Store table. + + //Each Axis table stores all baseline information and min / max extents for one layout direction. + //The HorizAxis table contains Y values for horizontal text layout; + //the VertAxis table contains X values for vertical text layout. + + + // A font may supply information for both layout directions. + //If a font has values for only one text direction, + //the Axis table offset value for the other direction will be set to NULL. + + //The optional Item Variation Store table is used in variable fonts to provide variation data + //for BASE metric values within the Axis tables. + + + // BASE Header, Version 1.0 + //Type Name Description + //uint16 majorVersion Major version of the BASE table, = 1 + //uint16 minorVersion Minor version of the BASE table, = 0 + //Offset16 horizAxisOffset Offset to horizontal Axis table, from beginning of BASE table(may be NULL) + //Offset16 vertAxisOffset Offset to vertical Axis table, from beginning of BASE table(may be NULL) + + //BASE Header, Version 1.1 + //Type Name Description + //uint16 majorVersion Major version of the BASE table, = 1 + //uint16 minorVersion Minor version of the BASE table, = 1 + //Offset16 horizAxisOffset Offset to horizontal Axis table, from beginning of BASE table(may be NULL) + //Offset16 vertAxisOffset Offset to vertical Axis table, from beginning of BASE table(may be NULL) + //Offset32 itemVarStoreOffset Offset to Item Variation Store table, from beginning of BASE table(may be null) + + + long tableStartAt = reader.BaseStream.Position; + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort horizAxisOffset = reader.ReadUInt16(); + ushort vertAxisOffset = reader.ReadUInt16(); + uint itemVarStoreOffset = 0; + if (minorVersion == 1) + { + itemVarStoreOffset = reader.ReadUInt32(); + } + + //Axis Tables: HorizAxis and VertAxis + + if (horizAxisOffset > 0) + { + reader.BaseStream.Position = tableStartAt + horizAxisOffset; + _horizontalAxis = ReadAxisTable(reader); + _horizontalAxis.isVerticalAxis = false; + } + if (vertAxisOffset > 0) + { + reader.BaseStream.Position = tableStartAt + vertAxisOffset; + _verticalAxis = ReadAxisTable(reader); + _verticalAxis.isVerticalAxis = true; + } + if (itemVarStoreOffset > 0) + { + //TODO + } + + } + + public class AxisTable + { + public bool isVerticalAxis; //false = horizontal , true= verical axis + public string[] baseTagList; + public BaseScript[] baseScripts; +#if DEBUG + public override string ToString() + { + return isVerticalAxis ? "vertical_axis" : "horizontal_axis"; + } +#endif + } + + static AxisTable ReadAxisTable(BinaryReader reader) + { + //An Axis table is used to render scripts either horizontally or vertically. + //It consists of offsets, measured from the beginning of the Axis table, + //to a BaseTagList and a BaseScriptList: + + //The BaseScriptList enumerates all scripts rendered in the text layout direction. + //The BaseTagList enumerates all baselines used to render the scripts in the text layout direction. + //If no baseline data is available for a text direction, + //the offset to the corresponding BaseTagList may be set to NULL. + + //Axis Table + //Type Name Description + //Offset16 baseTagListOffset Offset to BaseTagList table, from beginning of Axis table(may be NULL) + //Offset16 baseScriptListOffset Offset to BaseScriptList table, from beginning of Axis table + + long axisTableStartAt = reader.BaseStream.Position; + + ushort baseTagListOffset = reader.ReadUInt16(); + ushort baseScriptListOffset = reader.ReadUInt16(); + + AxisTable axisTable = new AxisTable(); + if (baseTagListOffset > 0) + { + reader.BaseStream.Position = axisTableStartAt + baseTagListOffset; + axisTable.baseTagList = ReadBaseTagList(reader); + } + if (baseScriptListOffset > 0) + { + reader.BaseStream.Position = axisTableStartAt + baseScriptListOffset; + axisTable.baseScripts = ReadBaseScriptList(reader); + } + return axisTable; + } + + static string ConvertToTagString(byte[] iden_tag_bytes) + { + return new string(new char[] { + (char)iden_tag_bytes[0], + (char)iden_tag_bytes[1], + (char)iden_tag_bytes[2], + (char)iden_tag_bytes[3]}); + } + static string[] ReadBaseTagList(BinaryReader reader) + { + //BaseTagList Table + + //The BaseTagList table identifies the baselines for all scripts in the font that are rendered in the same text direction. + //Each baseline is identified with a 4-byte baseline tag. + //The Baseline Tags section of the OpenType Layout Tag Registry lists currently registered baseline tags. + //The BaseTagList can define any number of baselines, and it may include baseline tags for scripts supported in other fonts. + + //Each script in the BaseScriptList table must designate one of these BaseTagList baselines as its default, + //which the OpenType Layout Services use to align all glyphs in the script. + //Even though the BaseScriptList and the BaseTagList are defined independently of one another, + //the BaseTagList typically includes a tag for each different default baseline needed to render the scripts in the layout direction. + //If some scripts use the same default baseline, the BaseTagList needs to list the common baseline tag only once. + + //The BaseTagList table consists of an array of baseline identification tags (baselineTags), + //listed alphabetically, and a count of the total number of baseline Tags in the array (baseTagCount). + + //BaseTagList table + //Type Name Description + //uint16 baseTagCount Number of baseline identification tags in this text direction — may be zero (0) + //Tag baselineTags[baseTagCount] Array of 4-byte baseline identification tags — must be in alphabetical order + + //see baseline tag => https://docs.microsoft.com/en-us/typography/opentype/spec/baselinetags + + + //Baseline Tag + //'hang' + //Baseline for HorizAxis: The hanging baseline.This is the horizontal line from which syllables seem to hang in Tibetan and other similar scripts. + //Baseline for VertAxis: The hanging baseline, (which now appears vertical) for Tibetan(or some other similar script) characters rotated 90 degrees clockwise, + // for vertical writing mode. + //------ + //'icfb' + //HorizAxis: Ideographic character face bottom edge. (See Ideographic Character Face below for usage.) + //VertAxis: Ideographic character face left edge. (See Ideographic Character Face below for usage.) + //-------- + //'icft' + //HorizAxis: Ideographic character face top edge. (See Ideographic Character Face below for usage.) + //VertAxis: Ideographic character face right edge. (See Ideographic Character Face below for usage.) + //----- + //'ideo' + //HorizAxis: Ideographic em-box bottom edge. (See Ideographic Em-Box below for usage.) + //VertAxis: Ideographic em-box left edge. If this tag is present in the VertAxis, the value must be set to 0. (See Ideographic Em - Box below for usage.) + + //------- + //'idtp' + //HorizAxis: Ideographic em-box top edge baseline. (See Ideographic Em - Box below for usage.) + //VertAxis: Ideographic em-box right edge baseline. + // If this tag is present in the VertAxis, + // the value is strongly recommended to be set to head.unitsPerEm. (See Ideographic Em - Box below for usage.) + //------- + //'math' + //HorizAxis: The baseline about which mathematical characters are centered. + //VertAxis: The baseline about which mathematical characters, when rotated 90 degrees clockwise for vertical writing mode, are centered. + + //------- + //'romn' + //HorizAxis: The baseline used by alphabetic scripts such as Latin, Cyrillic and Greek. + //VertAxis: The alphabetic baseline for characters rotated 90 degrees clockwise for vertical writing mode. (This would not apply to alphabetic characters that remain upright in vertical writing mode, since these characters are not rotated.) + + + ushort baseTagCount = reader.ReadUInt16(); + string[] baselineTags = new string[baseTagCount]; + for (int i = 0; i < baseTagCount; ++i) + { + baselineTags[i] = ConvertToTagString(reader.ReadBytes(4)); + } + return baselineTags; + } + static BaseScript[] ReadBaseScriptList(BinaryReader reader) + { + //BaseScriptList Table + + //The BaseScriptList table identifies all scripts in the font that are rendered in the same layout direction. + //If a script is not listed here, then + //the text-processing client will render the script using the layout information specified for the entire font. + + //For each script listed in the BaseScriptList table, + //a BaseScriptRecord must be defined that identifies the script and references its layout data. + //BaseScriptRecords are stored in the baseScriptRecords array, ordered alphabetically by the baseScriptTag in each record. + //The baseScriptCount specifies the total number of BaseScriptRecords in the array. + + //BaseScriptList table + //Type Name Description + //uint16 baseScriptCount Number of BaseScriptRecords defined + //BaseScriptRecord baseScriptRecords[baseScriptCount] Array of BaseScriptRecords, in alphabetical order by baseScriptTag + + long baseScriptListStartAt = reader.BaseStream.Position; + ushort baseScriptCount = reader.ReadUInt16(); + + BaseScriptRecord[] baseScriptRecord_offsets = new BaseScriptRecord[baseScriptCount]; + for (int i = 0; i < baseScriptCount; ++i) + { + //BaseScriptRecord + + //A BaseScriptRecord contains a script identification tag (baseScriptTag), + //which must be identical to the ScriptTag used to define the script in the ScriptList of a GSUB or GPOS table. + //Each record also must include an offset to a BaseScript table that defines the baseline and min/max extent data for the script. + + //BaseScriptRecord + //Type Name Description + //Tag baseScriptTag 4-byte script identification tag + //Offset16 baseScriptOffset Offset to BaseScript table, from beginning of BaseScriptList + baseScriptRecord_offsets[i] = new BaseScriptRecord(ConvertToTagString(reader.ReadBytes(4)), reader.ReadUInt16()); + } + BaseScript[] baseScripts = new BaseScript[baseScriptCount]; + for (int i = 0; i < baseScriptCount; ++i) + { + BaseScriptRecord baseScriptRecord = baseScriptRecord_offsets[i]; + reader.BaseStream.Position = baseScriptListStartAt + baseScriptRecord.baseScriptOffset; + // + BaseScript baseScipt = ReadBaseScriptTable(reader); + baseScipt.ScriptIdenTag = baseScriptRecord.baseScriptTag; + baseScripts[i] = baseScipt; + } + return baseScripts; + } + readonly struct BaseScriptRecord + { + public readonly string baseScriptTag; + public readonly ushort baseScriptOffset; + public BaseScriptRecord(string scriptTag, ushort offset) + { + this.baseScriptTag = scriptTag; + this.baseScriptOffset = offset; + } + } + public readonly struct BaseLangSysRecord + { + public readonly string baseScriptTag; + public readonly ushort baseScriptOffset; + public BaseLangSysRecord(string scriptTag, ushort offset) + { + this.baseScriptTag = scriptTag; + this.baseScriptOffset = offset; + } + } + + public class BaseScript + { + public string ScriptIdenTag; + public BaseValues baseValues; + public BaseLangSysRecord[] baseLangSysRecords; + public MinMax MinMax; + public BaseScript() { } + +#if DEBUG + public override string ToString() + { + return ScriptIdenTag; + } +#endif + } + static BaseScript ReadBaseScriptTable(BinaryReader reader) + { + //BaseScript Table + //A BaseScript table organizes and specifies the baseline data and min/max extent data for one script. + //Within a BaseScript table, the BaseValues table contains baseline information, + //and one or more MinMax tables contain min/max extent data + //.... + + //A BaseScript table has four components: + //... + + long baseScriptTableStartAt = reader.BaseStream.Position; + + //BaseScript Table + //Type Name Description + //Offset16 baseValuesOffset Offset to BaseValues table, from beginning of BaseScript table (may be NULL) + //Offset16 defaultMinMaxOffset Offset to MinMax table, from beginning of BaseScript table (may be NULL) + //uint16 baseLangSysCount Number of BaseLangSysRecords defined — may be zero (0) + //BaseLangSysRecord baseLangSysRecords[baseLangSysCount] Array of BaseLangSysRecords, in alphabetical order by BaseLangSysTag + + ushort baseValueOffset = reader.ReadUInt16(); + ushort defaultMinMaxOffset = reader.ReadUInt16(); + ushort baseLangSysCount = reader.ReadUInt16(); + BaseLangSysRecord[] baseLangSysRecords = null; + + if (baseLangSysCount > 0) + { + baseLangSysRecords = new BaseLangSysRecord[baseLangSysCount]; + for (int i = 0; i < baseLangSysCount; ++i) + { + //BaseLangSysRecord + //A BaseLangSysRecord defines min/max extents for a language system or a language-specific feature. + //Each record contains an identification tag for the language system (baseLangSysTag) and an offset to a MinMax table (MinMax) + //that defines extent coordinate values for the language system and references feature-specific extent data. + + //BaseLangSysRecord + //Type Name Description + //Tag baseLangSysTag 4-byte language system identification tag + //Offset16 minMaxOffset Offset to MinMax table, from beginning of BaseScript table + baseLangSysRecords[i] = new BaseLangSysRecord(ConvertToTagString(reader.ReadBytes(4)), reader.ReadUInt16()); + } + } + + BaseScript baseScript = new BaseScript(); + baseScript.baseLangSysRecords = baseLangSysRecords; + //-------------------- + if (baseValueOffset > 0) + { + reader.BaseStream.Position = baseScriptTableStartAt + baseValueOffset; + baseScript.baseValues = ReadBaseValues(reader); + + } + if (defaultMinMaxOffset > 0) + { + reader.BaseStream.Position = baseScriptTableStartAt + defaultMinMaxOffset; + baseScript.MinMax = ReadMinMaxTable(reader); + } + + return baseScript; + } + static BaseValues ReadBaseValues(BinaryReader reader) + { + //A BaseValues table lists the coordinate positions of all baselines named in the baselineTags array of the corresponding BaseTagList and + //identifies a default baseline for a script. + + //... + // + //BaseValues table + //Type Name Description + //uint16 defaultBaselineIndex Index number of default baseline for this script — equals index position of baseline tag in baselineTags array of the BaseTagList + //uint16 baseCoordCount Number of BaseCoord tables defined — should equal baseTagCount in the BaseTagList + //Offset16 baseCoords[baseCoordCount] Array of offsets to BaseCoord tables, from beginning of BaseValues table — order matches baselineTags array in the BaseTagList + + long baseValueTableStartAt = reader.BaseStream.Position; + + // + ushort defaultBaselineIndex = reader.ReadUInt16(); + ushort baseCoordCount = reader.ReadUInt16(); + ushort[] baseCoords_Offset = Utils.ReadUInt16Array(reader, baseCoordCount); + + BaseCoord[] baseCoords = new BaseCoord[baseCoordCount]; + for (int i = 0; i < baseCoordCount; ++i) + { + baseCoords[i] = ReadBaseCoordTable(reader, baseValueTableStartAt + baseCoords_Offset[i]); + } + + return new BaseValues(defaultBaselineIndex, baseCoords); + } + + public readonly struct BaseValues + { + public readonly ushort defaultBaseLineIndex; + public readonly BaseCoord[] baseCoords; + + public BaseValues(ushort defaultBaseLineIndex, BaseCoord[] baseCoords) + { + this.defaultBaseLineIndex = defaultBaseLineIndex; + this.baseCoords = baseCoords; + } + } + + + public readonly struct BaseCoord + { + public readonly ushort baseCoordFormat; + /// + /// X or Y value, in design units + /// + public readonly short coord; + + public readonly ushort referenceGlyph; //found in format2 + public readonly ushort baseCoordPoint; //found in format2 + + public BaseCoord(ushort baseCoordFormat, short coord) + { + this.baseCoordFormat = baseCoordFormat; + this.coord = coord; + this.referenceGlyph = this.baseCoordPoint = 0; + } + public BaseCoord(ushort baseCoordFormat, short coord, ushort referenceGlyph, ushort baseCoordPoint) + { + this.baseCoordFormat = baseCoordFormat; + this.coord = coord; + this.referenceGlyph = referenceGlyph; + this.baseCoordPoint = baseCoordPoint; + } +#if DEBUG + public override string ToString() + { + return "format:" + baseCoordFormat + ",coord=" + coord; + } +#endif + } + static BaseCoord ReadBaseCoordTable(BinaryReader reader, long pos) + { + reader.BaseStream.Position = pos; + //BaseCoord Tables + //Within the BASE table, a BaseCoord table defines baseline and min/max extent values. + //Each BaseCoord table defines one X or Y value: + + //If defined within the HorizAxis table, then the BaseCoord table contains a Y value. + //If defined within the VertAxis table, then the BaseCoord table contains an X value. + + //All values are defined in design units, which typically are scaled and rounded to the nearest integer when scaling the glyphs. + //Values may be negative. + + //---------------------- + //BaseCoord Format 1 + //The first BaseCoord format (BaseCoordFormat1) consists of a format identifier, + //followed by a single design unit coordinate that specifies the BaseCoord value. + //This format has the benefits of small size and simplicity, + //but the BaseCoord value cannot be hinted for fine adjustments at different sizes or device resolutions. + + //BaseCoordFormat1 table: Design units only + //Type Name Description + //uint16 baseCoordFormat Format identifier — format = 1 + //int16 coordinate X or Y value, in design units + //---------------------- + + //BaseCoord Format 2 + + //The second BaseCoord format (BaseCoordFormat2) specifies the BaseCoord value in design units, + //but also supplies a glyph index and a contour point for reference. During font hinting, + //the contour point on the glyph outline may move. + //The point’s final position after hinting provides the final value for rendering a given font size. + + //Note: Glyph positioning operations defined in the GPOS table do not affect the point’s final position. + + //BaseCoordFormat2 table: Design units plus contour point + //Type Name Description + //uint16 baseCoordFormat Format identifier — format = 2 + //int16 coordinate X or Y value, in design units + //uint16 referenceGlyph Glyph ID of control glyph + //uint16 baseCoordPoint Index of contour point on the reference glyph + + //---------------------- + //BaseCoord Format 3 + + //The third BaseCoord format (BaseCoordFormat3) also specifies the BaseCoord value in design units, + //but, in a non-variable font, it uses a Device table rather than a contour point to adjust the value. + //This format offers the advantage of fine-tuning the BaseCoord value for any font size and device resolution. + //(For more information about Device tables, see the chapter, Common Table Formats.) + + //In a variable font, BaseCoordFormat3 must be used to reference variation data + //to adjust the X or Y value for different variation instances, if needed. + //In this case, BaseCoordFormat3 specifies an offset to a VariationIndex table, + //which is a variant of the Device table that is used for referencing variation data. + + // Note: While separate VariationIndex table references are required for each Coordinate value that requires variation, two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table, and two or more VariationIndex tables can reference the same variation data entries. + + // Note: If no VariationIndex table is used for a particular X or Y value (the offset is zero, or a different BaseCoord format is used), then that value is used for all variation instances. + + + + //BaseCoordFormat3 table: Design units plus Device or VariationIndex table + //Type Name Description + //uint16 baseCoordFormat Format identifier — format = 3 + //int16 coordinate X or Y value, in design units + //Offset16 deviceTable Offset to Device table (non-variable font) / Variation Index table (variable font) for X or Y value, from beginning of BaseCoord table (may be NULL). + + ushort baseCoordFormat = reader.ReadUInt16(); + switch (baseCoordFormat) + { + default: throw new System.NotSupportedException(); + case 1: + return new BaseCoord(1, + reader.ReadInt16());//coord + case 2: + return new BaseCoord(2, + reader.ReadInt16(), //coordinate + reader.ReadUInt16(), //referenceGlyph + reader.ReadUInt16()); //baseCoordPoint + case 3: +#if DEBUG + +#endif + return new BaseCoord(); + // //TODO: implement this... + // break; + } + + + } + + + static MinMax ReadMinMaxTable(BinaryReader reader) + { + //The MinMax table specifies extents for scripts and language systems. + //It also contains an array of FeatMinMaxRecords used to define feature-specific extents. + + //... + + //Text-processing clients should use the following procedure to access the script, language system, and feature-specific extent data: + + //Determine script extents in relation to the text content. + //Select language-specific extent values with respect to the language system in use. + //Have the application or user choose feature-specific extent values. + //If no extent values are defined for a language system or for language-specific features, + //use the default min/max extent values for the script. + + //MinMax table + //Type Name Description + //Offset16 minCoord Offset to BaseCoord table that defines the minimum extent value, from the beginning of MinMax table (may be NULL) + //Offset16 maxCoord Offset to BaseCoord table that defines maximum extent value, from the beginning of MinMax table (may be NULL) + //uint16 featMinMaxCount Number of FeatMinMaxRecords — may be zero (0) + //FeatMinMaxRecord featMinMaxRecords[featMinMaxCount] Array of FeatMinMaxRecords, in alphabetical order by featureTableTag + + + //FeatMinMaxRecord + //Type Name Description + //Tag featureTableTag 4 - byte feature identification tag — must match feature tag in FeatureList + //Offset16 minCoord Offset to BaseCoord table that defines the minimum extent value, from beginning of MinMax table(may be NULL) + //Offset16 maxCoord Offset to BaseCoord table that defines the maximum extent value, from beginning of MinMax table(may be NULL) + + + long startMinMaxTableAt = reader.BaseStream.Position; + // + MinMax minMax = new MinMax(); + ushort minCoordOffset = reader.ReadUInt16(); + ushort maxCoordOffset = reader.ReadUInt16(); + ushort featMinMaxCount = reader.ReadUInt16(); + + FeatureMinMaxOffset[] minMaxFeatureOffsets = null; + if (featMinMaxCount > 0) + { + minMaxFeatureOffsets = new FeatureMinMaxOffset[featMinMaxCount]; + for (int i = 0; i < featMinMaxCount; ++i) + { + minMaxFeatureOffsets[i] = new FeatureMinMaxOffset( + ConvertToTagString(reader.ReadBytes(4)), //featureTableTag + reader.ReadUInt16(), //minCoord offset + reader.ReadUInt16() //maxCoord offset + ); + } + } + + //---------- + if (minCoordOffset > 0) + { + minMax.minCoord = ReadBaseCoordTable(reader, startMinMaxTableAt + minCoordOffset); + } + if (maxCoordOffset > 0) + { + minMax.maxCoord = ReadBaseCoordTable(reader, startMinMaxTableAt + maxCoordOffset); + } + + if (minMaxFeatureOffsets != null) + { + var featureMinMaxRecords = new FeatureMinMax[minMaxFeatureOffsets.Length]; + for (int i = 0; i < minMaxFeatureOffsets.Length; ++i) + { + FeatureMinMaxOffset featureMinMaxOffset = minMaxFeatureOffsets[i]; + + featureMinMaxRecords[i] = new FeatureMinMax( + featureMinMaxOffset.featureTableTag, //tag + ReadBaseCoordTable(reader, startMinMaxTableAt + featureMinMaxOffset.minCoord), //min + ReadBaseCoordTable(reader, startMinMaxTableAt + featureMinMaxOffset.maxCoord)); //max + } + minMax.featureMinMaxRecords = featureMinMaxRecords; + } + + return minMax; + } + + public class MinMax + { + public BaseCoord minCoord; + public BaseCoord maxCoord; + public FeatureMinMax[] featureMinMaxRecords; + } + public readonly struct FeatureMinMax + { + public readonly string featureTableTag; + public readonly BaseCoord minCoord; + public readonly BaseCoord maxCoord; + public FeatureMinMax(string tag, BaseCoord minCoord, BaseCoord maxCoord) + { + featureTableTag = tag; + this.minCoord = minCoord; + this.maxCoord = maxCoord; + } + } + readonly struct FeatureMinMaxOffset + { + public readonly string featureTableTag; + public readonly ushort minCoord; + public readonly ushort maxCoord; + public FeatureMinMaxOffset(string featureTableTag, ushort minCoord, ushort maxCoord) + { + this.featureTableTag = featureTableTag; + this.minCoord = minCoord; + this.maxCoord = maxCoord; + } + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/COLR.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/COLR.cs new file mode 100644 index 00000000..94dac2c5 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/COLR.cs @@ -0,0 +1,59 @@ +//Apache2, 2017-present Sam Hocevar , WinterDev + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + public class COLR : TableEntry + { + public const string _N = "COLR"; + public override string Name => _N; + + // Read the COLR table + // https://docs.microsoft.com/en-us/typography/opentype/spec/colr + protected override void ReadContentFrom(BinaryReader reader) + { + long offset = reader.BaseStream.Position; + + //Type Name Description + //uint16 version Table version number(starts at 0). + //uint16 numBaseGlyphRecords Number of Base Glyph Records. + //Offset32 baseGlyphRecordsOffset Offset(from beginning of COLR table) to Base Glyph records. + //Offset32 layerRecordsOffset Offset(from beginning of COLR table) to Layer Records. + //uint16 numLayerRecords Number of Layer Records. + + + ushort version = reader.ReadUInt16(); + ushort numBaseGlyphRecords = reader.ReadUInt16(); + uint baseGlyphRecordsOffset = reader.ReadUInt32(); + uint layerRecordsOffset = reader.ReadUInt32(); + ushort numLayerRecords = reader.ReadUInt16(); + + GlyphLayers = new ushort[numLayerRecords]; + GlyphPalettes = new ushort[numLayerRecords]; + + reader.BaseStream.Seek(offset + baseGlyphRecordsOffset, SeekOrigin.Begin); + for (int i = 0; i < numBaseGlyphRecords; ++i) + { + ushort gid = reader.ReadUInt16(); + LayerIndices[gid] = reader.ReadUInt16(); + LayerCounts[gid] = reader.ReadUInt16(); + } + + reader.BaseStream.Seek(offset + layerRecordsOffset, SeekOrigin.Begin); + for (int i = 0; i < GlyphLayers.Length; ++i) + { + GlyphLayers[i] = reader.ReadUInt16(); + GlyphPalettes[i] = reader.ReadUInt16(); + } + } + + public ushort[] GlyphLayers { get; private set; } + public ushort[] GlyphPalettes { get; private set; } + public readonly Dictionary LayerIndices = new Dictionary(); + public readonly Dictionary LayerCounts = new Dictionary(); + } +} + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CPAL.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CPAL.cs new file mode 100644 index 00000000..d8f52499 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CPAL.cs @@ -0,0 +1,93 @@ +//Apache2, 2017-present Sam Hocevar , WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + public class CPAL : TableEntry + { + public const string _N = "CPAL"; + public override string Name => _N; + // + + byte[] _colorBGRABuffer; + + // Palette Table Header + // Read the CPAL table + // https://docs.microsoft.com/en-us/typography/opentype/spec/cpal + protected override void ReadContentFrom(BinaryReader reader) + { + long beginAt = reader.BaseStream.Position; + + //The CPAL table begins with a header that starts with a version number. + //Currently, only versions 0 and 1 are defined. + + //CPAL version 0 + + //The CPAL header version 0 is organized as follows: + //CPAL version 0 + //Type Name Description + //uint16 version Table version number (=0). + //uint16 numPaletteEntries Number of palette entries in each palette. + //uint16 numPalettes Number of palettes in the table. + //uint16 numColorRecords Total number of color records, combined for all palettes. + //Offset32 offsetFirstColorRecord Offset from the beginning of CPAL table to the first ColorRecord. + //uint16 colorRecordIndices[numPalettes] Index of each palette’s first color record in the combined color record array. + + //CPAL version 1 + + //The CPAL header version 1 adds three additional fields to the end of the table header and is organized as follows: + //CPAL version 1 + //Type Name Description + //uint16 version Table version number (=1). + //uint16 numPaletteEntries Number of palette entries in each palette. + //uint16 numPalettes Number of palettes in the table. + //uint16 numColorRecords Total number of color records, combined for all palettes. + //Offset32 offsetFirstColorRecord Offset from the beginning of CPAL table to the first ColorRecord. + //uint16 colorRecordIndices[numPalettes] Index of each palette’s first color record in the combined color record array. + //Offset32 offsetPaletteTypeArray Offset from the beginning of CPAL table to the Palette Type Array. Set to 0 if no array is provided. + //Offset32 offsetPaletteLabelArray Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided. + //Offset32 offsetPaletteEntryLabelArray Offset from the beginning of CPAL table to the Palette Entry Label Array. Set to 0 if no array is provided. + + ushort version = reader.ReadUInt16(); + ushort numPaletteEntries = reader.ReadUInt16(); // XXX: unused? + ushort numPalettes = reader.ReadUInt16(); + ColorCount = reader.ReadUInt16(); //numColorRecords + uint offsetFirstColorRecord = reader.ReadUInt32(); //Offset from the beginning of CPAL table to the first ColorRecord. + Palettes = Utils.ReadUInt16Array(reader, numPalettes); //colorRecordIndices, Index of each palette’s first color record in the combined color record array. + +#if DEBUG + if (version == 1) + { + //Offset32 offsetPaletteTypeArray Offset from the beginning of CPAL table to the Palette Type Array. Set to 0 if no array is provided. + //Offset32 offsetPaletteLabelArray Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided. + //Offset32 offsetPaletteEntryLabelArray Offset from the beginning of CPAL table to the Palette Entry Label Array. Set to 0 if no array is provided. + } +#endif + + //move to color records + reader.BaseStream.Seek(beginAt + offsetFirstColorRecord, SeekOrigin.Begin); + _colorBGRABuffer = reader.ReadBytes(4 * ColorCount); + } + + public ushort[] Palettes { get; private set; } + public ushort ColorCount { get; private set; } + public void GetColor(int colorIndex, out byte r, out byte g, out byte b, out byte a) + { + //Each color record has BGRA values. The color space for these values is sRGB. + //Type Name Description + //uint8 blue Blue value(B0). + //uint8 green Green value(B1). + //uint8 red Red value(B2). + //uint8 alpha Alpha value(B3). + + byte[] colorBGRABuffer = _colorBGRABuffer; + int startAt = colorIndex * 4;//bgra + b = colorBGRABuffer[startAt]; + g = colorBGRABuffer[startAt + 1]; + r = colorBGRABuffer[startAt + 2]; + a = colorBGRABuffer[startAt + 3]; + } + } +} + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ClassDefTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ClassDefTable.cs new file mode 100644 index 00000000..d7508802 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ClassDefTable.cs @@ -0,0 +1,206 @@ +//Apache2, 2016-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + //---------------------------- + //Class Definition Table + //---------------------------- + // + //In OpenType Layout, index values identify glyphs. For efficiency and ease of representation, a font developer can group glyph indices to form glyph classes. + //Class assignments vary in meaning from one lookup subtable to another. + //For example, in the GSUB and GPOS tables, classes are used to describe glyph contexts. + //GDEF tables also use the idea of glyph classes. + + //Consider a substitution action that replaces only the lowercase ascender glyphs in a glyph string. + //To more easily describe the appropriate context for the substitution, + //the font developer might divide the font's lowercase glyphs into two classes, + //one that contains the ascenders and one that contains the glyphs without ascenders. + + //A font developer can assign any glyph to any class, each identified with an integer called a class value. + //A Class Definition table (ClassDef) groups glyph indices by class, + //beginning with Class 1, then Class 2, and so on. + //All glyphs not assigned to a class fall into Class 0. + //Within a given class definition table, each glyph in the font belongs to exactly one class. + + //The ClassDef table can have either of two formats: + //one that assigns a range of consecutive glyph indices to different classes, + //or one that puts groups of consecutive glyph indices into the same class. + // + // + //Class Definition Table Format 1 + // + // + //The first class definition format (ClassDefFormat1) specifies + //a range of consecutive glyph indices and a list of corresponding glyph class values. + //This table is useful for assigning each glyph to a different class because + //the glyph indices in each class are not grouped together. + + //A ClassDef Format 1 table begins with a format identifier (ClassFormat). + //The range of glyph indices (GlyphIDs) covered by the table is identified by two values: the GlyphID of the first glyph (StartGlyph), + //and the number of consecutive GlyphIDs (including the first one) that will be assigned class values (GlyphCount). + //The ClassValueArray lists the class value assigned to each GlyphID, starting with the class value for StartGlyph and + //following the same order as the GlyphIDs. + //Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. + + //Example 7 at the end of this chapter uses Format 1 to assign class values to the lowercase, x-height, ascender, and descender glyphs in a font. + // + //---------------------------- + //ClassDefFormat1 table: Class array + //---------------------------- + //Type Name Description + //uint16 ClassFormat Format identifier-format = 1 + //uint16 StartGlyph First glyph ID of the ClassValueArray + //uint16 GlyphCount Size of the ClassValueArray + //uint16 ClassValueArray[GlyphCount] Array of Class Values-one per GlyphID + //---------------------------------- + // + // + //Class Definition Table Format 2 + // + // + //The second class definition format (ClassDefFormat2) defines multiple groups of glyph indices that belong to the same class. + //Each group consists of a discrete range of glyph indices in consecutive order (ranges cannot overlap). + + //The ClassDef Format 2 table contains a format identifier (ClassFormat), + //a count of ClassRangeRecords that define the groups and assign class values (ClassRangeCount), + //and an array of ClassRangeRecords ordered by the GlyphID of the first glyph in each record (ClassRangeRecord). + + //Each ClassRangeRecord consists of a Start glyph index, an End glyph index, and a Class value. + //All GlyphIDs in a range, from Start to End inclusive, + //constitute the class identified by the Class value. + //Any glyph not covered by a ClassRangeRecord is assumed to belong to Class 0. + + //Example 8 at the end of this chapter uses Format 2 to assign class values to four types of glyphs in the Arabic script. + //--------------------------------------- + //ClassDefFormat2 table: Class ranges + //--------------------------------------- + //Type Name Description + //uint16 ClassFormat Format identifier-format = 2 + //uint16 ClassRangeCount Number of ClassRangeRecords + //struct ClassRangeRecord[ClassRangeCount] Array of ClassRangeRecords-ordered by Start GlyphID + //--------------------------------------- + // + //ClassRangeRecord + //--------------------------------------- + //Type Name Descriptionc + //uint16 Start First glyph ID in the range + //uint16 End Last glyph ID in the range + //uint16 Class Applied to all glyphs in the range + //--------------------------------------- + class ClassDefTable + { + public int Format { get; internal set; } + //---------------- + //format 1 + public ushort startGlyph; + public ushort[] classValueArray; + //--------------- + //format2 + public ClassRangeRecord[] records; + public static ClassDefTable CreateFrom(BinaryReader reader, long beginAt) + { + + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + //--------- + ClassDefTable classDefTable = new ClassDefTable(); + switch (classDefTable.Format = reader.ReadUInt16()) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + classDefTable.startGlyph = reader.ReadUInt16(); + ushort glyphCount = reader.ReadUInt16(); + classDefTable.classValueArray = Utils.ReadUInt16Array(reader, glyphCount); + } + break; + case 2: + { + ushort classRangeCount = reader.ReadUInt16(); + ClassRangeRecord[] records = new ClassRangeRecord[classRangeCount]; + for (int i = 0; i < classRangeCount; ++i) + { + records[i] = new ClassRangeRecord( + reader.ReadUInt16(), //start glyph id + reader.ReadUInt16(), //end glyph id + reader.ReadUInt16()); //classNo + } + classDefTable.records = records; + } + break; + } + return classDefTable; + } + internal readonly struct ClassRangeRecord + { + //--------------------------------------- + // + //ClassRangeRecord + //--------------------------------------- + //Type Name Descriptionc + //uint16 Start First glyph ID in the range + //uint16 End Last glyph ID in the range + //uint16 Class Applied to all glyphs in the range + //--------------------------------------- + public readonly ushort startGlyphId; + public readonly ushort endGlyphId; + public readonly ushort classNo; + public ClassRangeRecord(ushort startGlyphId, ushort endGlyphId, ushort classNo) + { + this.startGlyphId = startGlyphId; + this.endGlyphId = endGlyphId; + this.classNo = classNo; + } +#if DEBUG + public override string ToString() + { + return "class=" + classNo + " [" + startGlyphId + "," + endGlyphId + "]"; + } +#endif + } + + + public int GetClassValue(ushort glyphIndex) + { + switch (Format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + if (glyphIndex >= startGlyph && + glyphIndex < classValueArray.Length) + { + return classValueArray[glyphIndex - startGlyph]; + } + return -1; + } + case 2: + { + + for (int i = 0; i < records.Length; ++i) + { + //TODO: review a proper method here again + //esp. binary search + ClassRangeRecord rec = records[i]; + if (rec.startGlyphId <= glyphIndex) + { + if (glyphIndex <= rec.endGlyphId) + { + return rec.classNo; + } + } + else + { + return -1;//no need to go further + } + } + return -1; + } + } + } + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CoverageTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CoverageTable.cs new file mode 100644 index 00000000..bf61d676 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/CoverageTable.cs @@ -0,0 +1,162 @@ +//Apache2, 2016-present, WinterDev +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + + public abstract class CoverageTable + { + public abstract int FindPosition(ushort glyphIndex); + public abstract IEnumerable GetExpandedValueIter(); + +#if DEBUG + +#endif + + public static CoverageTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + ushort format = reader.ReadUInt16(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: return CoverageFmt1.CreateFrom(reader); + case 2: return CoverageFmt2.CreateFrom(reader); + } + } + + public static CoverageTable[] CreateMultipleCoverageTables(long initPos, ushort[] offsets, BinaryReader reader) + { + CoverageTable[] results = new CoverageTable[offsets.Length]; + for (int i = 0; i < results.Length; ++i) + { + results[i] = CoverageTable.CreateFrom(reader, initPos + offsets[i]); + } + return results; + } + } + + public class CoverageFmt1 : CoverageTable + { + public static CoverageFmt1 CreateFrom(BinaryReader reader) + { + // CoverageFormat1 table: Individual glyph indices + // Type Name Description + // uint16 CoverageFormat Format identifier-format = 1 + // uint16 GlyphCount Number of glyphs in the GlyphArray + // uint16 GlyphArray[GlyphCount] Array of glyph IDs — in numerical order + + ushort glyphCount = reader.ReadUInt16(); + ushort[] glyphs = Utils.ReadUInt16Array(reader, glyphCount); + return new CoverageFmt1() { _orderedGlyphIdList = glyphs }; + } + + public override int FindPosition(ushort glyphIndex) + { + // "The glyph indices must be in numerical order for binary searching of the list" + // (https://www.microsoft.com/typography/otspec/chapter2.htm#coverageFormat1) + int n = Array.BinarySearch(_orderedGlyphIdList, glyphIndex); + return n < 0 ? -1 : n; + } + public override IEnumerable GetExpandedValueIter() { return _orderedGlyphIdList; } + +#if DEBUG + + public override string ToString() + { + List stringList = new List(); + foreach (ushort g in _orderedGlyphIdList) + { + stringList.Add(g.ToString()); + } + return "CoverageFmt1: " + string.Join(",", stringList.ToArray()); + } +#endif + + internal ushort[] _orderedGlyphIdList; + } + + public class CoverageFmt2 : CoverageTable + { + public override int FindPosition(ushort glyphIndex) + { + // Ranges must be in glyph ID order, and they must be distinct, with no overlapping. + // [...] quick calculation of the Coverage Index for any glyph in any range using the + // formula: Coverage Index (glyphID) = startCoverageIndex + glyphID - startGlyphID. + // (https://www.microsoft.com/typography/otspec/chapter2.htm#coverageFormat2) + int n = Array.BinarySearch(_endIndices, glyphIndex); + n = n < 0 ? ~n : n; + if (n >= RangeCount || glyphIndex < _startIndices[n]) + { + return -1; + } + return _coverageIndices[n] + glyphIndex - _startIndices[n]; + } + + public override IEnumerable GetExpandedValueIter() + { + for (int i = 0; i < RangeCount; ++i) + { + for (ushort n = _startIndices[i]; n <= _endIndices[i]; ++n) + { + yield return n; + } + } + } + public static CoverageFmt2 CreateFrom(BinaryReader reader) + { + // CoverageFormat2 table: Range of glyphs + // Type Name Description + // uint16 CoverageFormat Format identifier-format = 2 + // uint16 RangeCount Number of RangeRecords + // struct RangeRecord[RangeCount] Array of glyph ranges — ordered by StartGlyphID. + // + // RangeRecord + // Type Name Description + // uint16 StartGlyphID First glyph ID in the range + // uint16 EndGlyphID Last glyph ID in the range + // uint16 StartCoverageIndex Coverage Index of first glyph ID in range + + ushort rangeCount = reader.ReadUInt16(); + ushort[] startIndices = new ushort[rangeCount]; + ushort[] endIndices = new ushort[rangeCount]; + ushort[] coverageIndices = new ushort[rangeCount]; + for (int i = 0; i < rangeCount; ++i) + { + startIndices[i] = reader.ReadUInt16(); + endIndices[i] = reader.ReadUInt16(); + coverageIndices[i] = reader.ReadUInt16(); + } + + return new CoverageFmt2() + { + _startIndices = startIndices, + _endIndices = endIndices, + _coverageIndices = coverageIndices + }; + } + +#if DEBUG + + public override string ToString() + { + List stringList = new List(); + for (int i = 0; i < RangeCount; ++i) + { + stringList.Add(string.Format("{0}-{1}", _startIndices[i], _endIndices[i])); + } + return "CoverageFmt2: " + string.Join(",", stringList.ToArray()); + } +#endif + + internal ushort[] _startIndices; + internal ushort[] _endIndices; + internal ushort[] _coverageIndices; + + private int RangeCount => _startIndices.Length; + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureInfo.cs new file mode 100644 index 00000000..8feacb89 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureInfo.cs @@ -0,0 +1,195 @@ +//Apache2, 2016-present, WinterDev +using System.Collections.Generic; +namespace Typography.OpenFont +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + + public sealed class FeatureInfo + { + public readonly string fullname; + public readonly string shortname; + public FeatureInfo(string fullname, string shortname) + { + this.fullname = fullname; + this.shortname = shortname; + } + } + public static class Features + { + static readonly Dictionary s_features = new Dictionary(); + + // + public static readonly FeatureInfo + aalt = _("aalt", "Access All Alternates"), + abvf = _("abvf", "Above-base Forms"), + abvm = _("abvm", "Above-base Mark Positioning"), + abvs = _("abvs", "Above-base Substitutions"), + afrc = _("afrc", "Alternative Fractions"), + akhn = _("akhn", "Akhands"), + // + blwf = _("blwf", "Below-base Forms"), + blwm = _("blwm", "Below-base Mark Positioning"), + blws = _("blws", "Below-base Substitutions"), + // + calt = _("calt", "Access All Alternates"), + case_ = _("case", "Above-base Forms"), + ccmp = _("ccmp", "Glyph Composition / Decomposition"), + cfar = _("cfar", "Conjunct Form After Ro"), + cjct = _("cjct", "Conjunct Forms"), + clig = _("clig", "Contextual Ligatures"), + cpct = _("cpct", "Centered CJK Punctuation"), + cpsp = _("cpsp", "Capital Spacing"), + cswh = _("cswh", "Contextual Swash"), + curs = _("curs", "Cursive Positioning"), + c2pc = _("c2pc", "Petite Capitals From Capitals"), + c2sc = _("c2sc", "Small Capitals From Capitals"), + // + dist = _("dist", "Distances"), + dlig = _("dlig", "Discretionary Ligatures"), + dnom = _("dnom", "Denominators"), + dtls = _("dtls", "Dotless Forms"), + // + expt = _("expt", "Expert Forms"), + // + falt = _("falt", "Final Glyph on Line Alternates"), + fin2 = _("fin2", "Terminal Forms #2"), + fin3 = _("fin3", "Terminal Forms #3"), + fina = _("fina", "Terminal Forms"), + flac = _("flac", "Flattened accent forms"), + frac = _("frac", "Fractions"), + fwid = _("fwid", "Full Widths"), + // + half = _("half", "Half Forms"), + haln = _("haln", "Halant Forms"), + halt = _("halt", "Alternate Half Widths"), + hist = _("hist", "Historical Forms"), + hkna = _("hkna", "Horizontal Kana Alternates"), + hlig = _("hlig", "Historical Ligatures"), + hngl = _("hngl", "Hangul"), + hojo = _("hojo", "Hojo Kanji Forms (JIS X 0212-1990 Kanji Forms)"), + hwid = _("hwid", "Half Widths"), + // + init = _("init", "Initial Forms"), + isol = _("isol", "Isolated Forms"), + ital = _("ital", "Italics"), + + jalt = _("jalt", "Justification Alternates"), + jp78 = _("jp78", "JIS78 Forms"), + jp83 = _("jp83", "JIS83 Forms"), + jp90 = _("jp90", "JIS90 Forms"), + jp04 = _("jp04", "JIS2004 Forms"), + // + kern = _("kern", "Kerning"), + // + lfbd = _("lfbd", "Left Bounds"), + liga = _("liga", "Standard Ligatures"), + ljmo = _("ljmo", "Leading Jamo Forms"), + lnum = _("lnum", "Lining Figures"), + locl = _("locl", "Localized Forms"), + ltra = _("ltra", "Left-to-right alternates"), + ltrm = _("ltrm", "Left-to-right mirrored forms"), + // + mark = _("mark", "Mark Positioning"), + med2 = _("med2", "Medial Forms #2"), + medi = _("medi", "Medial Forms"), + mgrk = _("mgrk", "Mathematical Greek"), + mkmk = _("mkmk", "Mark to Mark Positioning"), + mset = _("mset", "Mark Positioning via Substitution"), + // + nalt = _("nalt", "Alternate Annotation Forms"), + nlck = _("nlck", "NLC Kanji Forms"), + nukt = _("nukt", "Nukta Forms"), + numr = _("numr", "Numerators"), + // + onum = _("onum", "Oldstyle Figures"), + opbd = _("opbd", "Optical Bounds"), + ordn = _("ordn", "Ordinals"), + ornm = _("ornm", "Ornaments"), + // + palt = _("palt", "Proportional Alternate Widths"), + pcap = _("pcap", "Petite Capitals"), + pkna = _("pkna", "Proportional Kana"), + pnum = _("pnum", "Proportional Figures"), + pref = _("pref", "Pre-Base Forms"), + pres = _("pres", "Pre-base Substitutions"), + pstf = _("pstf", "Post-base Forms"), + psts = _("psts", "Post-base Substitutions"), + pwid = _("pwid", "Proportional Widths"), + // + qwid = _("qwid", "Quarter Widths"), + // + rand = _("rand", "Randomize"), + rclt = _("rclt", "Required Contextual Alternates"), + rkrf = _("rkrf", "Rakar Forms"), + rlig = _("rlig", "Required Ligatures"), + rphf = _("rphf", "Reph Forms"), + rtbd = _("rtbd", "Right Bounds"), + rtla = _("rtla", "Right-to-left alternates"), + rtlm = _("rtlm", "Right-to-left mirrored forms"), + ruby = _("ruby", "Ruby Notation Forms"), + rvrn = _("rvrn", "Required Variation Alternates"), + // + salt = _("salt", "Stylistic Alternates"), + sinf = _("sinf", "Scientific Inferiors"), + size = _("size", "Optical size"), + smcp = _("smcp", "Small Capitals"), + smpl = _("smpl", "Simplified Forms"), + + ssty = _("ssty", "Math script style alternates"), + stch = _("stch", "Stretching Glyph Decomposition"), + subs = _("subs", "Subscript"), + sups = _("sups", "Superscript"), + swsh = _("swsh", "Swash"), + // + titl = _("titl", "Titling"), + tjmo = _("tjmo", "Trailing Jamo Forms"), + tnam = _("tnam", "Traditional Name Forms"), + tnum = _("tnum", "Tabular Figures"), + trad = _("trad", "Traditional Forms"), + twid = _("twid", "Third Widths"), + // + unic = _("unic", "Unicase"), + // + valt = _("valt", "Alternate Vertical Metrics"), + vatu = _("vatu", "Vattu Variants"), + vert = _("vert", "Vertical Writing"), + vhal = _("vhal", "Alternate Vertical Half Metrics"), + vjmo = _("vjmo", "Vowel Jamo Forms"), + vkna = _("vkna", "Vertical Kana Alternates"), + vkrn = _("vkrn", "Vertical Kerning"), + vpal = _("vpal", "Proportional Alternate Vertical Metrics"), + vrt2 = _("vrt2", "Vertical Alternates and Rotation"), + vrtr = _("vrtr", "Vertical Alternates for Rotation") + ; + + static Features() + { + + // + for (int i = 1; i < 9; ++i) + { + _("cv0" + i, "Character Variants" + i); + } + for (int i = 10; i < 100; ++i) + { + _("cv" + i, "Character Variants" + i); + } + // + for (int i = 1; i < 9; ++i) + { + _("ss0" + i, "Stylistic Set " + i); + } + for (int i = 10; i < 21; ++i) + { + _("ss" + i, "Stylistic Set " + i); + } + } + + static FeatureInfo _(string shortname, string fullname) + { + var featureInfo = new FeatureInfo(fullname, shortname); + s_features.Add(shortname, featureInfo); + return featureInfo; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureList.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureList.cs new file mode 100644 index 00000000..5c3bcd08 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/FeatureList.cs @@ -0,0 +1,148 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + //https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + //The order for applying standard features encoded in OpenType fonts: + + //Feature Feature function Layout operation Required + //--------------------- + //Language based forms: + //--------------------- + //ccmp Character composition/decomposition substitution GSUB + //--------------------- + //Typographical forms: + //--------------------- + //liga Standard ligature substitution GSUB + //clig Contextual ligature substitution GSUB + //Positioning features: + //kern Pair kerning GPOS + //mark Mark to base positioning GPOS X + //mkmk Mark to mark positioning GPOS X + + //[GSUB = glyph substitution, GPOS = glyph positioning] + + public class FeatureList + { + + + public FeatureTable[] featureTables; + public static FeatureList CreateFrom(BinaryReader reader, long beginAt) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + + //------------------ + //FeatureList table + //------------------ + //Type Name Description + //uint16 FeatureCount Number of FeatureRecords in this table + //struct FeatureRecord[FeatureCount] Array of FeatureRecords-zero-based (first feature has FeatureIndex = 0)-listed alphabetically by FeatureTag + //------------------ + //FeatureRecord + //------------------ + //Type Name Description + //Tag FeatureTag 4-byte feature identification tag + //Offset16 Feature Offset to Feature table-from beginning of FeatureList + //---------------------------------------------------- + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + FeatureList featureList = new FeatureList(); + ushort featureCount = reader.ReadUInt16(); + FeatureRecord[] featureRecords = new FeatureRecord[featureCount]; + for (int i = 0; i < featureCount; ++i) + { + //read script record + featureRecords[i] = new FeatureRecord( + reader.ReadUInt32(), //feature tag + reader.ReadUInt16()); //Offset16 + } + //read each feature table + FeatureTable[] featureTables = featureList.featureTables = new FeatureTable[featureCount]; + for (int i = 0; i < featureCount; ++i) + { + FeatureRecord frecord = featureRecords[i]; + (featureTables[i] = FeatureTable.CreateFrom(reader, beginAt + frecord.offset)).FeatureTag = frecord.featureTag; + } + return featureList; + } + readonly struct FeatureRecord + { + public readonly uint featureTag;//4-byte ScriptTag identifier + public readonly ushort offset; //Script Offset to Script table-from beginning of ScriptList + public FeatureRecord(uint featureTag, ushort offset) + { + this.featureTag = featureTag; + this.offset = offset; + } + + public string FeatureName => Utils.TagToString(featureTag); +#if DEBUG + public override string ToString() + { + return FeatureName + "," + offset; + } +#endif + } + + + //Feature Table + + //A Feature table defines a feature with one or more lookups. + //The client uses the lookups to substitute or position glyphs. + + //Feature tables defined within the GSUB table contain references to glyph substitution lookups, + //and feature tables defined within the GPOS table contain references to glyph positioning lookups. + //If a text-processing operation requires both glyph substitution and positioning, + //then both the GSUB and GPOS tables must each define a Feature table, + //and the tables must use the same FeatureTags. + + //A Feature table consists of an offset to a Feature Parameters (FeatureParams) table + //(if one has been defined for this feature - see note in the following paragraph), + //a count of the lookups listed for the feature (LookupCount), + //and an arbitrarily ordered array of indices into a LookupList (LookupListIndex). + //The LookupList indices are references into an array of offsets to Lookup tables. + + //The format of the Feature Parameters table is specific to a particular feature, + //and must be specified in the feature's entry in the Feature Tags section of the OpenType Layout Tag Registry. + //The length of the Feature Parameters table must be implicitly or explicitly specified in the Feature Parameters table itself. + //The FeatureParams field in the Feature Table records the offset relative to the beginning of the Feature Table. + //If a Feature Parameters table is not needed, the FeatureParams field must be set to NULL. + + //To identify the features in a GSUB or GPOS table, + //a text-processing client reads the FeatureTag of each FeatureRecord referenced in a given LangSys table. + //Then the client selects the features it wants to implement and uses the LookupList to retrieve the Lookup indices of the chosen features. + //Next, the client arranges the indices in the LookupList order. + //Finally, the client applies the lookup data to substitute or position glyphs. + + //Example 3 at the end of this chapter shows the FeatureList and Feature tables used to substitute ligatures in two languages. + // + + + public class FeatureTable + { + public static FeatureTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + ushort featureParams = reader.ReadUInt16(); + ushort lookupCount = reader.ReadUInt16(); + + FeatureTable featureTable = new FeatureTable(); + featureTable.LookupListIndices = Utils.ReadUInt16Array(reader, lookupCount); + return featureTable; + } + public ushort[] LookupListIndices { get; private set; } + public uint FeatureTag { get; set; } + public string TagName => Utils.TagToString(this.FeatureTag); +#if DEBUG + public override string ToString() + { + return this.TagName; + } +#endif + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GDEF.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GDEF.cs new file mode 100644 index 00000000..8e070866 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GDEF.cs @@ -0,0 +1,328 @@ +//Apache2, 2016-present, WinterDev +using System; +using System.IO; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//from https://www.microsoft.com/typography/developers/opentype/detail.htm +//GDEF Table +//As discussed in Part One, +//the most important tables for glyph processing are GSUB and GPOS, +//but both these tables make use of data in the Glyph Definition table. +//The GDEF table contains three kinds of information in subtables: +//1. glyph class definitions that classify different types of glyphs in a font; +//2. attachment point lists that identify glyph positioning attachments for each glyph; +//and 3. ligature caret lists that provide information for caret positioning and text selection involving ligatures. + +//The Glyph Class Definition subtable identifies +//four glyph classes: +//1. simple glyphs, +//2. ligature glyphs (glyphs representing two or more glyph components), +//3. combining mark glyphs (glyphs that combine with other classes), +//and 4. glyph components (glyphs that represent individual parts of ligature glyphs). + +//These classes are used by both GSUB and GPOS to differentiate glyphs in a string; for example, +//to distinguish between a base vowel (simple glyph) +//and the accent (combining mark glyph) that a GPOS feature will position above it. + +//The Attachment Point List +//identifies all the glyph attachment points defined in the GPOS table. +//Clients that access this information in the GDEF table can cache attachment coordinates with the rasterized glyph bitmaps, +//and avoid having to recalculate the attachment points each time they display a glyph. +//Without this table, +//GPOS features could still be enabled, +//but processing speed would be slower because the client would need to decode the GPOS lookups +//that define the attachment points and compile its own list. + +//The Ligature Caret List +//defines the positions for the caret to occupy in ligatures. +//This information, which can be fine tuned for particular bitmap sizes, +//makes it possible for the caret to step across the component characters of a ligature, and for the user to select text including parts of ligatures. +//In the example on the left, below, the caret is positioned between two components of a ligature; on the right, text is selected from within a ligature. +// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//https://docs.microsoft.com/en-us/typography/opentype/spec/gdef + +//GDEF — Glyph Definition Table + +//The Glyph Definition (GDEF) table contains six types of information in six independent tables: + +// The GlyphClassDef table classifies the different types of glyphs in the font. +// The AttachmentList table identifies all attachment points on the glyphs, which streamlines data access and bitmap caching. +// The LigatureCaretList table contains positioning data for ligature carets, which the text-processing client uses on screen to select and highlight the individual components of a ligature glyph. +// The MarkAttachClassDef table classifies mark glyphs, to help group together marks that are positioned similarly. +// The MarkGlyphSetsTable allows the enumeration of an arbitrary number of glyph sets that can be used as an extension of the mark attachment class definition to allow lookups to filter mark glyphs by arbitrary sets of marks. +// The ItemVariationStore table is used in variable fonts to contain variation data used for adjustment of values in the GDEF, GPOS or JSTF tables. + +//The GSUB and GPOS tables may reference certain GDEF table information used for processing of lookup tables. See, for example, the LookupFlag bit enumeration in “OpenType Layout Common Table Formats”. + +//In variable fonts, the GDEF, GPOS and JSTF tables may all reference variation data within the ItemVariationStore table contained within the GDEF table. See below for further discussion of variable fonts and the ItemVariationStore table. +/////////////////////////////////////////////////////////////////////////////// + + +namespace Typography.OpenFont.Tables +{ + + class GDEF : TableEntry + { + public const string _N = "GDEF"; + public override string Name => _N; + // + long _tableStartAt; + protected override void ReadContentFrom(BinaryReader reader) + { + _tableStartAt = reader.BaseStream.Position; + //----------------------------------------- + //GDEF Header, Version 1.0 + //Type Name Description + //uint16 MajorVersion Major version of the GDEF table, = 1 + //uint16 MinorVersion Minor version of the GDEF table, = 0 + //Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL) + //Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL) + //Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL) + //Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL) + // + //GDEF Header, Version 1.2 + //Type Name Description + //uint16 MajorVersion Major version of the GDEF table, = 1 + //uint16 MinorVersion Minor version of the GDEF table, = 2 + //Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL) + //Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL) + //Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL) + //Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL) + //Offset16 MarkGlyphSetsDef Offset to the table of mark set definitions, from beginning of GDEF header (may be NULL) + // + //GDEF Header, Version 1.3 + //Type Name Description + //uint16 MajorVersion Major version of the GDEF table, = 1 + //uint16 MinorVersion Minor version of the GDEF table, = 3 + //Offset16 GlyphClassDef Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL) + //Offset16 AttachList Offset to list of glyphs with attachment points, from beginning of GDEF header (may be NULL) + //Offset16 LigCaretList Offset to list of positioning points for ligature carets, from beginning of GDEF header (may be NULL) + //Offset16 MarkAttachClassDef Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL) + //Offset16 MarkGlyphSetsDef Offset to the table of mark set definitions, from beginning of GDEF header (may be NULL) + //Offset32 ItemVarStore Offset to the Item Variation Store table, from beginning of GDEF header (may be NULL) + + //common to 1.0, 1.2, 1.3... + this.MajorVersion = reader.ReadUInt16(); + this.MinorVersion = reader.ReadUInt16(); + // + ushort glyphClassDefOffset = reader.ReadUInt16(); + ushort attachListOffset = reader.ReadUInt16(); + ushort ligCaretListOffset = reader.ReadUInt16(); + ushort markAttachClassDefOffset = reader.ReadUInt16(); + ushort markGlyphSetsDefOffset = 0; + uint itemVarStoreOffset = 0; + // + switch (MinorVersion) + { + default: + Utils.WarnUnimplemented("GDEF Minor Version {0}", MinorVersion); + return; + case 0: + break; + case 2: + markGlyphSetsDefOffset = reader.ReadUInt16(); + break; + case 3: + markGlyphSetsDefOffset = reader.ReadUInt16(); + itemVarStoreOffset = reader.ReadUInt32(); + break; + } + //--------------- + + + this.GlyphClassDef = (glyphClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + glyphClassDefOffset); + this.AttachmentListTable = (attachListOffset == 0) ? null : AttachmentListTable.CreateFrom(reader, _tableStartAt + attachListOffset); + this.LigCaretList = (ligCaretListOffset == 0) ? null : LigCaretList.CreateFrom(reader, _tableStartAt + ligCaretListOffset); + + //A Mark Attachment Class Definition Table defines the class to which a mark glyph may belong. + //This table uses the same format as the Class Definition table (for details, see the chapter, Common Table Formats ). + + +#if DEBUG + if (markAttachClassDefOffset == 2) + { + //temp debug invalid font + this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, reader.BaseStream.Position); + } + else + { + this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + markAttachClassDefOffset); + } +#else + this.MarkAttachmentClassDef = (markAttachClassDefOffset == 0) ? null : ClassDefTable.CreateFrom(reader, _tableStartAt + markAttachClassDefOffset); +#endif + + this.MarkGlyphSetsTable = (markGlyphSetsDefOffset == 0) ? null : MarkGlyphSetsTable.CreateFrom(reader, _tableStartAt + markGlyphSetsDefOffset); + + if (itemVarStoreOffset != 0) + { + //not supported + Utils.WarnUnimplemented("GDEF ItemVarStore"); + reader.BaseStream.Seek(this.Header.Offset + itemVarStoreOffset, SeekOrigin.Begin); + } + } + public int MajorVersion { get; private set; } + public int MinorVersion { get; private set; } + public ClassDefTable GlyphClassDef { get; private set; } + public AttachmentListTable AttachmentListTable { get; private set; } + public LigCaretList LigCaretList { get; private set; } + public ClassDefTable MarkAttachmentClassDef { get; private set; } + public MarkGlyphSetsTable MarkGlyphSetsTable { get; private set; } + + //------------------------ + /// + /// fill gdef to each glyphs + /// + /// + public void FillGlyphData(Glyph[] inputGlyphs) + { + //1. + FillClassDefs(inputGlyphs); + //2. + FillAttachPoints(inputGlyphs); + //3. + FillLigatureCarets(inputGlyphs); + //4. + FillMarkAttachmentClassDefs(inputGlyphs); + //5. + FillMarkGlyphSets(inputGlyphs); + } + void FillClassDefs(Glyph[] inputGlyphs) + { + //1. glyph def + ClassDefTable classDef = GlyphClassDef; + if (classDef == null) return; + //----------------------------------------- + + switch (classDef.Format) + { + default: + Utils.WarnUnimplemented("GDEF GlyphClassDef Format {0}", classDef.Format); + break; + case 1: + { + ushort startGlyph = classDef.startGlyph; + ushort[] classValues = classDef.classValueArray; + int gIndex = startGlyph; + for (int i = 0; i < classValues.Length; ++i) + { +#if DEBUG + ushort classV = classValues[i]; + if (classV > (ushort)GlyphClassKind.Component) + { + + } +#endif + + inputGlyphs[gIndex].GlyphClass = (GlyphClassKind)classValues[i]; + gIndex++; + } + + } + break; + case 2: + { + ClassDefTable.ClassRangeRecord[] records = classDef.records; + for (int n = 0; n < records.Length; ++n) + { + ClassDefTable.ClassRangeRecord rec = records[n]; + +#if DEBUG + + if (rec.classNo > (ushort)GlyphClassKind.Component) + { + + } +#endif + + GlyphClassKind glyphKind = (GlyphClassKind)rec.classNo; + for (int i = rec.startGlyphId; i <= rec.endGlyphId; ++i) + { + inputGlyphs[i].GlyphClass = glyphKind; + } + } + } + break; + } + } + void FillAttachPoints(Glyph[] inputGlyphs) + { + AttachmentListTable attachmentListTable = this.AttachmentListTable; + if (attachmentListTable == null) { return; } + //----------------------------------------- + + Utils.WarnUnimplemented("please implement GDEF.FillAttachPoints()"); + } + void FillLigatureCarets(Glyph[] inputGlyphs) + { + //Console.WriteLine("please implement FillLigatureCarets()"); + } + void FillMarkAttachmentClassDefs(Glyph[] inputGlyphs) + { + //Mark Attachment Class Definition Table + //A Mark Class Definition Table is used to assign mark glyphs into different classes + //that can be used in lookup tables within the GSUB or GPOS table to control how mark glyphs within a glyph sequence are treated by lookups. + //For more information on the use of mark attachment classes, + //see the description of lookup flags in the “Lookup Table” section of the chapter, OpenType Layout Common Table Formats. + ClassDefTable markAttachmentClassDef = this.MarkAttachmentClassDef; + if (markAttachmentClassDef == null) return; + //----------------------------------------- + + switch (markAttachmentClassDef.Format) + { + default: + Utils.WarnUnimplemented("GDEF MarkAttachmentClassDef Table Format {0}", markAttachmentClassDef.Format); + break; + case 1: + { + ushort startGlyph = markAttachmentClassDef.startGlyph; + ushort[] classValues = markAttachmentClassDef.classValueArray; + + int len = classValues.Length; + int gIndex = startGlyph; + for (int i = 0; i < len; ++i) + { +#if DEBUG + Glyph dbugTestGlyph = inputGlyphs[gIndex]; +#endif + inputGlyphs[gIndex].MarkClassDef = classValues[i]; + gIndex++; + } + + } + break; + case 2: + { + ClassDefTable.ClassRangeRecord[] records = markAttachmentClassDef.records; + int len = records.Length; + for (int n = 0; n < len; ++n) + { + ClassDefTable.ClassRangeRecord rec = records[n]; + for (int i = rec.startGlyphId; i <= rec.endGlyphId; ++i) + { +#if DEBUG + Glyph dbugTestGlyph = inputGlyphs[i]; +#endif + inputGlyphs[i].MarkClassDef = rec.classNo; + } + } + } + break; + } + } + void FillMarkGlyphSets(Glyph[] inputGlyphs) + { + //Mark Glyph Sets Table + //A Mark Glyph Sets table is used to define sets of mark glyphs that can be used in lookup tables within the GSUB or GPOS table to control + //how mark glyphs within a glyph sequence are treated by lookups. For more information on the use of mark glyph sets, + //see the description of lookup flags in the “Lookup Table” section of the chapter, OpenType Layout Common Table Formats. + MarkGlyphSetsTable markGlyphSets = this.MarkGlyphSetsTable; + if (markGlyphSets == null) return; + //----------------------------------------- + + Utils.WarnUnimplemented("please implement GDEF.FillMarkGlyphSets()"); + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.Others.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.Others.cs new file mode 100644 index 00000000..630bbce5 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.Others.cs @@ -0,0 +1,888 @@ +//Apache2, 2016-present, WinterDev +using System; +using System.IO; +using System.Text; + +//https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + +namespace Typography.OpenFont.Tables +{ + partial class GPOS + { + + static PosRuleSetTable[] CreateMultiplePosRuleSetTables(long initPos, ushort[] offsets, BinaryReader reader) + { + int j = offsets.Length; + PosRuleSetTable[] results = new PosRuleSetTable[j]; + for (int i = 0; i < j; ++i) + { + results[i] = PosRuleSetTable.CreateFrom(reader, initPos + offsets[i]); + } + return results; + } + + static PosLookupRecord[] CreateMultiplePosLookupRecords(BinaryReader reader, int count) + { + + PosLookupRecord[] results = new PosLookupRecord[count]; + for (int n = 0; n < count; ++n) + { + results[n] = PosLookupRecord.CreateFrom(reader); + } + return results; + } + + + class PairSetTable + { + internal PairSet[] _pairSets; + public void ReadFrom(BinaryReader reader, ushort v1format, ushort v2format) + { + ushort rowCount = reader.ReadUInt16(); + _pairSets = new PairSet[rowCount]; + for (int i = 0; i < rowCount; ++i) + { + //GlyphID SecondGlyph GlyphID of second glyph in the pair-first glyph is listed in the Coverage table + //ValueRecord Value1 Positioning data for the first glyph in the pair + //ValueRecord Value2 Positioning data for the second glyph in the pair + ushort secondGlyph = reader.ReadUInt16(); + ValueRecord v1 = ValueRecord.CreateFrom(reader, v1format); + ValueRecord v2 = ValueRecord.CreateFrom(reader, v2format); + // + _pairSets[i] = new PairSet(secondGlyph, v1, v2); + } + } + public bool FindPairSet(ushort secondGlyphIndex, out PairSet foundPairSet) + { + int j = _pairSets.Length; + for (int i = 0; i < j; ++i) + { + //TODO: binary search? + if (_pairSets[i].secondGlyph == secondGlyphIndex) + { + //found + foundPairSet = _pairSets[i]; + return true; + } + } + // + foundPairSet = new PairSet();//empty + return false; + } + } + + + readonly struct PairSet + { + public readonly ushort secondGlyph;//GlyphID of second glyph in the pair-first glyph is listed in the Coverage table + public readonly ValueRecord value1;//Positioning data for the first glyph in the pair + public readonly ValueRecord value2;//Positioning data for the second glyph in the pair + public PairSet(ushort secondGlyph, ValueRecord v1, ValueRecord v2) + { + this.secondGlyph = secondGlyph; + this.value1 = v1; + this.value2 = v2; + } +#if DEBUG + public override string ToString() + { + return "second_glyph:" + secondGlyph; + } +#endif + } + + + class ValueRecord + { + //ValueRecord (all fields are optional) + //Value Type Description + //-------------------------------- + //int16 XPlacement Horizontal adjustment for placement-in design units + //int16 YPlacement Vertical adjustment for placement, in design units + //int16 XAdvance Horizontal adjustment for advance, in design units (only used for horizontal writing) + //int16 YAdvance Vertical adjustment for advance, in design units (only used for vertical writing) + //Offset16 XPlaDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal placement, from beginning of PosTable (may be NULL) + //Offset16 YPlaDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical placement, from beginning of PosTable (may be NULL) + //Offset16 XAdvDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal advance, from beginning of PosTable (may be NULL) + //Offset16 YAdvDevice Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical advance, from beginning of PosTable (may be NULL) + + public short XPlacement; + public short YPlacement; + public short XAdvance; + public short YAdvance; + public ushort XPlaDevice; + public ushort YPlaDevice; + public ushort XAdvDevice; + public ushort YAdvDevice; + + ushort valueFormat; + public void ReadFrom(BinaryReader reader, ushort valueFormat) + { + this.valueFormat = valueFormat; + if (HasFormat(valueFormat, FMT_XPlacement)) + { + this.XPlacement = reader.ReadInt16(); + } + if (HasFormat(valueFormat, FMT_YPlacement)) + { + this.YPlacement = reader.ReadInt16(); + } + if (HasFormat(valueFormat, FMT_XAdvance)) + { + this.XAdvance = reader.ReadInt16(); + } + if (HasFormat(valueFormat, FMT_YAdvance)) + { + this.YAdvance = reader.ReadInt16(); + } + if (HasFormat(valueFormat, FMT_XPlaDevice)) + { + this.XPlaDevice = reader.ReadUInt16(); + } + if (HasFormat(valueFormat, FMT_YPlaDevice)) + { + this.YPlaDevice = reader.ReadUInt16(); + } + if (HasFormat(valueFormat, FMT_XAdvDevice)) + { + this.XAdvDevice = reader.ReadUInt16(); + } + if (HasFormat(valueFormat, FMT_YAdvDevice)) + { + this.YAdvDevice = reader.ReadUInt16(); + } + } + static bool HasFormat(ushort value, int flags) + { + return (value & flags) == flags; + } + //Mask Name Description + //0x0001 XPlacement Includes horizontal adjustment for placement + //0x0002 YPlacement Includes vertical adjustment for placement + //0x0004 XAdvance Includes horizontal adjustment for advance + //0x0008 YAdvance Includes vertical adjustment for advance + //0x0010 XPlaDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for horizontal placement + //0x0020 YPlaDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for vertical placement + //0x0040 XAdvDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for horizontal advance + //0x0080 YAdvDevice Includes Device table (non-variable font) / VariationIndex table (variable font) for vertical advance + //0xFF00 Reserved For future use (set to zero) + + //check bits + const int FMT_XPlacement = 1; + const int FMT_YPlacement = 1 << 1; + const int FMT_XAdvance = 1 << 2; + const int FMT_YAdvance = 1 << 3; + const int FMT_XPlaDevice = 1 << 4; + const int FMT_YPlaDevice = 1 << 5; + const int FMT_XAdvDevice = 1 << 6; + const int FMT_YAdvDevice = 1 << 7; + + public static ValueRecord CreateFrom(BinaryReader reader, ushort valueFormat) + { + if (valueFormat == 0) + return null;//empty + + var v = new ValueRecord(); + v.ReadFrom(reader, valueFormat); + return v; + } + +#if DEBUG + public override string ToString() + { + StringBuilder stbuilder = new StringBuilder(); + bool appendComma = false; + if (XPlacement != 0) + { + stbuilder.Append("XPlacement=" + XPlacement); + appendComma = true; + } + + + + if (YPlacement != 0) + { + if (appendComma) { stbuilder.Append(','); } + stbuilder.Append(" YPlacement=" + YPlacement); + appendComma = true; + } + if (XAdvance != 0) + { + if (appendComma) { stbuilder.Append(','); } + stbuilder.Append(" XAdvance=" + XAdvance); + appendComma = true; + } + if (YAdvance != 0) + { + if (appendComma) { stbuilder.Append(','); } + stbuilder.Append(" YAdvance=" + YAdvance); + appendComma = true; + } + return stbuilder.ToString(); + } +#endif + } + + + /// + /// To describe an anchor point + /// + class AnchorPoint + { + //Anchor Table + + //A GPOS table uses anchor points to position one glyph with respect to another. + //Each glyph defines an anchor point, and the text-processing client attaches the glyphs by aligning their corresponding anchor points. + + //To describe an anchor point, an Anchor table can use one of three formats. + //The first format uses design units to specify a location for the anchor point. + //The other two formats refine the location of the anchor point using contour points (Format 2) or Device tables (Format 3). + //In a variable font, the third format uses a VariationIndex table (a variant of a Device table) to + //reference variation data for adjustment of the anchor position for the current variation instance, as needed. + + public ushort format; + public short xcoord; + public short ycoord; + /// + /// an index to a glyph contour point (AnchorPoint) + /// + public ushort refGlyphContourPoint; + public ushort xdeviceTableOffset; + public ushort ydeviceTableOffset; + public static AnchorPoint CreateFrom(BinaryReader reader, long beginAt) + { + AnchorPoint anchorPoint = new AnchorPoint(); + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + switch (anchorPoint.format = reader.ReadUInt16()) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + // AnchorFormat1 table: Design units only + //AnchorFormat1 consists of a format identifier (AnchorFormat) and a pair of design unit coordinates (XCoordinate and YCoordinate) + //that specify the location of the anchor point. + //This format has the benefits of small size and simplicity, + //but the anchor point cannot be hinted to adjust its position for different device resolutions. + //Value Type Description + //uint16 AnchorFormat Format identifier, = 1 + //int16 XCoordinate Horizontal value, in design units + //int16 YCoordinate Vertical value, in design units + anchorPoint.xcoord = reader.ReadInt16(); + anchorPoint.ycoord = reader.ReadInt16(); + } + break; + case 2: + { + //Anchor Table: Format 2 + + //Like AnchorFormat1, AnchorFormat2 specifies a format identifier (AnchorFormat) and + //a pair of design unit coordinates for the anchor point (Xcoordinate and Ycoordinate). + + //For fine-tuning the location of the anchor point, + //AnchorFormat2 also provides an index to a glyph contour point (AnchorPoint) + //that is on the outline of a glyph (AnchorPoint).*** + //Hinting can be used to move the AnchorPoint. In the rendered text, + //the AnchorPoint will provide the final positioning data for a given ppem size. + + //Example 16 at the end of this chapter uses AnchorFormat2. + + + //AnchorFormat2 table: Design units plus contour point + //Value Type Description + //uint16 AnchorFormat Format identifier, = 2 + //int16 XCoordinate Horizontal value, in design units + //int16 YCoordinate Vertical value, in design units + //uint16 AnchorPoint Index to glyph contour point + + anchorPoint.xcoord = reader.ReadInt16(); + anchorPoint.ycoord = reader.ReadInt16(); + anchorPoint.refGlyphContourPoint = reader.ReadUInt16(); + + } + break; + case 3: + { + + //Anchor Table: Format 3 + + //Like AnchorFormat1, AnchorFormat3 specifies a format identifier (AnchorFormat) and + //locates an anchor point (Xcoordinate and Ycoordinate). + //And, like AnchorFormat 2, it permits fine adjustments in variable fonts to the coordinate values. + //However, AnchorFormat3 uses Device tables, rather than a contour point, for this adjustment. + + //With a Device table, a client can adjust the position of the anchor point for any font size and device resolution. + //AnchorFormat3 can specify offsets to Device tables for the the X coordinate (XDeviceTable) + //and the Y coordinate (YDeviceTable). + //If only one coordinate requires adjustment, + //the offset to the Device table may be set to NULL for the other coordinate. + + //In variable fonts, AnchorFormat3 must be used to reference variation data to adjust anchor points for different variation instances, + //if needed. + //In this case, AnchorFormat3 specifies an offset to a VariationIndex table, + //which is a variant of the Device table used for variations. + //If no VariationIndex table is used for a particular anchor point X or Y coordinate, + //then that value is used for all variation instances. + //While separate VariationIndex table references are required for each value that requires variation, + //two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table, and two or more VariationIndex tables can reference the same variation data entries. + + //Example 17 at the end of the chapter shows an AnchorFormat3 table. + + + //AnchorFormat3 table: Design units plus Device or VariationIndex tables + //Value Type Description + //uint16 AnchorFormat Format identifier, = 3 + //int16 XCoordinate Horizontal value, in design units + //int16 YCoordinate Vertical value, in design units + //Offset16 XDeviceTable Offset to Device table (non-variable font) / VariationIndex table (variable font) for X coordinate, from beginning of Anchor table (may be NULL) + //Offset16 YDeviceTable Offset to Device table (non-variable font) / VariationIndex table (variable font) for Y coordinate, from beginning of Anchor table (may be NULL) + + anchorPoint.xcoord = reader.ReadInt16(); + anchorPoint.ycoord = reader.ReadInt16(); + anchorPoint.xdeviceTableOffset = reader.ReadUInt16(); + anchorPoint.ydeviceTableOffset = reader.ReadUInt16(); + } + break; + } + return anchorPoint; + + } +#if DEBUG + public override string ToString() + { + switch (format) + { + default: return ""; + case 1: + return format + "(" + xcoord + "," + ycoord + ")"; + case 2: + return format + "(" + xcoord + "," + ycoord + "), ref_point=" + refGlyphContourPoint; + case 3: + return format + "(" + xcoord + "," + ycoord + "), xy_device(" + xdeviceTableOffset + "," + ydeviceTableOffset + ")"; + } + + } +#endif + } + + + class MarkArrayTable + { + //Mark Array + //The MarkArray table defines the class and the anchor point for a mark glyph. + //Three GPOS subtables-MarkToBase, MarkToLigature, and MarkToMark Attachment + //use the MarkArray table to specify data for attaching marks. + + //The MarkArray table contains a count of the number of mark records (MarkCount) and an array of those records (MarkRecord). + //Each mark record defines the class of the mark and an offset to the Anchor table that contains data for the mark. + + //A class value can be 0 (zero), but the MarkRecord must explicitly assign that class value (this differs from the ClassDef table, + //in which all glyphs not assigned class values automatically belong to Class 0). + //The GPOS subtables that refer to MarkArray tables use the class assignments for indexing zero-based arrays that contain data for each mark class. + + // MarkArray table + //------------------- + //Value Type Description + //uint16 MarkCount Number of MarkRecords + //struct MarkRecord[MarkCount] Array of MarkRecords in Coverage order + // + //MarkRecord + //Value Type Description + //------------------- + //uint16 Class Class defined for this mark + //Offset16 MarkAnchor Offset to Anchor table-from beginning of MarkArray table + internal MarkRecord[] _records; + internal AnchorPoint[] _anchorPoints; + public AnchorPoint GetAnchorPoint(int index) + { + return _anchorPoints[index]; + } + public ushort GetMarkClass(int index) + { + return _records[index].markClass; + } + void ReadFrom(BinaryReader reader) + { + long markTableBeginAt = reader.BaseStream.Position; + ushort markCount = reader.ReadUInt16(); + _records = new MarkRecord[markCount]; + for (int i = 0; i < markCount; ++i) + { + //1 mark : 1 anchor + _records[i] = new MarkRecord( + reader.ReadUInt16(),//mark class + reader.ReadUInt16()); //offset to anchor table + } + //--------------------------- + //read anchor + _anchorPoints = new AnchorPoint[markCount]; + for (int i = 0; i < markCount; ++i) + { + MarkRecord markRec = _records[i]; + //bug? + if (markRec.offset < 0) + { + //TODO: review here + //found err on Tahoma + continue; + } + //read table detail + _anchorPoints[i] = AnchorPoint.CreateFrom(reader, markTableBeginAt + markRec.offset); + } + + } +#if DEBUG + public int dbugGetAnchorCount() + { + return _anchorPoints.Length; + } +#endif + public static MarkArrayTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + var markArrTable = new MarkArrayTable(); + markArrTable.ReadFrom(reader); + return markArrTable; + } + } + + readonly struct MarkRecord + { + /// + /// Class defined for this mark,. A mark class is identified by a specific integer, called a class value + /// + public readonly ushort markClass; + /// + /// Offset to Anchor table-from beginning of MarkArray table + /// + public readonly ushort offset; + public MarkRecord(ushort markClass, ushort offset) + { + this.markClass = markClass; + this.offset = offset; + } +#if DEBUG + public override string ToString() + { + return "class " + markClass + ",offset=" + offset; + } +#endif + } + + class Mark2ArrayTable + { + ///Mark2Array table + //Value Type Description + //uint16 Mark2Count Number of Mark2 records + //struct Mark2Record[Mark2Count] Array of Mark2 records-in Coverage order + + //Each Mark2Record contains an array of offsets to Anchor tables (Mark2Anchor). + //The array of zero-based offsets, measured from the beginning of the Mark2Array table, + //defines the entire set of Mark2 attachment points used to attach Mark1 glyphs to a specific Mark2 glyph. + //The Anchor tables in the Mark2Anchor array are ordered by Mark1 class value. + + //A Mark2Record declares one Anchor table for each mark class (including Class 0) + //identified in the MarkRecords of the MarkArray. + //Each Anchor table specifies one Mark2 attachment point used to attach all + //the Mark1 glyphs in a particular class to the Mark2 glyph. + + //Mark2Record + //Value Type Description + //Offset16 Mark2Anchor[ClassCount] Array of offsets (one per class) to Anchor tables-from beginning of Mark2Array table-zero-based array + + public static Mark2ArrayTable CreateFrom(BinaryReader reader, long beginAt, ushort classCount) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //--- + ushort mark2Count = reader.ReadUInt16(); + ushort[] offsets = Utils.ReadUInt16Array(reader, mark2Count * classCount); + //read mark2 anchors + AnchorPoint[] anchors = new AnchorPoint[mark2Count * classCount]; + for (int i = 0; i < mark2Count * classCount; ++i) + { + anchors[i] = AnchorPoint.CreateFrom(reader, beginAt + offsets[i]); + } + return new Mark2ArrayTable(classCount, anchors); + } + + public AnchorPoint GetAnchorPoint(int index, int markClassId) + { + return _anchorPoints[index * _classCount + markClassId]; + } + + public Mark2ArrayTable(ushort classCount, AnchorPoint[] anchorPoints) + { + _classCount = classCount; + _anchorPoints = anchorPoints; + } + + internal readonly ushort _classCount; + internal readonly AnchorPoint[] _anchorPoints; + } + + class BaseArrayTable + { + //BaseArray table + //Value Type Description + //uint16 BaseCount Number of BaseRecords + //struct BaseRecord[BaseCount] Array of BaseRecords-in order of BaseCoverage Index + + //A BaseRecord declares one Anchor table for each mark class (including Class 0) + //identified in the MarkRecords of the MarkArray. + //Each Anchor table specifies one attachment point used to attach all the marks in a particular class to the base glyph. + //A BaseRecord contains an array of offsets to Anchor tables (BaseAnchor). + //The zero-based array of offsets defines the entire set of attachment points each base glyph uses to attach marks. + //The offsets to Anchor tables are ordered by mark class. + + // Note: Anchor tables are not tagged with class value identifiers. + //Instead, the index value of an Anchor table in the array defines the class value represented by the Anchor table. + + internal BaseRecord[] _records; + + public BaseRecord GetBaseRecords(int index) + { + return _records[index]; + } + public static BaseArrayTable CreateFrom(BinaryReader reader, long beginAt, ushort classCount) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //--- + var baseArrTable = new BaseArrayTable(); + ushort baseCount = reader.ReadUInt16(); + baseArrTable._records = new BaseRecord[baseCount]; + // Read all baseAnchorOffsets in one go + ushort[] baseAnchorOffsets = Utils.ReadUInt16Array(reader, classCount * baseCount); + for (int i = 0; i < baseCount; ++i) + { + AnchorPoint[] anchors = new AnchorPoint[classCount]; + BaseRecord baseRec = new BaseRecord(anchors); + + //each base has anchor point for mark glyph'class + for (int n = 0; n < classCount; ++n) + { + ushort offset = baseAnchorOffsets[i * classCount + n]; + if (offset <= 0) + { + //TODO: review here + //bug? + continue; + } + anchors[n] = AnchorPoint.CreateFrom(reader, beginAt + offset); + } + + baseArrTable._records[i] = baseRec; + } + return baseArrTable; + } + +#if DEBUG + public int dbugGetRecordCount() + { + return _records.Length; + } +#endif + } + + readonly struct BaseRecord + { + //BaseRecord + //Value Type Description + //Offset16 BaseAnchor[ClassCount] Array of offsets (one per class) to + //Anchor tables-from beginning of BaseArray table-ordered by class-zero-based + + public readonly AnchorPoint[] anchors; + + public BaseRecord(AnchorPoint[] anchors) + { + this.anchors = anchors; + } +#if DEBUG + public override string ToString() + { + StringBuilder stbuilder = new StringBuilder(); + if (anchors != null) + { + int i = 0; + foreach (AnchorPoint a in anchors) + { + if (i > 0) + { + stbuilder.Append(','); + } + if (a == null) + { + stbuilder.Append("null"); + } + else + { + stbuilder.Append(a.ToString()); + } + } + } + return stbuilder.ToString(); + } +#endif + } + + + // LigatureArray table + //Value Type Description + //USHORT LigatureCount Number of LigatureAttach table offsets + //Offset LigatureAttach + //[LigatureCount] Array of offsets to LigatureAttach tables-from beginning of LigatureArray table-ordered by LigatureCoverage Index + + //Each LigatureAttach table consists of an array (ComponentRecord) and count (ComponentCount) of the component glyphs in a ligature. The array stores the ComponentRecords in the same order as the components in the ligature. The order of the records also corresponds to the writing direction of the text. For text written left to right, the first component is on the left; for text written right to left, the first component is on the right. + //------------------------------- + //LigatureAttach table + //Value Type Description + //uint16 ComponentCount Number of ComponentRecords in this ligature + //struct ComponentRecord[ComponentCount] Array of Component records-ordered in writing direction + //------------------------------- + //A ComponentRecord, one for each component in the ligature, contains an array of offsets to the Anchor tables that define all the attachment points used to attach marks to the component (LigatureAnchor). For each mark class (including Class 0) identified in the MarkArray records, an Anchor table specifies the point used to attach all the marks in a particular class to the ligature base glyph, relative to the component. + + //In a ComponentRecord, the zero-based LigatureAnchor array lists offsets to Anchor tables by mark class. If a component does not define an attachment point for a particular class of marks, then the offset to the corresponding Anchor table will be NULL. + + //Example 8 at the end of this chapter shows a MarkLisPosFormat1 subtable used to attach mark accents to a ligature glyph in the Arabic script. + //------------------- + //ComponentRecord + //Value Type Description + //Offset16 LigatureAnchor[ClassCount] Array of offsets (one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array + class LigatureArrayTable + { + LigatureAttachTable[] _ligatures; + public void ReadFrom(BinaryReader reader, ushort classCount) + { + long startPos = reader.BaseStream.Position; + ushort ligatureCount = reader.ReadUInt16(); + ushort[] offsets = Utils.ReadUInt16Array(reader, ligatureCount); + + _ligatures = new LigatureAttachTable[ligatureCount]; + + for (int i = 0; i < ligatureCount; ++i) + { + //each ligature table + reader.BaseStream.Seek(startPos + offsets[i], SeekOrigin.Begin); + _ligatures[i] = LigatureAttachTable.ReadFrom(reader, classCount); + } + } + public LigatureAttachTable GetLigatureAttachTable(int index) => _ligatures[index]; + } + class LigatureAttachTable + { + //LigatureAttach table + //Value Type Description + //uint16 ComponentCount Number of ComponentRecords in this ligature + //struct ComponentRecord[ComponentCount] Array of Component records-ordered in writing direction + //------------------------------- + ComponentRecord[] _records; + public static LigatureAttachTable ReadFrom(BinaryReader reader, ushort classCount) + { + LigatureAttachTable table = new LigatureAttachTable(); + ushort componentCount = reader.ReadUInt16(); + ComponentRecord[] componentRecs = new ComponentRecord[componentCount]; + table._records = componentRecs; + for (int i = 0; i < componentCount; ++i) + { + componentRecs[i] = new ComponentRecord( + Utils.ReadUInt16Array(reader, classCount)); + } + return table; + } + public ComponentRecord GetComponentRecord(int index) => _records[index]; + } + readonly struct ComponentRecord + { + //ComponentRecord + //Value Type Description + //Offset16 LigatureAnchor[ClassCount] Array of offsets(one per class) to Anchor tables-from beginning of LigatureAttach table-ordered by class-NULL if a component does not have an attachment for a class-zero-based array + + public readonly ushort[] offsets; + public ComponentRecord(ushort[] offsets) + { + this.offsets = offsets; + } + + } + + //------ + readonly struct PosLookupRecord + { + + + //PosLookupRecord + //Value Type Description + //USHORT SequenceIndex Index to input glyph sequence-first glyph = 0 + //USHORT LookupListIndex Lookup to apply to that position-zero-based + + public readonly ushort seqIndex; + public readonly ushort lookupListIndex; + public PosLookupRecord(ushort seqIndex, ushort lookupListIndex) + { + this.seqIndex = seqIndex; + this.lookupListIndex = lookupListIndex; + } + public static PosLookupRecord CreateFrom(BinaryReader reader) + { + return new PosLookupRecord(reader.ReadUInt16(), reader.ReadUInt16()); + } + } + + + class PosRuleSetTable + { + + //PosRuleSet table: All contexts beginning with the same glyph + // Value Type Description + //uint16 PosRuleCount Number of PosRule tables + //Offset16 PosRule[PosRuleCount] Array of offsets to PosRule tables-from beginning of PosRuleSet-ordered by preference + // + //A PosRule table consists of a count of the glyphs to be matched in the input context sequence (GlyphCount), + //including the first glyph in the sequence, and an array of glyph indices that describe the context (Input). + //The Coverage table specifies the index of the first glyph in the context, and the Input array begins with the second glyph in the context sequence. As a result, the first index position in the array is specified with the number one (1), not zero (0). The Input array lists the indices in the order the corresponding glyphs appear in the text. For text written from right to left, the right-most glyph will be first; conversely, for text written from left to right, the left-most glyph will be first. + + //A PosRule table also contains a count of the positioning operations to be performed on the input glyph sequence (PosCount) and an array of PosLookupRecords (PosLookupRecord). Each record specifies a position in the input glyph sequence and a LookupList index to the positioning lookup to be applied there. The array should list records in design order, or the order the lookups should be applied to the entire glyph sequence. + + //Example 10 at the end of this chapter demonstrates glyph kerning in context with a ContextPosFormat1 subtable. + + PosRuleTable[] _posRuleTables; + void ReadFrom(BinaryReader reader) + { + long tableStartAt = reader.BaseStream.Position; + ushort posRuleCount = reader.ReadUInt16(); + ushort[] posRuleTableOffsets = Utils.ReadUInt16Array(reader, posRuleCount); + int j = posRuleTableOffsets.Length; + _posRuleTables = new PosRuleTable[posRuleCount]; + for (int i = 0; i < j; ++i) + { + //move to and read + reader.BaseStream.Seek(tableStartAt + posRuleTableOffsets[i], SeekOrigin.Begin); + var posRuleTable = new PosRuleTable(); + posRuleTable.ReadFrom(reader); + _posRuleTables[i] = posRuleTable; + + } + } + + public static PosRuleSetTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //------------ + var posRuleSetTable = new PosRuleSetTable(); + posRuleSetTable.ReadFrom(reader); + return posRuleSetTable; + } + } + class PosRuleTable + { + + //PosRule subtable + //Value Type Description + //uint16 GlyphCount Number of glyphs in the Input glyph sequence + //uint16 PosCount Number of PosLookupRecords + //uint16 Input[GlyphCount - 1] Array of input GlyphIDs-starting with the second glyph*** + //struct PosLookupRecord[PosCount] Array of positioning lookups-in design order + PosLookupRecord[] _posLookupRecords; + ushort[] _inputGlyphIds; + public void ReadFrom(BinaryReader reader) + { + ushort glyphCount = reader.ReadUInt16(); + ushort posCount = reader.ReadUInt16(); + _inputGlyphIds = Utils.ReadUInt16Array(reader, glyphCount - 1); + _posLookupRecords = CreateMultiplePosLookupRecords(reader, posCount); + } + } + + + class PosClassSetTable + { + //PosClassSet table: All contexts beginning with the same class + //Value Type Description + //---------------------- + //uint16 PosClassRuleCnt Number of PosClassRule tables + //Offset16 PosClassRule[PosClassRuleCnt] Array of offsets to PosClassRule tables-from beginning of PosClassSet-ordered by preference + //---------------------- + // + //For each context, a PosClassRule table contains a count of the glyph classes in a given context (GlyphCount), + //including the first class in the context sequence. + //A class array lists the classes, beginning with the second class, + //that follow the first class in the context. + //The first class listed indicates the second position in the context sequence. + + //Note: Text order depends on the writing direction of the text. + //For text written from right to left, the right-most glyph will be first. + //Conversely, for text written from left to right, the left-most glyph will be first. + + //The values specified in the Class array are those defined in the ClassDef table. + //For example, consider a context consisting of the sequence: Class 2, Class 7, Class 5, Class 0. + //The Class array will read: Class[0] = 7, Class[1] = 5, and Class[2] = 0. + //The first class in the sequence, Class 2, is defined by the index into the PosClassSet array of offsets. + //The total number and sequence of glyph classes listed in the Class array must match the total number and sequence of glyph classes contained in the input context. + + //A PosClassRule also contains a count of the positioning operations to be performed on the context (PosCount) and + //an array of PosLookupRecords (PosLookupRecord) that supply the positioning data. + //For each position in the context that requires a positioning operation, + //a PosLookupRecord specifies a LookupList index and a position in the input glyph class sequence where the lookup is applied. + //The PosLookupRecord array lists PosLookupRecords in design order, or the order in which lookups are applied to the entire glyph sequence. + + //Example 11 at the end of this chapter demonstrates a ContextPosFormat2 subtable that uses glyph classes to modify accent positions in glyph strings. + //---------------------- + //PosClassRule table: One class context definition + //---------------------- + //Value Type Description + //uint16 GlyphCount Number of glyphs to be matched + //uint16 PosCount Number of PosLookupRecords + //uint16 Class[GlyphCount - 1] Array of classes-beginning with the second class-to be matched to the input glyph sequence + //struct PosLookupRecord[PosCount] Array of positioning lookups-in design order + //---------------------- + + public PosClassRule[] PosClassRules; + + void ReadFrom(BinaryReader reader) + { + long tableStartAt = reader.BaseStream.Position; + // + ushort posClassRuleCnt = reader.ReadUInt16(); + ushort[] posClassRuleOffsets = Utils.ReadUInt16Array(reader, posClassRuleCnt); + PosClassRules = new PosClassRule[posClassRuleCnt]; + for (int i = 0; i < posClassRuleOffsets.Length; ++i) + { + //move to and read + PosClassRules[i] = PosClassRule.CreateFrom(reader, tableStartAt + posClassRuleOffsets[i]); + } + } + + public static PosClassSetTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //-------- + var posClassSetTable = new PosClassSetTable(); + posClassSetTable.ReadFrom(reader); + return posClassSetTable; + } + } + + class PosClassRule + { + public PosLookupRecord[] PosLookupRecords; + public ushort[] InputGlyphIds; + + public static PosClassRule CreateFrom(BinaryReader reader, long beginAt) + { + //-------- + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //-------- + PosClassRule posClassRule = new PosClassRule(); + ushort glyphCount = reader.ReadUInt16(); + ushort posCount = reader.ReadUInt16(); + if (glyphCount > 1) + { + posClassRule.InputGlyphIds = Utils.ReadUInt16Array(reader, glyphCount - 1); + } + + posClassRule.PosLookupRecords = CreateMultiplePosLookupRecords(reader, posCount); + return posClassRule; + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.cs new file mode 100644 index 00000000..4d4b337b --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GPOS.cs @@ -0,0 +1,1293 @@ +//Apache2, 2016-present, WinterDev, Sam Hocevar + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + + public partial class GPOS : GlyphShapingTableEntry + { + public const string _N = "GPOS"; + public override string Name => _N; + + /// + /// heuristic lookback optimization, + /// some layout-context may need=> eg. **Emoji**, some complex script + /// some layout-context may not need. + /// + public bool EnableLongLookBack { get; set; } + +#if DEBUG + public GPOS() { } +#endif + protected override void ReadLookupTable(BinaryReader reader, long lookupTablePos, + ushort lookupType, ushort lookupFlags, + ushort[] subTableOffsets, ushort markFilteringSet) + { + LookupTable lookupTable = new LookupTable(lookupFlags, markFilteringSet); + var subTables = new LookupSubTable[subTableOffsets.Length]; + lookupTable.SubTables = subTables; + + for (int i = 0; i < subTableOffsets.Length; ++i) + { + LookupSubTable subTable = LookupTable.ReadSubTable(lookupType, reader, lookupTablePos + subTableOffsets[i]); + subTable.OwnerGPos = this; + subTables[i] = subTable; + + + if (lookupType == 9) + { + //temp fix + // (eg. Emoji) => enable long look back + this.EnableLongLookBack = true; + } + } + + +#if DEBUG + lookupTable.dbugLkIndex = LookupList.Count; +#endif + + LookupList.Add(lookupTable); + } + + protected override void ReadFeatureVariations(BinaryReader reader, long featureVariationsBeginAt) + { + Utils.WarnUnimplemented("GPOS feature variations"); + } + + readonly List _lookupList = new List(); + + public IList LookupList => _lookupList; + + public abstract class LookupSubTable + { + public GPOS OwnerGPos; + public abstract void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len); + } + + /// + /// Subtable for unhandled/unimplemented features + /// + public class UnImplementedLookupSubTable : LookupSubTable + { + readonly string _msg; + + public UnImplementedLookupSubTable(string message) + { + _msg = message; + Utils.WarnUnimplemented(message); + } + public override string ToString() => _msg; + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) { } + } + + /// + /// sub table of a lookup list + /// + public partial class LookupTable + { +#if DEBUG + public int dbugLkIndex; +#endif + + + public readonly ushort lookupFlags; + public readonly ushort markFilteringSet; + //-------------------------- + LookupSubTable[] _subTables; + public LookupTable(ushort lookupFlags, ushort markFilteringSet) + { + this.lookupFlags = lookupFlags; + this.markFilteringSet = markFilteringSet; + } + public void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + foreach (LookupSubTable subTable in SubTables) + { + subTable.DoGlyphPosition(inputGlyphs, startAt, len); + //update len + len = inputGlyphs.Count; + } + } + public LookupSubTable[] SubTables + { + get => _subTables; + internal set => _subTables = value; + } + + public static LookupSubTable ReadSubTable(int lookupType, BinaryReader reader, long subTableStartAt) + { + switch (lookupType) + { + case 1: return ReadLookupType1(reader, subTableStartAt); + case 2: return ReadLookupType2(reader, subTableStartAt); + case 3: return ReadLookupType3(reader, subTableStartAt); + case 4: return ReadLookupType4(reader, subTableStartAt); + case 5: return ReadLookupType5(reader, subTableStartAt); + case 6: return ReadLookupType6(reader, subTableStartAt); + case 7: return ReadLookupType7(reader, subTableStartAt); + case 8: return ReadLookupType8(reader, subTableStartAt); + case 9: return ReadLookupType9(reader, subTableStartAt); + } + + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Type {0}", lookupType)); + } + + static int FindGlyphBackwardByKind(IGlyphPositions inputGlyphs, GlyphClassKind kind, int pos, int lim) + { + for (int i = pos; --i >= lim;) + { + if (inputGlyphs.GetGlyphClassKind(i) == kind) + { + return i; + } + } + return -1; + } + + class LkSubTableType1 : LookupSubTable + { + public LkSubTableType1(CoverageTable coverage, ValueRecord singleValue) + { + this.Format = 1; + _coverageTable = coverage; + _valueRecords = new ValueRecord[] { singleValue }; + } + + public LkSubTableType1(CoverageTable coverage, ValueRecord[] valueRecords) + { + this.Format = 2; + _coverageTable = coverage; + _valueRecords = valueRecords; + } + + public int Format { get; } + readonly CoverageTable _coverageTable; + readonly ValueRecord[] _valueRecords; + + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + int lim = Math.Min(startAt + len, inputGlyphs.Count); + for (int i = startAt; i < lim; ++i) + { + ushort glyph_index = inputGlyphs.GetGlyph(i, out short glyph_advW); + int cov_index = _coverageTable.FindPosition(glyph_index); + if (cov_index > -1) + { + var vr = _valueRecords[Format == 1 ? 0 : cov_index]; + inputGlyphs.AppendGlyphOffset(i, vr.XPlacement, vr.YPlacement); + inputGlyphs.AppendGlyphAdvance(i, vr.XAdvance, 0); + } + } + } + } + + /// + /// Lookup Type 1: Single Adjustment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType1(BinaryReader reader, long subTableStartAt) + { + // Single Adjustment Positioning: Format 1 + // Value Type Description + // uint16 PosFormat Format identifier-format = 1 + // Offset16 Coverage Offset to Coverage table-from beginning of SinglePos subtable + // uint16 ValueFormat Defines the types of data in the ValueRecord + // ValueRecord Value Defines positioning value(s)-applied to all glyphs in the Coverage table + + // Single Adjustment Positioning: Format 2 + // Value Type Description + // USHORT PosFormat Format identifier-format = 2 + // Offset16 Coverage Offset to Coverage table-from beginning of SinglePos subtable + // uint16 ValueFormat Defines the types of data in the ValueRecord + // uint16 ValueCount Number of ValueRecords + // ValueRecord Value[ValueCount] Array of ValueRecords-positioning values applied to glyphs + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + ushort coverage = reader.ReadUInt16(); + ushort valueFormat = reader.ReadUInt16(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + ValueRecord valueRecord = ValueRecord.CreateFrom(reader, valueFormat); + CoverageTable coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return new LkSubTableType1(coverageTable, valueRecord); + } + case 2: + { + ushort valueCount = reader.ReadUInt16(); + var valueRecords = new ValueRecord[valueCount]; + for (int n = 0; n < valueCount; ++n) + { + valueRecords[n] = ValueRecord.CreateFrom(reader, valueFormat); + } + CoverageTable coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return new LkSubTableType1(coverageTable, valueRecords); + } + } + } + + /// + /// Lookup Type 2, Format1: Pair Adjustment Positioning Subtable + /// + class LkSubTableType2Fmt1 : LookupSubTable + { + internal PairSetTable[] _pairSetTables; + public LkSubTableType2Fmt1(PairSetTable[] pairSetTables) + { + _pairSetTables = pairSetTables; + } + public CoverageTable CoverageTable { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + //find marker + CoverageTable covTable = this.CoverageTable; + int lim = inputGlyphs.Count - 1; + for (int i = 0; i < lim; ++i) + { + int firstGlyphFound = covTable.FindPosition(inputGlyphs.GetGlyph(i, out short glyph_advW)); + if (firstGlyphFound > -1) + { + //test this with Palatino A-Y sequence + PairSetTable pairSet = _pairSetTables[firstGlyphFound]; + + //check second glyph + ushort second_glyph_index = inputGlyphs.GetGlyph(i + 1, out short second_glyph_w); + + if (pairSet.FindPairSet(second_glyph_index, out PairSet foundPairSet)) + { + ValueRecord v1 = foundPairSet.value1; + ValueRecord v2 = foundPairSet.value2; + //TODO: recheck for vertical writing ... (YAdvance) + if (v1 != null) + { + inputGlyphs.AppendGlyphOffset(i, v1.XPlacement, v1.YPlacement); + inputGlyphs.AppendGlyphAdvance(i, v1.XAdvance, 0); + } + + if (v2 != null) + { + inputGlyphs.AppendGlyphOffset(i + 1, v2.XPlacement, v2.YPlacement); + inputGlyphs.AppendGlyphAdvance(i + 1, v2.XAdvance, 0); + } + } + } + } + } + } + + /// + /// Lookup Type2, Format2: Class pair adjustment + /// + class LkSubTableType2Fmt2 : LookupSubTable + { + //Format 2 defines a pair as a set of two glyph classes and modifies the positions of all the glyphs in a class + internal readonly Lk2Class1Record[] _class1records; + internal readonly ClassDefTable _class1Def; + internal readonly ClassDefTable _class2Def; + + public LkSubTableType2Fmt2(Lk2Class1Record[] class1records, ClassDefTable class1Def, ClassDefTable class2Def) + { + _class1records = class1records; + _class1Def = class1Def; + _class2Def = class2Def; + } + public CoverageTable CoverageTable { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + + //coverage + //The Coverage table lists the indices of the first glyphs that may appear in each glyph pair. + //More than one pair may begin with the same glyph, + //but the Coverage table lists the glyph index only once + + CoverageTable covTable = this.CoverageTable; + int lim = inputGlyphs.Count - 1; + for (int i = 0; i < lim; ++i) //start at 0 + { + ushort glyph1_index = inputGlyphs.GetGlyph(i, out short glyph_advW); + int record1Index = covTable.FindPosition(glyph1_index); + if (record1Index > -1) + { + int class1_no = _class1Def.GetClassValue(glyph1_index); + if (class1_no > -1) + { + ushort glyph2_index = inputGlyphs.GetGlyph(i + 1, out short glyph_advW2); + int class2_no = _class2Def.GetClassValue(glyph2_index); + + if (class2_no > -1) + { + Lk2Class1Record class1Rec = _class1records[class1_no]; + //TODO: recheck for vertical writing ... (YAdvance) + Lk2Class2Record pair = class1Rec.class2Records[class2_no]; + + ValueRecord v1 = pair.value1; + ValueRecord v2 = pair.value2; + + if (v1 != null) + { + inputGlyphs.AppendGlyphOffset(i, v1.XPlacement, v1.YPlacement); + inputGlyphs.AppendGlyphAdvance(i, v1.XAdvance, 0); + } + + if (v2 != null) + { + inputGlyphs.AppendGlyphOffset(i + 1, v2.XPlacement, v2.YPlacement); + inputGlyphs.AppendGlyphAdvance(i + 1, v2.XAdvance, 0); + } + } + } + } + + } + } + } + readonly struct Lk2Class1Record + { + // a Class1Record enumerates all pairs that contain a particular class as a first component. + //The Class1Record array stores all Class1Records according to class value. + + //Note: Class1Records are not tagged with a class value identifier. + //Instead, the index value of a Class1Record in the array defines the class value represented by the record. + //For example, the first Class1Record enumerates pairs that begin with a Class 0 glyph, + //the second Class1Record enumerates pairs that begin with a Class 1 glyph, and so on. + + //Each Class1Record contains an array of Class2Records (Class2Record), which also are ordered by class value. + //One Class2Record must be declared for each class in the ClassDef2 table, including Class 0. + //-------------------------------- + //Class1Record + //Value Type Description + //struct Class2Record[Class2Count] Array of Class2 records-ordered by Class2 + //-------------------------------- + public readonly Lk2Class2Record[] class2Records; + public Lk2Class1Record(Lk2Class2Record[] class2Records) + { + this.class2Records = class2Records; + } + //#if DEBUG + // public override string ToString() + // { + // System.Text.StringBuilder stbuilder = new System.Text.StringBuilder(); + // for (int i = 0; i < class2Records.Length; ++i) + // { + // Lk2Class2Record rec = class2Records[i]; + // string str = rec.ToString(); + + // if (str != "value1:,value2:") + // { + // //skip + // stbuilder.Append("i=" + i + "=>" + str + " "); + // } + // } + // return stbuilder.ToString(); + // //return base.ToString(); + // } + //#endif + } + + class Lk2Class2Record + { + //A Class2Record consists of two ValueRecords, + //one for the first glyph in a class pair (Value1) and one for the second glyph (Value2). + //If the PairPos subtable has a value of zero (0) for ValueFormat1 or ValueFormat2, + //the corresponding record (ValueRecord1 or ValueRecord2) will be empty. + + //Class2Record + //-------------------------------- + //Value Type Description + //ValueRecord Value1 Positioning for first glyph-empty if ValueFormat1 = 0 + //ValueRecord Value2 Positioning for second glyph-empty if ValueFormat2 = 0 + //-------------------------------- + public readonly ValueRecord value1;//null= empty + public readonly ValueRecord value2;//null= empty + + public Lk2Class2Record(ValueRecord value1, ValueRecord value2) + { + this.value1 = value1; + this.value2 = value2; + } + +#if DEBUG + public override string ToString() + { + return "value1:" + (value1?.ToString()) + ",value2:" + value2?.ToString(); + } +#endif + } + + /// + /// Lookup Type 2: Pair Adjustment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType2(BinaryReader reader, long subTableStartAt) + { + //A pair adjustment positioning subtable(PairPos) is used to adjust the positions of two glyphs + //in relation to one another-for instance, + //to specify kerning data for pairs of glyphs. + // + //Compared to a typical kerning table, however, a PairPos subtable offers more flexiblity and + //precise control over glyph positioning. + + //The PairPos subtable can adjust each glyph in a pair independently in both the X and Y directions, + //and it can explicitly describe the particular type of adjustment applied to each glyph. + // + //PairPos subtables can be either of two formats: + //1) one that identifies glyphs individually by index(Format 1), + //or 2) one that identifies glyphs by class (Format 2). + //----------------------------------------------- + //FORMAT1: + //Format 1 uses glyph indices to access positioning data for one or more specific pairs of glyphs + //All pairs are specified in the order determined by the layout direction of the text. + // + //Note: For text written from right to left, the right - most glyph will be the first glyph in a pair; + //conversely, for text written from left to right, the left - most glyph will be first. + // + //A PairPosFormat1 subtable contains a format identifier(PosFormat) and two ValueFormats: + //ValueFormat1 applies to the ValueRecord of the first glyph in each pair. + //ValueRecords for all first glyphs must use ValueFormat1. + //If ValueFormat1 is set to zero(0), + //the corresponding glyph has no ValueRecord and, therefore, should not be repositioned. + // + //ValueFormat2 applies to the ValueRecord of the second glyph in each pair. + //ValueRecords for all second glyphs must use ValueFormat2. + //If ValueFormat2 is set to null, then the second glyph of the pair is the “next” glyph for which a lookup should be performed. + // + //A PairPos subtable also defines an offset to a Coverage table(Coverage) that lists the indices of the first glyphs in each pair. + //More than one pair can have the same first glyph, but the Coverage table will list that glyph only once. + // + //The subtable also contains an array of offsets to PairSet tables(PairSet) and a count of the defined tables(PairSetCount). + //The PairSet array contains one offset for each glyph listed in the Coverage table and uses the same order as the Coverage Index. + + //----------------- + //PairPosFormat1 subtable: Adjustments for glyph pairs + //uint16 PosFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of PairPos subtable-only the first glyph in each pair + //uint16 ValueFormat1 Defines the types of data in ValueRecord1-for the first glyph in the pair -may be zero (0) + //uint16 ValueFormat2 Defines the types of data in ValueRecord2-for the second glyph in the pair -may be zero (0) + //uint16 PairSetCount Number of PairSet tables + //Offset16 PairSetOffset[PairSetCount] Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index // + //----------------- + // + //PairSet table + //Value Type Description + //uint16 PairValueCount Number of PairValueRecords + //struct PairValueRecord[PairValueCount] Array of PairValueRecords-ordered by GlyphID of the second glyph + //----------------- + //A PairValueRecord specifies the second glyph in a pair (SecondGlyph) and defines a ValueRecord for each glyph (Value1 and Value2). + //If ValueFormat1 is set to zero (0) in the PairPos subtable, ValueRecord1 will be empty; similarly, if ValueFormat2 is 0, Value2 will be empty. + + + //PairValueRecord + //Value Type Description + //GlyphID SecondGlyph GlyphID of second glyph in the pair-first glyph is listed in the Coverage table + //ValueRecord Value1 Positioning data for the first glyph in the pair + //ValueRecord Value2 Positioning data for the second glyph in the pair + //----------------------------------------------- + + //PairPosFormat2 subtable: Class pair adjustment + //Value Type Description + //uint16 PosFormat Format identifier-format = 2 + //Offset16 Coverage Offset to Coverage table-from beginning of PairPos subtable-for the first glyph of the pair + //uint16 ValueFormat1 ValueRecord definition-for the first glyph of the pair-may be zero (0) + //uint16 ValueFormat2 ValueRecord definition-for the second glyph of the pair-may be zero (0) + //Offset16 ClassDef1 Offset to ClassDef table-from beginning of PairPos subtable-for the first glyph of the pair + //Offset16 ClassDef2 Offset to ClassDef table-from beginning of PairPos subtable-for the second glyph of the pair + //uint16 Class1Count Number of classes in ClassDef1 table-includes Class0 + //uint16 Class2Count Number of classes in ClassDef2 table-includes Class0 + //struct Class1Record[Class1Count] Array of Class1 records-ordered by Class1 + + //Each Class1Record contains an array of Class2Records (Class2Record), which also are ordered by class value. + //One Class2Record must be declared for each class in the ClassDef2 table, including Class 0. + //-------------------------------- + //Class1Record + //Value Type Description + //struct Class2Record[Class2Count] Array of Class2 records-ordered by Class2 + //-------------------------------- + + //A Class2Record consists of two ValueRecords, + //one for the first glyph in a class pair (Value1) and one for the second glyph (Value2). + //If the PairPos subtable has a value of zero (0) for ValueFormat1 or ValueFormat2, + //the corresponding record (ValueRecord1 or ValueRecord2) will be empty. + + + //Class2Record + //-------------------------------- + //Value Type Description + //ValueRecord Value1 Positioning for first glyph-empty if ValueFormat1 = 0 + //ValueRecord Value2 Positioning for second glyph-empty if ValueFormat2 = 0 + //-------------------------------- + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + switch (format) + { + default: + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Table Type 2 Format {0}", format)); + case 1: + { + ushort coverage = reader.ReadUInt16(); + ushort value1Format = reader.ReadUInt16(); + ushort value2Format = reader.ReadUInt16(); + ushort pairSetCount = reader.ReadUInt16(); + ushort[] pairSetOffsetArray = Utils.ReadUInt16Array(reader, pairSetCount); + PairSetTable[] pairSetTables = new PairSetTable[pairSetCount]; + for (int n = 0; n < pairSetCount; ++n) + { + reader.BaseStream.Seek(subTableStartAt + pairSetOffsetArray[n], SeekOrigin.Begin); + var pairSetTable = new PairSetTable(); + pairSetTable.ReadFrom(reader, value1Format, value2Format); + pairSetTables[n] = pairSetTable; + } + var subTable = new LkSubTableType2Fmt1(pairSetTables); + //coverage + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return subTable; + } + case 2: + { + ushort coverage = reader.ReadUInt16(); + ushort value1Format = reader.ReadUInt16(); + ushort value2Format = reader.ReadUInt16(); + ushort classDef1_offset = reader.ReadUInt16(); + ushort classDef2_offset = reader.ReadUInt16(); + ushort class1Count = reader.ReadUInt16(); + ushort class2Count = reader.ReadUInt16(); + + Lk2Class1Record[] class1Records = new Lk2Class1Record[class1Count]; + for (int c1 = 0; c1 < class1Count; ++c1) + { + //for each c1 record + + Lk2Class2Record[] class2Records = new Lk2Class2Record[class2Count]; + for (int c2 = 0; c2 < class2Count; ++c2) + { + class2Records[c2] = new Lk2Class2Record( + ValueRecord.CreateFrom(reader, value1Format), + ValueRecord.CreateFrom(reader, value2Format)); + } + class1Records[c1] = new Lk2Class1Record(class2Records); + } + + var subTable = new LkSubTableType2Fmt2(class1Records, + ClassDefTable.CreateFrom(reader, subTableStartAt + classDef1_offset), + ClassDefTable.CreateFrom(reader, subTableStartAt + classDef2_offset)); + + + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return subTable; + } + } + } + + /// + /// Lookup Type 3: Cursive Attachment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType3(BinaryReader reader, long subTableStartAt) + { + // TODO: implement this + + return new UnImplementedLookupSubTable("GPOS Lookup Table Type 3"); + } + + /// + /// Lookup Type 4: MarkToBase Attachment Positioning, or called (MarkBasePos) table + /// + class LkSubTableType4 : LookupSubTable + { + public CoverageTable MarkCoverageTable { get; set; } + public CoverageTable BaseCoverageTable { get; set; } + public BaseArrayTable BaseArrayTable { get; set; } + public MarkArrayTable MarkArrayTable { get; set; } + + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + int lim = Math.Min(startAt + len, inputGlyphs.Count); + + // Find the mark glyph, starting at 1 + bool longLookBack = this.OwnerGPos.EnableLongLookBack; + for (int i = Math.Max(startAt, 1); i < lim; ++i) + { + int markFound = MarkCoverageTable.FindPosition(inputGlyphs.GetGlyph(i, out short glyph_advW)); + if (markFound < 0) + { + continue; + } + + // Look backwards for the base glyph + int j = FindGlyphBackwardByKind(inputGlyphs, GlyphClassKind.Base, i, longLookBack ? startAt : i - 1); + if (j < 0) + { + // Fall back to type 0 + j = FindGlyphBackwardByKind(inputGlyphs, GlyphClassKind.Zero, i, longLookBack ? startAt : i - 1); + if (j < 0) + { + continue; + } + } + + ushort prev_glyph = inputGlyphs.GetGlyph(j, out short prev_glyph_adv_w); + int baseFound = BaseCoverageTable.FindPosition(prev_glyph); + if (baseFound < 0) + { + continue; + } + + BaseRecord baseRecord = BaseArrayTable.GetBaseRecords(baseFound); + ushort markClass = MarkArrayTable.GetMarkClass(markFound); + // find anchor on base glyph + AnchorPoint anchor = MarkArrayTable.GetAnchorPoint(markFound); + AnchorPoint prev_anchor = baseRecord.anchors[markClass]; + inputGlyphs.GetOffset(j, out short prev_glyph_xoffset, out short prev_glyph_yoffset); + inputGlyphs.GetOffset(i, out short glyph_xoffset, out short glyph_yoffset); + int xoffset = prev_glyph_xoffset + prev_anchor.xcoord - (prev_glyph_adv_w + glyph_xoffset + anchor.xcoord); + int yoffset = prev_glyph_yoffset + prev_anchor.ycoord - (glyph_yoffset + anchor.ycoord); + inputGlyphs.AppendGlyphOffset(i, (short)xoffset, (short)yoffset); + } + } + +#if DEBUG + public void dbugTest() + { + //count base covate + List expandedMarks = new List(MarkCoverageTable.GetExpandedValueIter()); + if (expandedMarks.Count != MarkArrayTable.dbugGetAnchorCount()) + { + throw new OpenFontNotSupportedException(); + } + //-------------------------- + List expandedBase = new List(BaseCoverageTable.GetExpandedValueIter()); + if (expandedBase.Count != BaseArrayTable.dbugGetRecordCount()) + { + throw new OpenFontNotSupportedException(); + } + } +#endif + } + + /// + /// Lookup Type 4: MarkToBase Attachment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType4(BinaryReader reader, long subTableStartAt) + { + //The MarkToBase attachment (MarkBasePos) subtable is used to position combining mark glyphs with respect to base glyphs. + //For example, the Arabic, Hebrew, and Thai scripts combine vowels, diacritical marks, and tone marks with base glyphs. + + //In the MarkBasePos subtable, every mark glyph has an anchor point and is associated with a class of marks. + //Each base glyph then defines an anchor point for each class of marks it uses. + + //For example, assume two mark classes: all marks positioned above base glyphs (Class 0), + //and all marks positioned below base glyphs (Class 1). + //In this case, each base glyph that uses these marks would define two anchor points, + //one for attaching the mark glyphs listed in Class 0, + //and one for attaching the mark glyphs listed in Class 1. + + //To identify the base glyph that combines with a mark, + //the text-processing client must look backward in the glyph string from the mark to the preceding base glyph. + //To combine the mark and base glyph, the client aligns their attachment points, + //positioning the mark with respect to the final pen point (advance) position of the base glyph. + + //The MarkToBase Attachment subtable has one format: MarkBasePosFormat1. + //The subtable begins with a format identifier (PosFormat) and + //offsets to two Coverage tables: one that lists all the mark glyphs referenced in the subtable (MarkCoverage), + //and one that lists all the base glyphs referenced in the subtable (BaseCoverage). + + //For each mark glyph in the MarkCoverage table, + //a record specifies its class and an offset to the Anchor table that describes the mark's attachment point (MarkRecord). + //A mark class is identified by a specific integer, called a class value. + //ClassCount specifies the total number of distinct mark classes defined in all the MarkRecords. + + //The MarkBasePosFormat1 subtable also contains an offset to a MarkArray table, + //which contains all the MarkRecords stored in an array (MarkRecord) by MarkCoverage Index. + //A MarkArray table also contains a count of the defined MarkRecords (MarkCount). + //(For details about MarkArrays and MarkRecords, see the end of this chapter.) + + //The MarkBasePosFormat1 subtable also contains an offset to a BaseArray table (BaseArray). + + //MarkBasePosFormat1 subtable: MarkToBase attachment point + //---------------------------------------------- + //Value Type Description + //uint16 PosFormat Format identifier-format = 1 + //Offset16 MarkCoverage Offset to MarkCoverage table-from beginning of MarkBasePos subtable ( all the mark glyphs referenced in the subtable) + //Offset16 BaseCoverage Offset to BaseCoverage table-from beginning of MarkBasePos subtable (all the base glyphs referenced in the subtable) + //uint16 ClassCount Number of classes defined for marks + //Offset16 MarkArray Offset to MarkArray table-from beginning of MarkBasePos subtable + //Offset16 BaseArray Offset to BaseArray table-from beginning of MarkBasePos subtable + //---------------------------------------------- + + //The BaseArray table consists of an array (BaseRecord) and count (BaseCount) of BaseRecords. + //The array stores the BaseRecords in the same order as the BaseCoverage Index. + //Each base glyph in the BaseCoverage table has a BaseRecord. + + //BaseArray table + //Value Type Description + //uint16 BaseCount Number of BaseRecords + //struct BaseRecord[BaseCount] Array of BaseRecords-in order of BaseCoverage Index + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + if (format != 1) + { + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Sub Table Type 4 Format {0}", format)); + } + ushort markCoverageOffset = reader.ReadUInt16(); //offset from + ushort baseCoverageOffset = reader.ReadUInt16(); + ushort markClassCount = reader.ReadUInt16(); + ushort markArrayOffset = reader.ReadUInt16(); + ushort baseArrayOffset = reader.ReadUInt16(); + + //read mark array table + var lookupType4 = new LkSubTableType4(); + lookupType4.MarkCoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + markCoverageOffset); + lookupType4.BaseCoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + baseCoverageOffset); + lookupType4.MarkArrayTable = MarkArrayTable.CreateFrom(reader, subTableStartAt + markArrayOffset); + lookupType4.BaseArrayTable = BaseArrayTable.CreateFrom(reader, subTableStartAt + baseArrayOffset, markClassCount); +#if DEBUG + //lookupType4.dbugTest(); +#endif + return lookupType4; + } + + + //Lookup Type 5: MarkToLigature Attachment Positioning Subtable + class LkSubTableType5 : LookupSubTable + { + public CoverageTable MarkCoverage { get; set; } + public CoverageTable LigatureCoverage { get; set; } + public MarkArrayTable MarkArrayTable { get; set; } + public LigatureArrayTable LigatureArrayTable { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + Utils.WarnUnimplemented("GPOS Lookup Sub Table Type 5"); + } + } + + /// + /// Lookup Type 5: MarkToLigature Attachment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType5(BinaryReader reader, long subTableStartAt) + { + //uint16 PosFormat Format identifier-format = 1 + //Offset16 MarkCoverage Offset to Mark Coverage table-from beginning of MarkLigPos subtable + //Offset16 LigatureCoverage Offset to Ligature Coverage table-from beginning of MarkLigPos subtable + //uint16 ClassCount Number of defined mark classes + //Offset16 MarkArray Offset to MarkArray table-from beginning of MarkLigPos subtable + //Offset16 LigatureArray Offset to LigatureArray table-from beginning of MarkLigPos subtable + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + if (format != 1) + { + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Sub Table Type 5 Format {0}", format)); + } + ushort markCoverageOffset = reader.ReadUInt16(); //from beginning of MarkLigPos subtable + ushort ligatureCoverageOffset = reader.ReadUInt16(); + ushort classCount = reader.ReadUInt16(); + ushort markArrayOffset = reader.ReadUInt16(); + ushort ligatureArrayOffset = reader.ReadUInt16(); + //----------------------- + var subTable = new LkSubTableType5(); + subTable.MarkCoverage = CoverageTable.CreateFrom(reader, subTableStartAt + markCoverageOffset); + subTable.LigatureCoverage = CoverageTable.CreateFrom(reader, subTableStartAt + ligatureCoverageOffset); + subTable.MarkArrayTable = MarkArrayTable.CreateFrom(reader, subTableStartAt + markArrayOffset); + + reader.BaseStream.Seek(subTableStartAt + ligatureArrayOffset, SeekOrigin.Begin); + var ligatureArrayTable = new LigatureArrayTable(); + ligatureArrayTable.ReadFrom(reader, classCount); + subTable.LigatureArrayTable = ligatureArrayTable; + + return subTable; + } + + //----------------------------------------------------------------- + //https://docs.microsoft.com/en-us/typography/opentype/otspec180/gpos#lookup-type-6--marktomark-attachment-positioning-subtable + /// + /// Lookup Type 6: MarkToMark Attachment + /// defines the position of one mark relative to another mark + /// + class LkSubTableType6 : LookupSubTable + { + public CoverageTable MarkCoverage1 { get; set; } + public CoverageTable MarkCoverage2 { get; set; } + public MarkArrayTable Mark1ArrayTable { get; set; } + public Mark2ArrayTable Mark2ArrayTable { get; set; } // Mark2 attachment points used to attach Mark1 glyphs to a specific Mark2 glyph. + + + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + //The attaching mark is Mark1, + //and the base mark being attached to is Mark2. + + //The Mark2 glyph (that combines with a Mark1 glyph) is the glyph preceding the Mark1 glyph in glyph string order + //(skipping glyphs according to LookupFlags) + + //@prepare: we must found mark2 glyph before mark1 + bool longLookBack = this.OwnerGPos.EnableLongLookBack; +#if DEBUG + if (len == 3 || len == 4) + { + + } +#endif + //find marker + int lim = Math.Min(startAt + len, inputGlyphs.Count); + + for (int i = Math.Max(startAt, 1); i < lim; ++i) + { + // Find first mark glyph + int mark1Found = MarkCoverage1.FindPosition(inputGlyphs.GetGlyph(i, out short glyph_adv_w)); + if (mark1Found < 0) + { + continue; + } + + // Look back for previous mark glyph + int prev_mark = FindGlyphBackwardByKind(inputGlyphs, GlyphClassKind.Mark, i, longLookBack ? startAt : i - 1); + if (prev_mark < 0) + { + continue; + } + + int mark2Found = MarkCoverage2.FindPosition(inputGlyphs.GetGlyph(prev_mark, out short prev_pos_adv_w)); + if (mark2Found < 0) + { + continue; + } + + // Examples: + // 👨🏻‍👩🏿‍👧🏽‍👦🏽‍👦🏿 in Segoe UI Emoji + + int mark1ClassId = Mark1ArrayTable.GetMarkClass(mark1Found); + AnchorPoint prev_anchor = Mark2ArrayTable.GetAnchorPoint(mark2Found, mark1ClassId); + AnchorPoint anchor = Mark1ArrayTable.GetAnchorPoint(mark1Found); + if (anchor.ycoord < 0) + { + //temp HACK! น้ำ in Tahoma + inputGlyphs.AppendGlyphOffset(prev_mark /*PREV*/, anchor.xcoord, anchor.ycoord); + } + else + { + inputGlyphs.GetOffset(prev_mark, out short prev_glyph_xoffset, out short prev_glyph_yoffset); + inputGlyphs.GetOffset(i, out short glyph_xoffset, out short glyph_yoffset); + int xoffset = prev_glyph_xoffset + prev_anchor.xcoord - (prev_pos_adv_w + glyph_xoffset + anchor.xcoord); + int yoffset = prev_glyph_yoffset + prev_anchor.ycoord - (glyph_yoffset + anchor.ycoord); + inputGlyphs.AppendGlyphOffset(i, (short)xoffset, (short)yoffset); + } + } + } + } + + /// + /// Lookup Type 6: MarkToMark Attachment Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType6(BinaryReader reader, long subTableStartAt) + { + // uint16 PosFormat Format identifier-format = 1 + // Offset16 Mark1Coverage Offset to Combining Mark Coverage table-from beginning of MarkMarkPos subtable + // Offset16 Mark2Coverage Offset to Base Mark Coverage table-from beginning of MarkMarkPos subtable + // uint16 ClassCount Number of Combining Mark classes defined + // Offset16 Mark1Array Offset to MarkArray table for Mark1-from beginning of MarkMarkPos subtable + // Offset16 Mark2Array Offset to Mark2Array table for Mark2-from beginning of MarkMarkPos subtable + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + if (format != 1) + { + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Sub Table Type 6 Format {0}", format)); + } + ushort mark1CoverageOffset = reader.ReadUInt16(); + ushort mark2CoverageOffset = reader.ReadUInt16(); + ushort classCount = reader.ReadUInt16(); + ushort mark1ArrayOffset = reader.ReadUInt16(); + ushort mark2ArrayOffset = reader.ReadUInt16(); + // + var subTable = new LkSubTableType6(); + subTable.MarkCoverage1 = CoverageTable.CreateFrom(reader, subTableStartAt + mark1CoverageOffset); + subTable.MarkCoverage2 = CoverageTable.CreateFrom(reader, subTableStartAt + mark2CoverageOffset); + subTable.Mark1ArrayTable = MarkArrayTable.CreateFrom(reader, subTableStartAt + mark1ArrayOffset); + subTable.Mark2ArrayTable = Mark2ArrayTable.CreateFrom(reader, subTableStartAt + mark2ArrayOffset, classCount); + + return subTable; + } + + /// + /// Lookup Type 7: Contextual Positioning Subtables + /// + /// + static LookupSubTable ReadLookupType7(BinaryReader reader, long subTableStartAt) + { + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + switch (format) + { + default: + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Sub Table Type 7 Format {0}", format)); + case 1: + { + //Context Positioning Subtable: Format 1 + //ContextPosFormat1 subtable: Simple context positioning + //Value Type Description + //uint16 PosFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of ContextPos subtable + //uint16 PosRuleSetCount Number of PosRuleSet tables + //Offset16 PosRuleSet[PosRuleSetCount] + // + ushort coverageOffset = reader.ReadUInt16(); + ushort posRuleSetCount = reader.ReadUInt16(); + ushort[] posRuleSetOffsets = Utils.ReadUInt16Array(reader, posRuleSetCount); + + LkSubTableType7Fmt1 subTable = new LkSubTableType7Fmt1(); + subTable.PosRuleSetTables = CreateMultiplePosRuleSetTables(subTableStartAt, posRuleSetOffsets, reader); + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + return subTable; + } + case 2: + { + //Context Positioning Subtable: Format 2 + //uint16 PosFormat Format identifier-format = 2 + //Offset16 Coverage Offset to Coverage table-from beginning of ContextPos subtable + //Offset16 ClassDef Offset to ClassDef table-from beginning of ContextPos subtable + //uint16 PosClassSetCnt Number of PosClassSet tables + //Offset16 PosClassSet[PosClassSetCnt] Array of offsets to PosClassSet tables-from beginning of ContextPos subtable-ordered by class-may be NULL + + ushort coverageOffset = reader.ReadUInt16(); + ushort classDefOffset = reader.ReadUInt16(); + ushort posClassSetCount = reader.ReadUInt16(); + ushort[] posClassSetOffsets = Utils.ReadUInt16Array(reader, posClassSetCount); + + var subTable = new LkSubTableType7Fmt2(); + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + subTable.ClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + classDefOffset); + + PosClassSetTable[] posClassSetTables = new PosClassSetTable[posClassSetCount]; + subTable.PosClassSetTables = posClassSetTables; + for (int n = 0; n < posClassSetCount; ++n) + { + ushort offset = posClassSetOffsets[n]; + if (offset > 0) + { + posClassSetTables[n] = PosClassSetTable.CreateFrom(reader, subTableStartAt + offset); + } + } + return subTable; + } + case 3: + { + //ContextPosFormat3 subtable: Coverage-based context glyph positioning + //Value Type Description + //uint16 PosFormat Format identifier-format = 3 + //uint16 GlyphCount Number of glyphs in the input sequence + //uint16 PosCount Number of PosLookupRecords + //Offset16 Coverage[GlyphCount] Array of offsets to Coverage tables-from beginning of ContextPos subtable + //struct PosLookupRecord[PosCount] Array of positioning lookups-in design order + var subTable = new LkSubTableType7Fmt3(); + ushort glyphCount = reader.ReadUInt16(); + ushort posCount = reader.ReadUInt16(); + //read each lookahead record + ushort[] coverageOffsets = Utils.ReadUInt16Array(reader, glyphCount); + subTable.PosLookupRecords = CreateMultiplePosLookupRecords(reader, posCount); + subTable.CoverageTables = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, coverageOffsets, reader); + + return subTable; + } + } + } + + class LkSubTableType7Fmt1 : LookupSubTable + { + public CoverageTable CoverageTable { get; set; } + public PosRuleSetTable[] PosRuleSetTables { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + Utils.WarnUnimplemented("GPOS Lookup Sub Table Type 7 Format 1"); + } + } + + class LkSubTableType7Fmt2 : LookupSubTable + { + public ClassDefTable ClassDef { get; set; } + public CoverageTable CoverageTable { get; set; } + public PosClassSetTable[] PosClassSetTables { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + int lim = Math.Min(startAt + len, inputGlyphs.Count); + for (int i = startAt; i < lim; ++i) + { + ushort glyph1_index = inputGlyphs.GetGlyph(i, out short unused); + if (CoverageTable.FindPosition(glyph1_index) < 0) + { + continue; + } + + int glyph1_class = ClassDef.GetClassValue(glyph1_index); + if (glyph1_class >= PosClassSetTables.Length || PosClassSetTables[glyph1_class] == null) + { + continue; + } + + foreach (PosClassRule rule in PosClassSetTables[glyph1_class].PosClassRules) + { + ushort[] glyphIds = rule.InputGlyphIds; + int matches = 0; + for (int n = 0; n < glyphIds.Length && i + 1 + n < lim; ++n) + { + ushort glyphn_index = inputGlyphs.GetGlyph(i + 1 + n, out unused); + int glyphn_class = ClassDef.GetClassValue(glyphn_index); + if (glyphn_class != glyphIds[n]) + { + break; + } + ++matches; + } + + if (matches == glyphIds.Length) + { + foreach (PosLookupRecord plr in rule.PosLookupRecords) + { + LookupTable lookup = OwnerGPos.LookupList[plr.lookupListIndex]; + lookup.DoGlyphPosition(inputGlyphs, i + plr.seqIndex, glyphIds.Length - plr.seqIndex); + } + break; + } + } + } + } + } + class LkSubTableType7Fmt3 : LookupSubTable + { + public CoverageTable[] CoverageTables { get; set; } + public PosLookupRecord[] PosLookupRecords { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + Utils.WarnUnimplemented("GPOS Lookup Sub Table Type 7 Format 3"); + } + } + //---------------------------------------------------------------- + class LkSubTableType8Fmt1 : LookupSubTable + { + public CoverageTable CoverageTable { get; set; } + public PosRuleSetTable[] PosRuleSetTables { get; set; } + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + Utils.WarnUnimplemented("GPOS Lookup Sub Table Type 8 Format 1"); + } + } + + class LkSubTableType8Fmt2 : LookupSubTable + { + public LkSubTableType8Fmt2() + { + + } + public CoverageTable CoverageTable { get; set; } + public PosClassSetTable[] PosClassSetTables { get; set; } + + public ClassDefTable BackTrackClassDef { get; set; } + public ClassDefTable InputClassDef { get; set; } + public ClassDefTable LookaheadClassDef { get; set; } + + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + ushort glyphIndex = inputGlyphs.GetGlyph(startAt, out short advW); + + int coverage_pos = CoverageTable.FindPosition(glyphIndex); + if (coverage_pos < 0) { return; } + + + Utils.WarnUnimplemented("GPOS Lookup Sub Table Type 8 Format 2"); + } + } + + class LkSubTableType8Fmt3 : LookupSubTable + { + public CoverageTable[] BacktrackCoverages { get; set; } + public CoverageTable[] InputGlyphCoverages { get; set; } + public CoverageTable[] LookaheadCoverages { get; set; } + public PosLookupRecord[] PosLookupRecords { get; set; } + + public override void DoGlyphPosition(IGlyphPositions inputGlyphs, int startAt, int len) + { + startAt = Math.Max(startAt, BacktrackCoverages.Length); + int lim = Math.Min(startAt + len, inputGlyphs.Count) - (InputGlyphCoverages.Length - 1) - LookaheadCoverages.Length; + for (int pos = startAt; pos < lim; ++pos) + { + DoGlyphPositionAt(inputGlyphs, pos); + } + } + + protected void DoGlyphPositionAt(IGlyphPositions inputGlyphs, int pos) + { + // Check all coverages: if any of them does not match, abort substitution + for (int i = 0; i < InputGlyphCoverages.Length; ++i) + { + if (InputGlyphCoverages[i].FindPosition(inputGlyphs.GetGlyph(pos + i, out var unused)) < 0) + { + return; + } + } + + for (int i = 0; i < BacktrackCoverages.Length; ++i) + { + if (BacktrackCoverages[i].FindPosition(inputGlyphs.GetGlyph(pos - 1 - i, out var unused)) < 0) + { + return; + } + } + + for (int i = 0; i < LookaheadCoverages.Length; ++i) + { + if (LookaheadCoverages[i].FindPosition(inputGlyphs.GetGlyph(pos + InputGlyphCoverages.Length + i, out var unused)) < 0) + { + return; + } + } + + foreach (var plr in PosLookupRecords) + { + var lookup = OwnerGPos.LookupList[plr.lookupListIndex]; + lookup.DoGlyphPosition(inputGlyphs, pos + plr.seqIndex, InputGlyphCoverages.Length - plr.seqIndex); + } + } + } + + /// + /// LookupType 8: Chaining Contextual Positioning Subtable + /// + /// + static LookupSubTable ReadLookupType8(BinaryReader reader, long subTableStartAt) + { + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + switch (format) + { + default: + return new UnImplementedLookupSubTable(string.Format("GPOS Lookup Table Type 8 Format {0}", format)); + case 1: + { + //Chaining Context Positioning Format 1: Simple Chaining Context Glyph Positioning + //uint16 PosFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of ContextPos subtable + //uint16 ChainPosRuleSetCount Number of ChainPosRuleSet tables + //Offset16 ChainPosRuleSet[ChainPosRuleSetCount] Array of offsets to ChainPosRuleSet tables-from beginning of ContextPos subtable-ordered by Coverage Index + + ushort coverageOffset = reader.ReadUInt16(); + ushort chainPosRuleSetCount = reader.ReadUInt16(); + ushort[] chainPosRuleSetOffsetList = Utils.ReadUInt16Array(reader, chainPosRuleSetCount); + + LkSubTableType8Fmt1 subTable = new LkSubTableType8Fmt1(); + subTable.PosRuleSetTables = CreateMultiplePosRuleSetTables(subTableStartAt, chainPosRuleSetOffsetList, reader); + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + return subTable; + } + case 2: + { + //Chaining Context Positioning Format 2: Class-based Chaining Context Glyph Positioning + //uint16 PosFormat Format identifier-format = 2 + //Offset16 Coverage Offset to Coverage table-from beginning of ChainContextPos subtable + //Offset16 BacktrackClassDef Offset to ClassDef table containing backtrack sequence context-from beginning of ChainContextPos subtable + //Offset16 InputClassDef Offset to ClassDef table containing input sequence context-from beginning of ChainContextPos subtable + //Offset16 LookaheadClassDef Offset to ClassDef table containing lookahead sequence context-from beginning of ChainContextPos subtable + //uint16 ChainPosClassSetCnt Number of ChainPosClassSet tables + //Offset16 ChainPosClassSet[ChainPosClassSetCnt] Array of offsets to ChainPosClassSet tables-from beginning of ChainContextPos subtable-ordered by input class-may be NULL + + ushort coverageOffset = reader.ReadUInt16(); + ushort backTrackClassDefOffset = reader.ReadUInt16(); + ushort inputClassDefOffset = reader.ReadUInt16(); + ushort lookadheadClassDefOffset = reader.ReadUInt16(); + ushort chainPosClassSetCnt = reader.ReadUInt16(); + ushort[] chainPosClassSetOffsetArray = Utils.ReadUInt16Array(reader, chainPosClassSetCnt); + + LkSubTableType8Fmt2 subTable = new LkSubTableType8Fmt2(); + subTable.BackTrackClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + backTrackClassDefOffset); + subTable.InputClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + inputClassDefOffset); + subTable.LookaheadClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + lookadheadClassDefOffset); + + //---------- + PosClassSetTable[] posClassSetTables = new PosClassSetTable[chainPosClassSetCnt]; + for (int n = 0; n < chainPosClassSetCnt; ++n) + { + ushort offset = chainPosClassSetOffsetArray[n]; + if (offset > 0) + { + posClassSetTables[n] = PosClassSetTable.CreateFrom(reader, subTableStartAt + offset); + } + + } + subTable.PosClassSetTables = posClassSetTables; + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + + return subTable; + } + case 3: + { + //Chaining Context Positioning Format 3: Coverage-based Chaining Context Glyph Positioning + //uint16 PosFormat Format identifier-format = 3 + //uint16 BacktrackGlyphCount Number of glyphs in the backtracking sequence + //Offset16 Coverage[BacktrackGlyphCount] Array of offsets to coverage tables in backtracking sequence, in glyph sequence order + //uint16 InputGlyphCount Number of glyphs in input sequence + //Offset16 Coverage[InputGlyphCount] Array of offsets to coverage tables in input sequence, in glyph sequence order + //uint16 LookaheadGlyphCount Number of glyphs in lookahead sequence + //Offset16 Coverage[LookaheadGlyphCount] Array of offsets to coverage tables in lookahead sequence, in glyph sequence order + //uint16 PosCount Number of PosLookupRecords + //struct PosLookupRecord[PosCount] Array of PosLookupRecords,in design order + + var subTable = new LkSubTableType8Fmt3(); + + ushort backtrackGlyphCount = reader.ReadUInt16(); + ushort[] backtrackCoverageOffsets = Utils.ReadUInt16Array(reader, backtrackGlyphCount); + ushort inputGlyphCount = reader.ReadUInt16(); + ushort[] inputGlyphCoverageOffsets = Utils.ReadUInt16Array(reader, inputGlyphCount); + ushort lookaheadGlyphCount = reader.ReadUInt16(); + ushort[] lookaheadCoverageOffsets = Utils.ReadUInt16Array(reader, lookaheadGlyphCount); + + ushort posCount = reader.ReadUInt16(); + subTable.PosLookupRecords = CreateMultiplePosLookupRecords(reader, posCount); + + subTable.BacktrackCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, backtrackCoverageOffsets, reader); + subTable.InputGlyphCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, inputGlyphCoverageOffsets, reader); + subTable.LookaheadCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, lookaheadCoverageOffsets, reader); + + return subTable; + } + } + } + + /// + /// LookupType 9: Extension Positioning + /// + /// + static LookupSubTable ReadLookupType9(BinaryReader reader, long subTableStartAt) + { + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + ushort format = reader.ReadUInt16(); + ushort extensionLookupType = reader.ReadUInt16(); + uint extensionOffset = reader.ReadUInt32(); + if (extensionLookupType == 9) + { + throw new OpenFontNotSupportedException(); + } + // Simply read the lookup table again with updated offsets + + return ReadSubTable(extensionLookupType, reader, subTableStartAt + extensionOffset); + } + } + } +} + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GSUB.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GSUB.cs new file mode 100644 index 00000000..f1c1b659 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GSUB.cs @@ -0,0 +1,1628 @@ +//Apache2, 2016-present, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/gsub + + + //////////////////////////////////////////////////////////////// + //GSUB Table + //The GSUB table contains substitution lookups that map GIDs to GIDs and associate these mappings with particular OpenType Layout features. The OpenType specification currently supports six different GSUB lookup types: + + // 1. Single Replaces one glyph with one glyph. + // 2. Multiple Replaces one glyph with more than one glyph. + // 3. Alternate Replaces one glyph with one of many glyphs. + // 4. Ligature Replaces multiple glyphs with one glyph. + // 5. Context Replaces one or more glyphs in context. + // 6. Chaining Context Replaces one or more glyphs in chained context. + + //Although these lookups are defined by the font developer, + //it is important for application developers to understand that some features require relatively complex UI support. + //In particular, OTL features using type 3 lookups may require the application to present options + //to the user (an example of this is provided in the discussion of OTLS in Part One). + //In addition, some registered features allow more than one lookup type to be employed, + //so application developers cannot rely on supporting only some lookup types. + //Similarly, features may have both GSUB and GPOS solutions—e.g. the 'Case-Sensitive Forms' feature—so applications + //that want to support these features should avoid limiting their support to only one of these tables. + //In setting priorities for feature support, + //it is important to consider the possible interaction of features and to provide users with powerful sets of typographic tools that work together. + + //////////////////////////////////////////////////////////////// + + public partial class GSUB : GlyphShapingTableEntry + { + public const string _N = "GSUB"; + public override string Name => _N; + +#if DEBUG + public GSUB() { } +#endif + // + protected override void ReadLookupTable(BinaryReader reader, long lookupTablePos, + ushort lookupType, ushort lookupFlags, + ushort[] subTableOffsets, ushort markFilteringSet) + { + LookupTable lookupTable = new LookupTable(lookupFlags, markFilteringSet); + LookupSubTable[] subTables = new LookupSubTable[subTableOffsets.Length]; + lookupTable.SubTables = subTables; + + for (int i = 0; i < subTableOffsets.Length; ++i) + { + LookupSubTable subTable = LookupTable.ReadSubTable(lookupType, reader, lookupTablePos + subTableOffsets[i]); + subTable.OwnerGSub = this; + subTables[i] = subTable; + } + +#if DEBUG + lookupTable.dbugLkIndex = LookupList.Count; +#endif + LookupList.Add(lookupTable); + } + + protected override void ReadFeatureVariations(BinaryReader reader, long featureVariationsBeginAt) + { + Utils.WarnUnimplemented("GSUB feature variations"); + } + + private List _lookupList = new List(); + + public IList LookupList => _lookupList; + + + //-------------------------- + /// + /// base class of lookup sub table + /// + public abstract class LookupSubTable + { + public GSUB OwnerGSub; + + public abstract bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len); + + //collect all substitution glyphs + // + //if we lookup glyph index from the unicode char + // (eg. building pre-built glyph texture) + //we may miss some glyph that is needed for substitution process. + // + //so, we collect it here, based on current script lang. + public abstract void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs); + + } + + /// + /// Empty lookup sub table for unimplemented formats + /// + public class UnImplementedLookupSubTable : LookupSubTable + { + readonly string _message; + public UnImplementedLookupSubTable(string msg) + { + _message = msg; + Utils.WarnUnimplemented(msg); + } + public override string ToString() => _message; + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + Utils.WarnUnimplemented("collect-assoc-sub-glyph: " + this.ToString()); + } + } + + + + /// + /// sub table of a lookup list + /// + public partial class LookupTable + { +#if DEBUG + public int dbugLkIndex; +#endif + //-------------------------- + + public readonly ushort lookupFlags; + public readonly ushort markFilteringSet; + + public LookupTable(ushort lookupFlags, ushort markFilteringSet) + { + this.lookupFlags = lookupFlags; + this.markFilteringSet = markFilteringSet; + } + // + public LookupSubTable[] SubTables { get; internal set; } + + // + public bool DoSubstitutionAt(IGlyphIndexList inputGlyphs, int pos, int len) + { + foreach (LookupSubTable subTable in SubTables) + { + // We return after the first substitution, as explained in the spec: + // "A lookup is finished for a glyph after the client locates the target + // glyph or glyph context and performs a substitution, if specified." + // https://www.microsoft.com/typography/otspec/gsub.htm + if (subTable.DoSubstitutionAt(inputGlyphs, pos, len)) + return true; + } + return false; + } + + public void CollectAssociatedSubstitutionGlyph(List outputAssocGlyphs) + { + + //collect all substitution glyphs + // + //if we lookup glyph index from the unicode char + // (eg. building pre-built glyph texture) + //we may miss some glyph that is needed for substitution process. + // + //so, we collect it here, based on current script lang. + foreach (LookupSubTable subTable in SubTables) + { + subTable.CollectAssociatedSubtitutionGlyphs(outputAssocGlyphs); + } + } + + public static LookupSubTable ReadSubTable(int lookupType, BinaryReader reader, long subTableStartAt) + { + switch (lookupType) + { + case 1: return ReadLookupType1(reader, subTableStartAt); + case 2: return ReadLookupType2(reader, subTableStartAt); + case 3: return ReadLookupType3(reader, subTableStartAt); + case 4: return ReadLookupType4(reader, subTableStartAt); + case 5: return ReadLookupType5(reader, subTableStartAt); + case 6: return ReadLookupType6(reader, subTableStartAt); + case 7: return ReadLookupType7(reader, subTableStartAt); + case 8: return ReadLookupType8(reader, subTableStartAt); + } + + return new UnImplementedLookupSubTable(string.Format("GSUB Lookup Type {0}", lookupType)); + } + + /// + /// for lookup table type 1, format1 + /// + class LkSubTableT1Fmt1 : LookupSubTable + { + public LkSubTableT1Fmt1(CoverageTable coverageTable, ushort deltaGlyph) + { + this.CoverageTable = coverageTable; + this.DeltaGlyph = deltaGlyph; + } + /// + /// Add to original GlyphID to get substitute GlyphID + /// + public ushort DeltaGlyph { get; private set; } + public CoverageTable CoverageTable { get; private set; } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + ushort glyphIndex = glyphIndices[pos]; + if (CoverageTable.FindPosition(glyphIndex) > -1) + { + glyphIndices.Replace(pos, (ushort)(glyphIndex + DeltaGlyph)); + return true; + } + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + //1. iterate glyphs from CoverageTable + foreach (ushort glyphIndex in CoverageTable.GetExpandedValueIter()) + { + //2. add substitution glyph + outputAssocGlyphs.Add((ushort)(glyphIndex + DeltaGlyph)); + } + } + } + /// + /// for lookup table type 1, format2 + /// + class LkSubTableT1Fmt2 : LookupSubTable + { + public LkSubTableT1Fmt2(CoverageTable coverageTable, ushort[] substituteGlyphs) + { + this.CoverageTable = coverageTable; + this.SubstituteGlyphs = substituteGlyphs; + } + /// + /// It provides an array of output glyph indices (Substitute) explicitly matched to the input glyph indices specified in the Coverage table + /// + public ushort[] SubstituteGlyphs { get; } + public CoverageTable CoverageTable { get; } + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + int foundAt = CoverageTable.FindPosition(glyphIndices[pos]); + if (foundAt > -1) + { + glyphIndices.Replace(pos, SubstituteGlyphs[foundAt]); + return true; + } + return false; + } + + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + foreach (ushort glyphIndex in CoverageTable.GetExpandedValueIter()) + { + //2. add substitution glyph + int foundAt = CoverageTable.FindPosition(glyphIndex); + outputAssocGlyphs.Add((ushort)(SubstituteGlyphs[foundAt])); + } + } + + } + + + /// + /// LookupType 1: Single Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType1(BinaryReader reader, long subTableStartAt) + { + //--------------------- + //LookupType 1: Single Substitution Subtable + //Single substitution (SingleSubst) subtables tell a client to replace a single glyph with another glyph. + //The subtables can be either of two formats. + //Both formats require two distinct sets of glyph indices: one that defines input glyphs (specified in the Coverage table), + //and one that defines the output glyphs. Format 1 requires less space than Format 2, but it is less flexible. + //------------------------------------ + // 1.1 Single Substitution Format 1 + //------------------------------------ + //Format 1 calculates the indices of the output glyphs, + //which are not explicitly defined in the subtable. + //To calculate an output glyph index, Format 1 adds a constant delta value to the input glyph index. + //For the substitutions to occur properly, the glyph indices in the input and output ranges must be in the same order. + //This format does not use the Coverage Index that is returned from the Coverage table. + + //The SingleSubstFormat1 subtable begins with a format identifier (SubstFormat) of 1. + //An offset references a Coverage table that specifies the indices of the input glyphs. + //DeltaGlyphID is the constant value added to each input glyph index to calculate the index of the corresponding output glyph. + + //Example 2 at the end of this chapter uses Format 1 to replace standard numerals with lining numerals. + + //--------------------------------- + //SingleSubstFormat1 subtable: Calculated output glyph indices + //--------------------------------- + //Type Name Description + //uint16 SubstFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of Substitution table + //uint16 DeltaGlyphID Add to original GlyphID to get substitute GlyphID + + //------------------------------------ + //1.2 Single Substitution Format 2 + //------------------------------------ + //Format 2 is more flexible than Format 1, but requires more space. + //It provides an array of output glyph indices (Substitute) explicitly matched to the input glyph indices specified in the Coverage table. + //The SingleSubstFormat2 subtable specifies a format identifier (SubstFormat), an offset to a Coverage table that defines the input glyph indices, + //a count of output glyph indices in the Substitute array (GlyphCount), and a list of the output glyph indices in the Substitute array (Substitute). + //The Substitute array must contain the same number of glyph indices as the Coverage table. To locate the corresponding output glyph index in the Substitute array, this format uses the Coverage Index returned from the Coverage table. + + //Example 3 at the end of this chapter uses Format 2 to substitute vertically oriented glyphs for horizontally oriented glyphs. + //--------------------------------- + //SingleSubstFormat2 subtable: Specified output glyph indices + //--------------------------------- + //Type Name Description + //USHORT SubstFormat Format identifier-format = 2 + //Offset Coverage Offset to Coverage table-from beginning of Substitution table + //USHORT GlyphCount Number of GlyphIDs in the Substitute array + //GlyphID Substitute[GlyphCount] Array of substitute GlyphIDs-ordered by Coverage Index + //--------------------------------- + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + ushort coverage = reader.ReadUInt16(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + ushort deltaGlyph = reader.ReadUInt16(); + CoverageTable coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return new LkSubTableT1Fmt1(coverageTable, deltaGlyph); + } + case 2: + { + ushort glyphCount = reader.ReadUInt16(); + ushort[] substituteGlyphs = Utils.ReadUInt16Array(reader, glyphCount); // Array of substitute GlyphIDs-ordered by Coverage Index + CoverageTable coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return new LkSubTableT1Fmt2(coverageTable, substituteGlyphs); + } + } + } + + class LkSubTableT2 : LookupSubTable + { + + public CoverageTable CoverageTable { get; set; } + public SequenceTable[] SeqTables { get; set; } + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + int foundPos = CoverageTable.FindPosition(glyphIndices[pos]); + if (foundPos > -1) + { + SequenceTable seqTable = SeqTables[foundPos]; + //replace current glyph index with new seq +#if DEBUG + int new_seqCount = seqTable.substituteGlyphs.Length; +#endif + glyphIndices.Replace(pos, seqTable.substituteGlyphs); + return true; + } + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + foreach (ushort glyphIndex in CoverageTable.GetExpandedValueIter()) + { + int pos = CoverageTable.FindPosition(glyphIndex); +#if DEBUG + if (pos >= SeqTables.Length) + { + + } +#endif + outputAssocGlyphs.AddRange(SeqTables[pos].substituteGlyphs); + } + } + } + readonly struct SequenceTable + { + public readonly ushort[] substituteGlyphs; + public SequenceTable(ushort[] substituteGlyphs) + { + this.substituteGlyphs = substituteGlyphs; + } + } + + /// + /// LookupType 2: Multiple Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType2(BinaryReader reader, long subTableStartAt) + { + //LookupType 2: Multiple Substitution Subtable + //A Multiple Substitution (MultipleSubst) subtable replaces a single glyph with more than one glyph, + //as when multiple glyphs replace a single ligature. + + //The subtable has a single format: MultipleSubstFormat1. + + //The subtable specifies a format identifier (SubstFormat), + //an offset to a Coverage table that defines the input glyph indices, a count of offsets in the Sequence array (SequenceCount), + //and an array of offsets to Sequence tables that define the output glyph indices (Sequence). + //The Sequence table offsets are ordered by the Coverage Index of the input glyphs. + + //For each input glyph listed in the Coverage table, a Sequence table defines the output glyphs. + //Each Sequence table contains a count of the glyphs in the output glyph sequence (GlyphCount) and an array of output glyph indices (Substitute). + + // Note: The order of the output glyph indices depends on the writing direction of the text. + //For text written left to right, the left-most glyph will be first glyph in the sequence. + //Conversely, for text written right to left, the right-most glyph will be first. + + //The use of multiple substitution for deletion of an input glyph is prohibited. GlyphCount should always be greater than 0. + //Example 4 at the end of this chapter shows how to replace a single ligature with three glyphs. + + //---------------------- + //MultipleSubstFormat1 subtable: Multiple output glyphs + //---------------------- + //Type Name Description + //uint16 SubstFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of Substitution table + //uint16 SequenceCount Number of Sequence table offsets in the Sequence array + //Offset16 Sequence[SequenceCount] Array of offsets to Sequence tables-from beginning of Substitution table-ordered by Coverage Index + ////---------------------- + //Sequence table + //Type Name Description + //uint16 GlyphCount Number of glyph IDs in the Substitute array. This should always be greater than 0. + //uint16 Substitute[GlyphCount] String of glyph IDs to substitute + //---------------------- + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + ushort format = reader.ReadUInt16(); + switch (format) + { + default: + throw new OpenFontNotSupportedException(); + case 1: + { + ushort coverageOffset = reader.ReadUInt16(); + ushort seqCount = reader.ReadUInt16(); + ushort[] seqOffsets = Utils.ReadUInt16Array(reader, seqCount); + + var subTable = new LkSubTableT2(); + subTable.SeqTables = new SequenceTable[seqCount]; + for (int n = 0; n < seqCount; ++n) + { + reader.BaseStream.Seek(subTableStartAt + seqOffsets[n], SeekOrigin.Begin); + ushort glyphCount = reader.ReadUInt16(); + subTable.SeqTables[n] = new SequenceTable( + Utils.ReadUInt16Array(reader, glyphCount)); + } + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + + return subTable; + } + } + } + + /// + /// LookupType 3: Alternate Substitution Subtable + /// + class LkSubTableT3 : LookupSubTable + { + public CoverageTable CoverageTable { get; set; } + public AlternativeSetTable[] AlternativeSetTables { get; set; } + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + //Coverage table containing the indices of glyphs with alternative forms(Coverage), + int iscovered = this.CoverageTable.FindPosition(glyphIndices[pos]); + //this.CoverageTable.FindPosition() + Utils.WarnUnimplemented("GSUB, Lookup Subtable Type 3"); + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + Utils.WarnUnimplementedCollectAssocGlyphs(this.ToString()); + } + } + /// + /// LookupType 3: Alternate Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType3(BinaryReader reader, long subTableStartAt) + { + //LookupType 3: Alternate Substitution Subtable + + //An Alternate Substitution (AlternateSubst)subtable identifies any number of aesthetic alternatives + //from which a user can choose a glyph variant to replace the input glyph. + + //For example, if a font contains four variants of the ampersand symbol, + //the cmap table will specify the index of one of the four glyphs as the default glyph index, + //and an AlternateSubst subtable will list the indices of the other three glyphs as alternatives. + //A text - processing client would then have the option of replacing the default glyph with any of the three alternatives. + + //The subtable has one format: AlternateSubstFormat1. + //The subtable contains a format identifier (SubstFormat), + // an offset to a Coverage table containing the indices of glyphs with alternative forms(Coverage), + // a count of offsets to AlternateSet tables(AlternateSetCount), + // and an array of offsets to AlternateSet tables(AlternateSet). + + //For each glyph, an AlternateSet subtable contains a count of the alternative glyphs(GlyphCount) and + // an array of their glyph indices(Alternate). + //Because all the glyphs are functionally equivalent, they can be in any order in the array. + + //Example 5 at the end of this chapter shows how to replace the default ampersand glyph with alternative glyphs. + + //----------------------- + //AlternateSubstFormat1 subtable: Alternative output glyphs + //----------------------- + //Type Name Description + //uint16 SubstFormat Format identifier - format = 1 + //Offset16 Coverage Offset to Coverage table - from beginning of Substitution table + //uint16 AlternateSetCount Number of AlternateSet tables + //Offset16 AlternateSet[AlternateSetCount] Array of offsets to AlternateSet tables - from beginning of Substitution table - ordered by Coverage Index + // + //AlternateSet table + //Type Name Description + //uint16 GlyphCount Number of glyph IDs in the Alternate array + //uint16 Alternate[GlyphCount] Array of alternate glyph IDs -in arbitrary order + //----------------------- + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); //The subtable has one format: AlternateSubstFormat1. + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + ushort coverageOffset = reader.ReadUInt16(); + ushort alternativeSetCount = reader.ReadUInt16(); + ushort[] alternativeTableOffsets = Utils.ReadUInt16Array(reader, alternativeSetCount); + + LkSubTableT3 subTable = new LkSubTableT3(); + AlternativeSetTable[] alternativeSetTables = new AlternativeSetTable[alternativeSetCount]; + subTable.AlternativeSetTables = alternativeSetTables; + for (int n = 0; n < alternativeSetCount; ++n) + { + alternativeSetTables[n] = AlternativeSetTable.CreateFrom(reader, subTableStartAt + alternativeTableOffsets[n]); + } + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + + return subTable; + } + } + } + + class AlternativeSetTable + { + public ushort[] alternativeGlyphIds; + public static AlternativeSetTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + AlternativeSetTable altTable = new AlternativeSetTable(); + ushort glyphCount = reader.ReadUInt16(); + altTable.alternativeGlyphIds = Utils.ReadUInt16Array(reader, glyphCount); + return altTable; + } + } + + class LkSubTableT4 : LookupSubTable + { + public CoverageTable CoverageTable { get; set; } + public LigatureSetTable[] LigatureSetTables { get; set; } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + //check coverage + ushort glyphIndex = glyphIndices[pos]; + int foundPos = this.CoverageTable.FindPosition(glyphIndex); + if (foundPos > -1) + { + LigatureSetTable ligTable = LigatureSetTables[foundPos]; + foreach (LigatureTable lig in ligTable.Ligatures) + { + int remainingLen = len - 1; + int compLen = lig.ComponentGlyphs.Length; + if (compLen > remainingLen) + { // skip tp next component + continue; + } + bool allMatched = true; + int tmp_i = pos + 1; + for (int p = 0; p < compLen; ++p) + { + if (glyphIndices[tmp_i + p] != lig.ComponentGlyphs[p]) + { + allMatched = false; + break; //exit from loop + } + } + if (allMatched) + { + // remove all matches and replace with selected glyph + glyphIndices.Replace(pos, compLen + 1, lig.GlyphId); + return true; + } + } + } + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + foreach (ushort glyphIndex in CoverageTable.GetExpandedValueIter()) + { + int foundPos = CoverageTable.FindPosition(glyphIndex); + LigatureSetTable ligTable = LigatureSetTables[foundPos]; + foreach (LigatureTable lig in ligTable.Ligatures) + { + outputAssocGlyphs.Add(lig.GlyphId); + } + } + } + } + class LigatureSetTable + { + //LigatureSet table: All ligatures beginning with the same glyph + //Type Name Description + //uint16 LigatureCount Number of Ligature tables + //Offset16 Ligature[LigatureCount] Array of offsets to Ligature tables-from beginning of LigatureSet table-ordered by preference + + public LigatureTable[] Ligatures { get; set; } + public static LigatureSetTable CreateFrom(BinaryReader reader, long beginAt) + { + LigatureSetTable ligSetTable = new LigatureSetTable(); + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + ushort ligCount = reader.ReadUInt16(); //Number of Ligature tables + ushort[] ligOffsets = Utils.ReadUInt16Array(reader, ligCount); + // + LigatureTable[] ligTables = ligSetTable.Ligatures = new LigatureTable[ligCount]; + for (int i = 0; i < ligCount; ++i) + { + ligTables[i] = LigatureTable.CreateFrom(reader, beginAt + ligOffsets[i]); + } + return ligSetTable; + } + + } + readonly struct LigatureTable + { + //uint16 LigGlyph GlyphID of ligature to substitute + //uint16 CompCount Number of components in the ligature + //uint16 Component[CompCount - 1] Array of component GlyphIDs-start with the second component-ordered in writing direction + /// + /// output glyph + /// + public readonly ushort GlyphId; + /// + /// ligature component start with second ordered glyph + /// + public readonly ushort[] ComponentGlyphs; + + public LigatureTable(ushort glyphId, ushort[] componentGlyphs) + { + GlyphId = glyphId; + ComponentGlyphs = componentGlyphs; + } + public static LigatureTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + // + ushort glyphIndex = reader.ReadUInt16(); + ushort compCount = reader.ReadUInt16(); + return new LigatureTable(glyphIndex, Utils.ReadUInt16Array(reader, compCount - 1)); + } +#if DEBUG + public override string ToString() + { + var stbuilder = new System.Text.StringBuilder(); + int j = ComponentGlyphs.Length; + stbuilder.Append("output:" + GlyphId + ",{"); + + for (int i = 0; i < j; ++i) + { + if (i > 0) + { + stbuilder.Append(','); + } + stbuilder.Append(ComponentGlyphs[i]); + } + stbuilder.Append("}"); + return stbuilder.ToString(); + } +#endif + } + /// + /// LookupType 4: Ligature Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType4(BinaryReader reader, long subTableStartAt) + { + //LookupType 4: Ligature Substitution Subtable + + //A Ligature Substitution (LigatureSubst) subtable identifies ligature substitutions where a single glyph + //replaces multiple glyphs. One LigatureSubst subtable can specify any number of ligature substitutions. + + //The subtable uses a single format: LigatureSubstFormat1. + //It contains a format identifier (SubstFormat), + //a Coverage table offset (Coverage), a count of the ligature sets defined in this table (LigSetCount), + //and an array of offsets to LigatureSet tables (LigatureSet). + //The Coverage table specifies only the index of the first glyph component of each ligature set. + + //----------------------------- + //LigatureSubstFormat1 subtable: All ligature substitutions in a script + //----------------------------- + //Type Name Description + //uint16 SubstFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of Substitution table + //uint16 LigSetCount Number of LigatureSet tables + //Offset16 LigatureSet[LigSetCount] Array of offsets to LigatureSet tables-from beginning of Substitution table-ordered by Coverage Index + //----------------------------- + + //A LigatureSet table, one for each covered glyph, + //specifies all the ligature strings that begin with the covered glyph. + //For example, if the Coverage table lists the glyph index for a lowercase “f,” + //then a LigatureSet table will define the “ffl,” “fl,” “ffi,” “fi,” and “ff” ligatures. + //If the Coverage table also lists the glyph index for a lowercase “e,” + //then a different LigatureSet table will define the “etc” ligature. + + //A LigatureSet table consists of a count of the ligatures that begin with + //the covered glyph (LigatureCount) and an array of offsets to Ligature tables, + //which define the glyphs in each ligature (Ligature). + //The order in the Ligature offset array defines the preference for using the ligatures. + //For example, if the “ffl” ligature is preferable to the “ff” ligature, then the Ligature array would list the offset to the “ffl” Ligature table before the offset to the “ff” Ligature table. + //----------------------------- + //LigatureSet table: All ligatures beginning with the same glyph + //----------------------------- + //Type Name Description + //uint16 LigatureCount Number of Ligature tables + //Offset16 Ligature[LigatureCount] Array of offsets to Ligature tables-from beginning of LigatureSet table-ordered by preference + //----------------------------- + + //For each ligature in the set, a Ligature table specifies the GlyphID of the output ligature glyph (LigGlyph); + // count of the total number of component glyphs in the ligature, including the first component (CompCount); + //and an array of GlyphIDs for the components (Component). + //The array starts with the second component glyph (array index = 1) in the ligature + //because the first component glyph is specified in the Coverage table. + + // Note: The Component array lists GlyphIDs according to the writing direction of the text. + //For text written right to left, the right-most glyph will be first. + //Conversely, for text written left to right, the left-most glyph will be first. + + //Example 6 at the end of this chapter shows how to replace a string of glyphs with a single ligature. + //----------------------------- + //Ligature table: Glyph components for one ligature + //----------------------------- + //Type Name Description + //uint16 LigGlyph GlyphID of ligature to substitute + //uint16 CompCount Number of components in the ligature + //uint16 Component[CompCount - 1] Array of component GlyphIDs-start with the second component-ordered in writing direction + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + ushort coverageOffset = reader.ReadUInt16(); + ushort ligSetCount = reader.ReadUInt16(); + ushort[] ligSetOffsets = Utils.ReadUInt16Array(reader, ligSetCount); + LkSubTableT4 subTable = new LkSubTableT4(); + LigatureSetTable[] ligSetTables = subTable.LigatureSetTables = new LigatureSetTable[ligSetCount]; + for (int n = 0; n < ligSetCount; ++n) + { + ligSetTables[n] = LigatureSetTable.CreateFrom(reader, subTableStartAt + ligSetOffsets[n]); + } + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + return subTable; + } + } + } + + + + + /// + /// LookupType 5: Contextual Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType5(BinaryReader reader, long subTableStartAt) + { + + + //LookupType 5: Contextual Substitution Subtable + //A Contextual Substitution (ContextSubst) subtable defines a powerful type of glyph substitution lookup: + //it describes glyph substitutions in context that replace one or more glyphs within a certain pattern of glyphs. + + //ContextSubst subtables can be any of three formats that define a context in terms of + //a specific sequence of glyphs, + //glyph classes, + //or glyph sets. + + //Each format can describe one or more input glyph sequences and one or more substitutions for each sequence. + //All three formats specify substitution data in a SubstLookupRecord, described above. + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort substFormat = reader.ReadUInt16(); + switch (substFormat) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + //ContextSubstFormat1 Subtable + //Table 14 + //Type Name Description + //uint16 substFormat Format identifier: format = 1 + //Offset16 coverageOffset Offset to Coverage table, from beginning of substitution subtable + //uint16 subRuleSetCount Number of SubRuleSet tables — must equal glyphCount in Coverage table*** + //Offset16 subRuleSetOffsets[subRuleSetCount] Array of offsets to SubRuleSet tables. + // Offsets are from beginning of substitution subtable, ordered by Coverage index + + LkSubTableT5Fmt1 fmt1 = new LkSubTableT5Fmt1(); + ushort coverageOffset = reader.ReadUInt16(); + ushort subRuleSetCount = reader.ReadUInt16(); + ushort[] subRuleSetOffsets = Utils.ReadUInt16Array(reader, subRuleSetCount); + + fmt1.coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + fmt1.subRuleSets = new LkSubT5Fmt1_SubRuleSet[subRuleSetCount]; + + for (int i = 0; i < subRuleSetCount; ++i) + { + fmt1.subRuleSets[i] = LkSubT5Fmt1_SubRuleSet.CreateFrom(reader, subTableStartAt + subRuleSetOffsets[i]); + } + + return fmt1; + } + case 2: + { + + //ContextSubstFormat2 Subtable + //Table 17 + //Type Name Description + //uint16 substFormat Format identifier: format = 2 + //Offset16 coverageOffset Offset to Coverage table, from beginning of substitution subtable + //Offset16 classDefOffset Offset to glyph ClassDef table, from beginning of substitution subtable + //uint16 subClassSetCount Number of SubClassSet tables + //Offset16 subClassSetOffsets[subClassSetCount] Array of offsets to SubClassSet tables. Offsets are from beginning of substitution subtable, ordered by class (may be NULL). + + LkSubTableT5Fmt2 fmt2 = new LkSubTableT5Fmt2(); + ushort coverageOffset = reader.ReadUInt16(); + ushort classDefOffset = reader.ReadUInt16(); + ushort subClassSetCount = reader.ReadUInt16(); + ushort[] subClassSetOffsets = Utils.ReadUInt16Array(reader, subClassSetCount); + // + fmt2.coverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverageOffset); + fmt2.classDef = ClassDefTable.CreateFrom(reader, subTableStartAt + classDefOffset); + + var subClassSets = new LkSubT5Fmt2_SubClassSet[subClassSetCount]; + fmt2.subClassSets = subClassSets; + for (int i = 0; i < subClassSetCount; ++i) + { + subClassSets[i] = LkSubT5Fmt2_SubClassSet.CreateFrom(reader, subTableStartAt + subClassSetOffsets[i]); + } + + return fmt2; + } + + case 3: + { + return new UnImplementedLookupSubTable("GSUB,Lookup Subtable Type 5,Fmt3"); + } + + } + } + + /// + /// 5.1 Context Substitution Format 1: Simple Glyph Contexts + /// + class LkSubTableT5Fmt1 : LookupSubTable + { + public CoverageTable coverageTable; + public LkSubT5Fmt1_SubRuleSet[] subRuleSets; + + //5.1 Context Substitution Format 1: Simple Glyph Contexts + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + throw new NotImplementedException(); + } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + + int coverage_pos = coverageTable.FindPosition(glyphIndices[pos]); + if (coverage_pos < 0) { return false; } + + + Utils.WarnUnimplemented("GSUB," + nameof(LkSubTableT5Fmt1)); + return false; + } + } + + + class LkSubT5Fmt1_SubRuleSet + { + public LkSubT5Fmt1_SubRule[] subRules; + public static LkSubT5Fmt1_SubRuleSet CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + LkSubT5Fmt1_SubRuleSet subRuleSet = new LkSubT5Fmt1_SubRuleSet(); + + //SubRuleSet table: All contexts beginning with the same glyph + //Table 15 + //Type Name Description + //uint16 subRuleCount Number of SubRule tables + //Offset16 subRuleOffsets[subRuleCount] Array of offsets to SubRule tables. Offsets are from beginning of SubRuleSet table, ordered by preference + + ushort subRuleCount = reader.ReadUInt16(); + + ushort[] subRuleOffsets = Utils.ReadUInt16Array(reader, subRuleCount); + var subRules = new LkSubT5Fmt1_SubRule[subRuleCount]; + subRuleSet.subRules = subRules; + for (int i = 0; i < subRuleCount; ++i) + { + LkSubT5Fmt1_SubRule rule = new LkSubT5Fmt1_SubRule(); + rule.ReadFrom(reader, beginAt + subRuleOffsets[i]); + + subRules[i] = rule; + } + return subRuleSet; + } + } + + class LkSubT5Fmt1_SubRule + { + + //SubRule table: One simple context definition + //Table 16 + //Type Name Description + //uint16 glyphCount Total number of glyphs in input glyph sequence — includes the first glyph. + //uint16 substitutionCount Number of SubstLookupRecords + //uint16 inputSequence[glyphCount - 1] Array of input glyph IDs — start with second glyph + //SubstLookupRecord substLookupRecords[substitutionCount] Array of SubstLookupRecords, in design order + + + public ushort[] inputSequence; + public SubstLookupRecord[] substRecords; + + public void ReadFrom(BinaryReader reader, long pos) + { + reader.BaseStream.Seek(pos, SeekOrigin.Begin); + ushort glyphCount = reader.ReadUInt16(); + ushort substitutionCount = reader.ReadUInt16(); + + inputSequence = Utils.ReadUInt16Array(reader, glyphCount - 1); + substRecords = SubstLookupRecord.CreateSubstLookupRecords(reader, substitutionCount); + } + } + + /// + /// 5.2 Context Substitution Format 2: Class-based Glyph Contexts + /// + class LkSubTableT5Fmt2 : LookupSubTable + { + //Format 2, a more flexible format than Format 1, + //describes class-based context substitution. + //For this format, a specific integer, called a class value, must be assigned to each glyph component in all context glyph sequences. + //Contexts are then defined as sequences of glyph class values. + //More than one context may be defined at a time. + + public CoverageTable coverageTable; + public ClassDefTable classDef; + public LkSubT5Fmt2_SubClassSet[] subClassSets; + + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + //collect only assoc + Dictionary collected = new Dictionary(); + foreach (ushort glyphIndex in coverageTable.GetExpandedValueIter()) + { + int class_value = classDef.GetClassValue(glyphIndex); + if (collected.ContainsKey(class_value)) + { + continue; + } + // + collected.Add(class_value, true); + + LkSubT5Fmt2_SubClassSet subClassSet = subClassSets[class_value]; + LkSubT5Fmt2_SubClassRule[] subClassRules = subClassSet.subClassRules; + + for (int i = 0; i < subClassRules.Length; ++i) + { + LkSubT5Fmt2_SubClassRule rule = subClassRules[i]; + if (rule != null && rule.substRecords != null) + { + for (int n = 0; n < rule.substRecords.Length; ++n) + { + SubstLookupRecord rect = rule.substRecords[n]; + LookupTable anotherLookup = OwnerGSub.LookupList[rect.lookupListIndex]; + anotherLookup.CollectAssociatedSubstitutionGlyph(outputAssocGlyphs); + } + } + } + } + } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + int coverage_pos = coverageTable.FindPosition(glyphIndices[pos]); + if (coverage_pos < 0) { return false; } + + int class_value = classDef.GetClassValue(glyphIndices[pos]); + + LkSubT5Fmt2_SubClassSet subClassSet = subClassSets[class_value]; + LkSubT5Fmt2_SubClassRule[] subClassRules = subClassSet.subClassRules; + + for (int i = 0; i < subClassRules.Length; ++i) + { + LkSubT5Fmt2_SubClassRule rule = subClassRules[i]; + ushort[] inputSequence = rule.inputSequence; //clas seq + int next_pos = pos + 1; + + + if (next_pos < glyphIndices.Count) + { + bool passAll = true; + for (int a = 0; a < inputSequence.Length && next_pos < glyphIndices.Count; ++a, ++next_pos) + { + int class_next = glyphIndices[next_pos]; + if (inputSequence[a] != class_next) + { + passAll = false; + break; + } + } + if (passAll) + { + + } + } + } + + Utils.WarnUnimplemented("GSUB,unfinish:" + nameof(LkSubTableT5Fmt2)); + return false; + } + } + + class LkSubT5Fmt2_SubClassSet + { + public LkSubT5Fmt2_SubClassRule[] subClassRules; + + public static LkSubT5Fmt2_SubClassSet CreateFrom(BinaryReader reader, long beginAt) + { + //SubClassSet subtable + //Table 18 + //Type Name Description + //uint16 subClassRuleCount Number of SubClassRule tables + //Offset16 subClassRuleOffsets[subClassRuleCount] Array of offsets to SubClassRule tables. Offsets are from beginning of SubClassSet, ordered by preference. + + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + LkSubT5Fmt2_SubClassSet fmt2 = new LkSubT5Fmt2_SubClassSet(); + ushort subClassRuleCount = reader.ReadUInt16(); + ushort[] subClassRuleOffsets = Utils.ReadUInt16Array(reader, subClassRuleCount); + fmt2.subClassRules = new LkSubT5Fmt2_SubClassRule[subClassRuleCount]; + for (int i = 0; i < subClassRuleCount; ++i) + { + var subClassRule = new LkSubT5Fmt2_SubClassRule(); + subClassRule.ReadFrom(reader, beginAt + subClassRuleOffsets[i]); + fmt2.subClassRules[i] = subClassRule; + } + + return fmt2; + } + } + class LkSubT5Fmt2_SubClassRule + { + //SubClassRule table: Context definition for one class + //Table 19 + //Type Name Description + //uint16 glyphCount Total number of classes specified for the context in the rule — includes the first class + //uint16 substitutionCount Number of SubstLookupRecords + //uint16 inputSequence[glyphCount - 1] Array of classes to be matched to the input glyph sequence, beginning with the second glyph position. + //SubstLookupRecord substLookupRecords[substitutionCount] Array of Substitution lookups, in design order. + + public ushort[] inputSequence; + public SubstLookupRecord[] substRecords; + public void ReadFrom(BinaryReader reader, long pos) + { + reader.BaseStream.Seek(pos, SeekOrigin.Begin); + + ushort glyphCount = reader.ReadUInt16(); + ushort substitutionCount = reader.ReadUInt16(); + inputSequence = Utils.ReadUInt16Array(reader, glyphCount - 1); + substRecords = SubstLookupRecord.CreateSubstLookupRecords(reader, substitutionCount); + } + } + + /// + /// 5.3 Context Substitution Format 3: Coverage-based Glyph Contexts + /// + class LkSubTableT5Fmt3 : LookupSubTable + { + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + throw new NotImplementedException(); + } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + throw new NotImplementedException(); + } + } + + + + class ChainSubRuleSetTable + { + //ChainSubRuleSet table: All contexts beginning with the same glyph + //------------------------------------------------------------------------- + //Type Name Description + //------------------------------------------------------------------------- + //uint16 ChainSubRuleCount Number of ChainSubRule tables + //Offset16 ChainSubRule[ChainSubRuleCount] Array of offsets to ChainSubRule tables-from beginning of ChainSubRuleSet table-ordered by preference + //------------------------------------------------------------------------- + // + //A ChainSubRule table consists of a count of the glyphs to be matched in the backtrack, + //input, and lookahead context sequences, including the first glyph in each sequence, + //and an array of glyph indices that describe each portion of the contexts. + //The Coverage table specifies the index of the first glyph in each context, + //and each array begins with the second glyph (array index = 1) in the context sequence. + + // Note: All arrays list the indices in the order the corresponding glyphs appear in the text. + //For text written from right to left, the right-most glyph will be first; conversely, + //for text written from left to right, the left-most glyph will be first. + + ChainSubRuleSubTable[] _chainSubRuleSubTables; + public static ChainSubRuleSetTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //--- + ChainSubRuleSetTable table = new ChainSubRuleSetTable(); + ushort subRuleCount = reader.ReadUInt16(); + ushort[] subRuleOffsets = Utils.ReadUInt16Array(reader, subRuleCount); + ChainSubRuleSubTable[] chainSubRuleSubTables = table._chainSubRuleSubTables = new ChainSubRuleSubTable[subRuleCount]; + for (int i = 0; i < subRuleCount; ++i) + { + chainSubRuleSubTables[i] = ChainSubRuleSubTable.CreateFrom(reader, beginAt + subRuleOffsets[i]); + } + + return table; + } + } + //--------------------- + //SubstLookupRecord + //--------------------- + //Type Name Description + //uint16 SequenceIndex Index into current glyph sequence-first glyph = 0 + //uint16 LookupListIndex Lookup to apply to that position-zero-based + //--------------------- + //The SequenceIndex in a SubstLookupRecord must take into consideration the order + //in which lookups are applied to the entire glyph sequence. + //Because multiple substitutions may occur per context, + //the SequenceIndex and LookupListIndex refer to the glyph sequence after the text-processing client has applied any previous lookups. + //In other words, the SequenceIndex identifies the location for the substitution at the time that the lookup is to be applied. + //For example, consider an input glyph sequence of four glyphs. + //The first glyph does not have a substitute, but the middle + //two glyphs will be replaced with a ligature, and a single glyph will replace the fourth glyph: + + // The first glyph is in position 0. No lookups will be applied at position 0, so no SubstLookupRecord is defined. + // The SubstLookupRecord defined for the ligature substitution specifies the SequenceIndex as position 1, + //which is the position of the first-glyph component in the ligature string. After the ligature replaces the glyphs in positions 1 and 2, however, + //the input glyph sequence consists of only three glyphs, not the original four. + // To replace the last glyph in the sequence, + //the SubstLookupRecord defines the SequenceIndex as position 2 instead of position 3. + //This position reflects the effect of the ligature substitution applied before this single substitution. + + // Note: This example assumes that the LookupList specifies the ligature substitution lookup before the single substitution lookup. + + readonly struct SubstLookupRecord + { + public readonly ushort sequenceIndex; + public readonly ushort lookupListIndex; + public SubstLookupRecord(ushort seqIndex, ushort lookupListIndex) + { + this.sequenceIndex = seqIndex; + this.lookupListIndex = lookupListIndex; + } + public static SubstLookupRecord[] CreateSubstLookupRecords(BinaryReader reader, ushort ncount) + { + SubstLookupRecord[] results = new SubstLookupRecord[ncount]; + for (int i = 0; i < ncount; ++i) + { + results[i] = new SubstLookupRecord(reader.ReadUInt16(), reader.ReadUInt16()); + } + return results; + } + } + class ChainSubRuleSubTable + { + + //A ChainSubRule table also contains a count of the substitutions to be performed on the input glyph sequence (SubstCount) + //and an array of SubstitutionLookupRecords (SubstLookupRecord). + //Each record specifies a position in the input glyph sequence and a LookupListIndex to the substitution lookup that is applied at that position. + //The array should list records in design order, or the order the lookups should be applied to the entire glyph sequence. + + //ChainSubRule subtable + //Type Name Description + //uint16 BacktrackGlyphCount Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph) + //uint16 Backtrack[BacktrackGlyphCount] Array of backtracking GlyphID's (to be matched before the input sequence) + //uint16 InputGlyphCount Total number of glyphs in the input sequence (includes the first glyph) + //uint16 Input[InputGlyphCount - 1] Array of input GlyphIDs (start with second glyph) + //uint16 LookaheadGlyphCount Total number of glyphs in the look ahead sequence (number of glyphs to be matched after the input sequence) + //uint16 LookAhead[LookAheadGlyphCount] Array of lookahead GlyphID's (to be matched after the input sequence) + //uint16 SubstCount Number of SubstLookupRecords + //struct SubstLookupRecord[SubstCount] Array of SubstLookupRecords (in design order) + + ushort[] backTrackingGlyphs; + ushort[] inputGlyphs; + ushort[] lookaheadGlyphs; + SubstLookupRecord[] substLookupRecords; + public static ChainSubRuleSubTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + //------------ + ChainSubRuleSubTable subRuleTable = new ChainSubRuleSubTable(); + ushort backtrackGlyphCount = reader.ReadUInt16(); + subRuleTable.backTrackingGlyphs = Utils.ReadUInt16Array(reader, backtrackGlyphCount); + //-------- + ushort inputGlyphCount = reader.ReadUInt16(); + subRuleTable.inputGlyphs = Utils.ReadUInt16Array(reader, inputGlyphCount - 1);//*** start with second glyph, so -1 + //---------- + ushort lookaheadGlyphCount = reader.ReadUInt16(); + subRuleTable.lookaheadGlyphs = Utils.ReadUInt16Array(reader, lookaheadGlyphCount); + //------------ + ushort substCount = reader.ReadUInt16(); + subRuleTable.substLookupRecords = SubstLookupRecord.CreateSubstLookupRecords(reader, substCount); + + return subRuleTable; + } + + } + + + class ChainSubClassSet + { + + //---------------------------------- + //ChainSubRuleSet table: All contexts beginning with the same glyph + //---------------------------------- + //Type Name Description + //uint16 ChainSubClassRuleCnt Number of ChainSubClassRule tables + //Offset16 ChainSubClassRule[ChainSubClassRuleCount] Array of offsets to ChainSubClassRule tables-from beginning of ChainSubClassSet-ordered by preference + //---------------------------------- + //For each context, a ChainSubClassRule table contains a count of the glyph classes in the context sequence (GlyphCount), + //including the first class. + //A Class array lists the classes, beginning with the second class (array index = 1), that follow the first class in the context. + + //Note: Text order depends on the writing direction of the text. For text written from right to left, the right-most class will be first. Conversely, for text written from left to right, the left-most class will be first. + + //The values specified in the Class array are the values defined in the ClassDef table. + //The first class in the sequence, + //Class 2, is identified in the ChainContextSubstFormat2 table by the ChainSubClassSet array index of the corresponding ChainSubClassSet. + + //A ChainSubClassRule also contains a count of the substitutions to be performed on the context (SubstCount) and an array of SubstLookupRecords (SubstLookupRecord) that supply the substitution data. For each position in the context that requires a substitution, a SubstLookupRecord specifies a LookupList index and a position in the input glyph sequence where the lookup is applied. The SubstLookupRecord array lists SubstLookupRecords in design order-that is, the order in which lookups should be applied to the entire glyph sequence. + + + ChainSubClassRuleTable[] subClassRuleTables; + public static ChainSubClassSet CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + ChainSubClassSet chainSubClassSet = new ChainSubClassSet(); + ushort count = reader.ReadUInt16(); + ushort[] subClassRuleOffsets = Utils.ReadUInt16Array(reader, count); + + ChainSubClassRuleTable[] subClassRuleTables = chainSubClassSet.subClassRuleTables = new ChainSubClassRuleTable[count]; + for (int i = 0; i < count; ++i) + { + subClassRuleTables[i] = ChainSubClassRuleTable.CreateFrom(reader, beginAt + subClassRuleOffsets[i]); + } + return chainSubClassSet; + } + } + class ChainSubClassRuleTable + { + //ChainSubClassRule table: Chaining context definition for one class + //Type Name Description + //USHORT BacktrackGlyphCount Total number of glyphs in the backtrack sequence (number of glyphs to be matched before the first glyph) + //USHORT Backtrack[BacktrackGlyphCount] Array of backtracking classes(to be matched before the input sequence) + //USHORT InputGlyphCount Total number of classes in the input sequence (includes the first class) + //USHORT Input[InputGlyphCount - 1] Array of input classes(start with second class; to be matched with the input glyph sequence) + //USHORT LookaheadGlyphCount Total number of classes in the look ahead sequence (number of classes to be matched after the input sequence) + //USHORT LookAhead[LookAheadGlyphCount] Array of lookahead classes(to be matched after the input sequence) + //USHORT SubstCount Number of SubstLookupRecords + //struct SubstLookupRecord[SubstCount] Array of SubstLookupRecords (in design order) + + ushort[] backtrakcingClassDefs; + ushort[] inputClassDefs; + ushort[] lookaheadClassDefs; + SubstLookupRecord[] subsLookupRecords; + public static ChainSubClassRuleTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + ChainSubClassRuleTable subClassRuleTable = new ChainSubClassRuleTable(); + ushort backtrackingCount = reader.ReadUInt16(); + subClassRuleTable.backtrakcingClassDefs = Utils.ReadUInt16Array(reader, backtrackingCount); + ushort inputGlyphCount = reader.ReadUInt16(); + subClassRuleTable.inputClassDefs = Utils.ReadUInt16Array(reader, inputGlyphCount - 1);//** -1 + ushort lookaheadGlyphCount = reader.ReadUInt16(); + subClassRuleTable.lookaheadClassDefs = Utils.ReadUInt16Array(reader, lookaheadGlyphCount); + ushort substCount = reader.ReadUInt16(); + subClassRuleTable.subsLookupRecords = SubstLookupRecord.CreateSubstLookupRecords(reader, substCount); + + return subClassRuleTable; + } + } + + //------------------------------------------------------------- + class LkSubTableT6Fmt1 : LookupSubTable + { + public CoverageTable CoverageTable { get; set; } + public ChainSubRuleSetTable[] SubRuleSets { get; set; } + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + Utils.WarnUnimplemented("GSUB, Lookup Subtable Type 6 Format 1"); + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + Utils.WarnUnimplementedCollectAssocGlyphs(this.ToString()); + } + } + + class LkSubTableT6Fmt2 : LookupSubTable + { + //Format 2 describes class-based chaining context substitution. + //For this format, a specific integer, called a class value, + //must be assigned to each glyph component in all context glyph sequences. + //Contexts are then defined as sequences of glyph class values. + //More than one context may be defined at a time. + + //For this format, the Coverage table lists indices for the complete set of unique glyphs + //(not glyph classes) that may appear as the first glyph of any class-based context. + //In other words, the Coverage table contains the list of glyph indices for all the glyphs in all classes + //that may be first in any of the context class sequences + + public CoverageTable CoverageTable { get; set; } + public ClassDefTable BacktrackClassDef { get; set; } + public ClassDefTable InputClassDef { get; set; } + public ClassDefTable LookaheadClassDef { get; set; } + public ChainSubClassSet[] ChainSubClassSets { get; set; } + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + + int coverage_pos = CoverageTable.FindPosition(glyphIndices[pos]); + if (coverage_pos < 0) { return false; } + + //-- + + int inputClass = InputClassDef.GetClassValue(glyphIndices[pos]); + + + Utils.WarnUnimplemented("GSUB, " + nameof(LkSubTableT6Fmt2)); + return false; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + Utils.WarnUnimplementedCollectAssocGlyphs(this.ToString()); + } + } + + class LkSubTableT6Fmt3 : LookupSubTable + { + public CoverageTable[] BacktrackingCoverages { get; set; } + public CoverageTable[] InputCoverages { get; set; } + public CoverageTable[] LookaheadCoverages { get; set; } + public SubstLookupRecord[] SubstLookupRecords { get; set; } + + public override bool DoSubstitutionAt(IGlyphIndexList glyphIndices, int pos, int len) + { + int inputLength = InputCoverages.Length; + + // Check that there are enough context glyphs + if (pos < BacktrackingCoverages.Length || + inputLength + LookaheadCoverages.Length > len) + { + return false; + } + + // Check all coverages: if any of them does not match, abort substitution + for (int i = 0; i < InputCoverages.Length; ++i) + { + if (InputCoverages[i].FindPosition(glyphIndices[pos + i]) < 0) + { + return false; + } + } + + for (int i = 0; i < BacktrackingCoverages.Length; ++i) + { + if (BacktrackingCoverages[i].FindPosition(glyphIndices[pos - 1 - i]) < 0) + { + return false; + } + } + + for (int i = 0; i < LookaheadCoverages.Length; ++i) + { + if (LookaheadCoverages[i].FindPosition(glyphIndices[pos + inputLength + i]) < 0) + { + return false; + } + } + + // It's a match! Perform substitutions and return true if anything changed + if (SubstLookupRecords.Length == 0) + { + //handled, NO substituion + return true; + } + + bool hasChanged = false; + foreach (SubstLookupRecord lookupRecord in SubstLookupRecords) + { + ushort replaceAt = lookupRecord.sequenceIndex; + ushort lookupIndex = lookupRecord.lookupListIndex; + + LookupTable anotherLookup = OwnerGSub.LookupList[lookupIndex]; + if (anotherLookup.DoSubstitutionAt(glyphIndices, pos + replaceAt, len - replaceAt)) + { + hasChanged = true; + } + } + + return hasChanged; + } + public override void CollectAssociatedSubtitutionGlyphs(List outputAssocGlyphs) + { + foreach (SubstLookupRecord lookupRecord in SubstLookupRecords) + { + ushort replaceAt = lookupRecord.sequenceIndex; + ushort lookupIndex = lookupRecord.lookupListIndex; + + LookupTable anotherLookup = OwnerGSub.LookupList[lookupIndex]; + anotherLookup.CollectAssociatedSubstitutionGlyph(outputAssocGlyphs); + } + } + } + + /// + /// LookupType 6: Chaining Contextual Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType6(BinaryReader reader, long subTableStartAt) + { + //LookupType 6: Chaining Contextual Substitution Subtable + //A Chaining Contextual Substitution subtable (ChainContextSubst) describes glyph substitutions in context with an ability to look back and/or look ahead + //in the sequence of glyphs. + //The design of the Chaining Contextual Substitution subtable is parallel to that of the Contextual Substitution subtable, + //including the availability of three formats for handling sequences of glyphs, glyph classes, or glyph sets. Each format can describe one or more backtrack, + //input, and lookahead sequences and one or more substitutions for each sequence. + //----------------------- + //TODO: impl here + + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + + ushort format = reader.ReadUInt16(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + //6.1 Chaining Context Substitution Format 1: Simple Chaining Context Glyph Substitution + //------------------------------- + //ChainContextSubstFormat1 subtable: Simple context glyph substitution + //------------------------------- + //Type Name Description + //uint16 SubstFormat Format identifier-format = 1 + //Offset16 Coverage Offset to Coverage table-from beginning of Substitution table + //uint16 ChainSubRuleSetCount Number of ChainSubRuleSet tables-must equal GlyphCount in Coverage table + //Offset16 ChainSubRuleSet[ChainSubRuleSetCount] Array of offsets to ChainSubRuleSet tables-from beginning of Substitution table-ordered by Coverage Index + //------------------------------- + + var subTable = new LkSubTableT6Fmt1(); + ushort coverage = reader.ReadUInt16(); + ushort chainSubRulesetCount = reader.ReadUInt16(); + ushort[] chainSubRulesetOffsets = Utils.ReadUInt16Array(reader, chainSubRulesetCount); + ChainSubRuleSetTable[] subRuleSets = subTable.SubRuleSets = new ChainSubRuleSetTable[chainSubRulesetCount]; + for (int n = 0; n < chainSubRulesetCount; ++n) + { + subRuleSets[n] = ChainSubRuleSetTable.CreateFrom(reader, subTableStartAt + chainSubRulesetOffsets[n]); + } + //---------------------------- + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return subTable; + } + case 2: + { + //------------------- + //ChainContextSubstFormat2 subtable: Class-based chaining context glyph substitution + //------------------- + //Type Name Description + //uint16 SubstFormat Format identifier-format = 2 + //Offset16 Coverage Offset to Coverage table-from beginning of Substitution table + //Offset16 BacktrackClassDef Offset to glyph ClassDef table containing backtrack sequence data-from beginning of Substitution table + //Offset16 InputClassDef Offset to glyph ClassDef table containing input sequence data-from beginning of Substitution table + //Offset16 LookaheadClassDef Offset to glyph ClassDef table containing lookahead sequence data-from beginning of Substitution table + //uint16 ChainSubClassSetCnt Number of ChainSubClassSet tables + //Offset16 ChainSubClassSet[ChainSubClassSetCnt] Array of offsets to ChainSubClassSet tables-from beginning of Substitution table-ordered by input class-may be NULL + //------------------- + var subTable = new LkSubTableT6Fmt2(); + ushort coverage = reader.ReadUInt16(); + ushort backtrackClassDefOffset = reader.ReadUInt16(); + ushort inputClassDefOffset = reader.ReadUInt16(); + ushort lookaheadClassDefOffset = reader.ReadUInt16(); + ushort chainSubClassSetCount = reader.ReadUInt16(); + ushort[] chainSubClassSetOffsets = Utils.ReadUInt16Array(reader, chainSubClassSetCount); + // + subTable.BacktrackClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + backtrackClassDefOffset); + subTable.InputClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + inputClassDefOffset); + subTable.LookaheadClassDef = ClassDefTable.CreateFrom(reader, subTableStartAt + lookaheadClassDefOffset); + if (chainSubClassSetCount != 0) + { + ChainSubClassSet[] chainSubClassSets = subTable.ChainSubClassSets = new ChainSubClassSet[chainSubClassSetCount]; + for (int n = 0; n < chainSubClassSetCount; ++n) + { + ushort offset = chainSubClassSetOffsets[n]; + if (offset > 0) + { + chainSubClassSets[n] = ChainSubClassSet.CreateFrom(reader, subTableStartAt + offset); + } + } + } + + subTable.CoverageTable = CoverageTable.CreateFrom(reader, subTableStartAt + coverage); + return subTable; + } + case 3: + { + //------------------- + //6.3 Chaining Context Substitution Format 3: Coverage-based Chaining Context Glyph Substitution + //------------------- + //uint16 substFormat Format identifier: format = 3 + //uint16 backtrackGlyphCount Number of glyphs in the backtracking sequence + //Offset16 backtrackCoverageOffsets[backtrackGlyphCount] Array of offsets to coverage tables in backtracking sequence, in glyph sequence order + //uint16 inputGlyphCount Number of glyphs in input sequence + //Offset16 inputCoverageOffsets[InputGlyphCount] Array of offsets to coverage tables in input sequence, in glyph sequence order + //uint16 lookaheadGlyphCount Number of glyphs in lookahead sequence + //Offset16 lookaheadCoverageOffsets[LookaheadGlyphCount] Array of offsets to coverage tables in lookahead sequence, in glyph sequence order + //uint16 substitutionCount Number of SubstLookupRecords + //struct substLookupRecords[SubstCount] Array of SubstLookupRecords, in design order + //------------------- + LkSubTableT6Fmt3 subTable = new LkSubTableT6Fmt3(); + ushort backtrackingGlyphCount = reader.ReadUInt16(); + ushort[] backtrackingCoverageOffsets = Utils.ReadUInt16Array(reader, backtrackingGlyphCount); + ushort inputGlyphCount = reader.ReadUInt16(); + ushort[] inputGlyphCoverageOffsets = Utils.ReadUInt16Array(reader, inputGlyphCount); + ushort lookAheadGlyphCount = reader.ReadUInt16(); + ushort[] lookAheadCoverageOffsets = Utils.ReadUInt16Array(reader, lookAheadGlyphCount); + ushort substCount = reader.ReadUInt16(); + subTable.SubstLookupRecords = SubstLookupRecord.CreateSubstLookupRecords(reader, substCount); + + subTable.BacktrackingCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, backtrackingCoverageOffsets, reader); + subTable.InputCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, inputGlyphCoverageOffsets, reader); + subTable.LookaheadCoverages = CoverageTable.CreateMultipleCoverageTables(subTableStartAt, lookAheadCoverageOffsets, reader); + + return subTable; + } + } + } + + /// + /// LookupType 7: Extension Substitution + /// + /// + static LookupSubTable ReadLookupType7(BinaryReader reader, long subTableStartAt) + { + //LookupType 7: Extension Substitution + //https://www.microsoft.com/typography/otspec/gsub.htm#ES + + //This lookup provides a mechanism whereby any other lookup type's subtables are stored at a 32-bit offset location in the 'GSUB' table. + //This is needed if the total size of the subtables exceeds the 16-bit limits of the various other offsets in the 'GSUB' table. + //In this specification, the subtable stored at the 32-bit offset location is termed the “extension” subtable. + //---------------------------- + //ExtensionSubstFormat1 subtable + //---------------------------- + //Type Name Description + //uint16 SubstFormat Format identifier.Set to 1. + //uint16 ExtensionLookupType Lookup type of subtable referenced by ExtensionOffset (i.e.the extension subtable). + //Offset32 ExtensionOffset Offset to the extension subtable, of lookup type ExtensionLookupType, relative to the start of the ExtensionSubstFormat1 subtable. + //---------------------------- + //ExtensionLookupType must be set to any lookup type other than 7. + //All subtables in a LookupType 7 lookup must have the same ExtensionLookupType. + //All offsets in the extension subtables are set in the usual way, + //i.e.relative to the extension subtables themselves. + + //When an OpenType layout engine encounters a LookupType 7 Lookup table, it shall: + + //Proceed as though the Lookup table's LookupType field were set to the ExtensionLookupType of the subtables. + //Proceed as though each extension subtable referenced by ExtensionOffset replaced the LookupType 7 subtable that referenced it. + + //Substitution Lookup Record + + //All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). + //Each record contains a SequenceIndex, + //which indicates the position where the substitution will occur in the glyph sequence. + //In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex. + + //The contextual substitution subtables defined in Examples 7, 8, and 9 at the end of this chapter show SubstLookupRecords. + reader.BaseStream.Seek(subTableStartAt, SeekOrigin.Begin); + ushort format = reader.ReadUInt16(); + ushort extensionLookupType = reader.ReadUInt16(); + uint extensionOffset = reader.ReadUInt32(); + if (extensionLookupType == 7) + { + throw new OpenFontNotSupportedException(); + } + // Simply read the lookup table again with updated offsets + return ReadSubTable(extensionLookupType, reader, subTableStartAt + extensionOffset); + } + + /// + /// LookupType 8: Reverse Chaining Contextual Single Substitution Subtable + /// + /// + static LookupSubTable ReadLookupType8(BinaryReader reader, long subTableStartAt) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GlyphShapingTableEntry.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GlyphShapingTableEntry.cs new file mode 100644 index 00000000..2a4b9e6a --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/GlyphShapingTableEntry.cs @@ -0,0 +1,218 @@ +//Apache2, 2016-present, WinterDev, Sam Hocevar + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#otttables + //Data Types + + // The following data types are used in the OpenType font file.All OpenType fonts use Motorola-style byte ordering (Big Endian): + // Data Type Description + // uint8 8-bit unsigned integer. + // int8 8-bit signed integer. + // uint16 16-bit unsigned integer. + // int16 16-bit signed integer. + // uint24 24-bit unsigned integer. + // uint32 32-bit unsigned integer. + // int32 32-bit signed integer. + // Fixed 32-bit signed fixed-point number(16.16) + // FWORD int16 that describes a quantity in font design units. + // UFWORD uint16 that describes a quantity in font design units. + // F2DOT14 16 - bit signed fixed number with the low 14 bits of fraction(2.14). + // LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904.The value is represented as a signed 64 - bit integer. + // Tag Array of four uint8s(length = 32 bits) used to identify a script, language system, feature, or baseline + // Offset16 Short offset to a table, same as uint16, NULL offset = 0x0000 + // Offset32 Long offset to a table, same as uint32, NULL offset = 0x00000000 + + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + // https://docs.microsoft.com/en-us/typography/opentype/spec/gsub + + public abstract class GlyphShapingTableEntry : TableEntry + { + + public ushort MajorVersion { get; private set; } + public ushort MinorVersion { get; private set; } + + public ScriptList ScriptList { get; private set; } + public FeatureList FeatureList { get; private set; } + + /// + /// read script_list, feature_list, and skip look up table + /// + internal bool OnlyScriptList { get; set; } // + + protected override void ReadContentFrom(BinaryReader reader) + { + //------------------------------------------- + // GPOS/GSUB Header + // The GPOS/GSUB table begins with a header that contains a version number for the table. Two versions are defined. + // Version 1.0 contains offsets to three tables: ScriptList, FeatureList, and LookupList. + // Version 1.1 also includes an offset to a FeatureVariations table. + // For descriptions of these tables, see the chapter, OpenType Layout Common Table Formats . + // Example 1 at the end of this chapter shows a GPOS/GSUB Header table definition. + // + // GPOS/GSUB Header, Version 1.0 + // Value Type Description + // uint16 MajorVersion Major version of the GPOS/GSUB table, = 1 + // uint16 MinorVersion Minor version of the GPOS/GSUB table, = 0 + // Offset16 ScriptList Offset to ScriptList table, from beginning of GPOS/GSUB table + // Offset16 FeatureList Offset to FeatureList table, from beginning of GPOS/GSUB table + // Offset16 LookupList Offset to LookupList table, from beginning of GPOS/GSUB table + // + // GPOS/GSUB Header, Version 1.1 + // Value Type Description + // uint16 MajorVersion Major version of the GPOS/GSUB table, = 1 + // uint16 MinorVersion Minor version of the GPOS/GSUB table, = 1 + // Offset16 ScriptList Offset to ScriptList table, from beginning of GPOS/GSUB table + // Offset16 FeatureList Offset to FeatureList table, from beginning of GPOS/GSUB table + // Offset16 LookupList Offset to LookupList table, from beginning of GPOS/GSUB table + // Offset32 FeatureVariations Offset to FeatureVariations table, from beginning of GPOS/GSUB table (may be NULL) + + long tableStartAt = reader.BaseStream.Position; + + MajorVersion = reader.ReadUInt16(); + MinorVersion = reader.ReadUInt16(); + + ushort scriptListOffset = reader.ReadUInt16(); // from beginning of table + ushort featureListOffset = reader.ReadUInt16(); // from beginning of table + ushort lookupListOffset = reader.ReadUInt16(); // from beginning of table + uint featureVariations = (MinorVersion == 1) ? reader.ReadUInt32() : 0; // from beginning of table + + //----------------------- + //1. scriptlist + ScriptList = ScriptList.CreateFrom(reader, tableStartAt + scriptListOffset); + + if (OnlyScriptList) return; //for preview script-list and feature list only + + //----------------------- + //2. feature list + + FeatureList = FeatureList.CreateFrom(reader, tableStartAt + featureListOffset); + + //3. lookup list + ReadLookupListTable(reader, tableStartAt + lookupListOffset); + + //----------------------- + //4. feature variations + if (featureVariations > 0) + { + ReadFeatureVariations(reader, tableStartAt + featureVariations); + } + } + + void ReadLookupListTable(BinaryReader reader, long lookupListBeginAt) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + // ----------------------- + // LookupList table + // ----------------------- + // Type Name Description + // uint16 LookupCount Number of lookups in this table + // Offset16 Lookup[LookupCount] Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0) + // ----------------------- + // + // Lookup Table + // A Lookup table (Lookup) defines the specific conditions, type, + // and results of a substitution or positioning action that is used to implement a feature. + // For example, a substitution operation requires a list of target glyph indices to be replaced, + // a list of replacement glyph indices, and a description of the type of substitution action. + // Each Lookup table may contain only one type of information (LookupType), + // determined by whether the lookup is part of a GSUB or GPOS table. GSUB supports eight LookupTypes, + // and GPOS supports nine LookupTypes (for details about LookupTypes, see the GSUB and GPOS chapters of the document). + // + // Each LookupType is defined with one or more subtables, + // and each subtable definition provides a different representation format. + // The format is determined by the content of the information required for an operation and by required storage efficiency. + // When glyph information is best presented in more than one format, + // a single lookup may contain more than one subtable, as long as all the subtables are the same LookupType. + // For example, within a given lookup, a glyph index array format may best represent one set of target glyphs, + // whereas a glyph index range format may be better for another set of target glyphs. + // + // During text processing, a client applies a lookup to each glyph in the string before moving to the next lookup. + // A lookup is finished for a glyph after the client makes the substitution/positioning operation. + // To move to the “next” glyph, the client will typically skip all the glyphs that participated in the lookup operation: glyphs + // that were substituted/positioned as well as any other glyphs that formed a context for the operation. + // However, in the case of pair positioning operations (i.e., kerning), + // the “next” glyph in a sequence may be the second glyph of the positioned pair (see pair positioning lookup for details). + // + // A Lookup table contains a LookupType, specified as an integer, that defines the type of information stored in the lookup. + // The LookupFlag specifies lookup qualifiers that assist a text-processing client in substituting or positioning glyphs. + // The SubTableCount specifies the total number of SubTables. + // The SubTable array specifies offsets, measured from the beginning of the Lookup table, to each SubTable enumerated in the SubTable array. + // + // Lookup table + // -------------------------------- + // Type Name Description + // unit16 LookupType Different enumerations for GSUB and GPOS + // unit16 LookupFlag Lookup qualifiers + // unit16 SubTableCount Number of SubTables for this lookup + // Offset16 SubTable[SubTableCount] Array of offsets to SubTables-from beginning of Lookup table + // uint16 MarkFilteringSet Index (base 0) into GDEF mark glyph sets structure. + // *** This field is only present if bit UseMarkFilteringSet of lookup flags is set. + // -------------------------------- + + + // -------------------------------- + //The LookupFlag uses two bytes of data: + + //Each of the first four bits can be set in order to specify additional instructions for applying a lookup to a glyph string.The LookUpFlag bit enumeration table provides details about the use of these bits. + //The fifth bit indicates the presence of a MarkFilteringSet field in the Lookup table. + //The next three bits are reserved for future use. + + //The high byte is set to specify the type of mark attachment. + + + //LookupFlag bit enumeration + //Type Name Description + //0x0001 rightToLeft This bit relates only to the correct processing of the cursive attachment lookup type(GPOS lookup type 3).When this bit is set, the last glyph in a given sequence to which the cursive attachment lookup is applied, will be positioned on the baseline. + // Note: Setting of this bit is not intended to be used by operating systems or applications to determine text direction. + //0x0002 ignoreBaseGlyphs If set, skips over base glyphs + //0x0004 ignoreLigatures If set, skips over ligatures + //0x0008 ignoreMarks If set, skips over all combining marks + //0x0010 useMarkFilteringSet If set, indicates that the lookup table structure is followed by a MarkFilteringSet field. + // The layout engine skips over all mark glyphs not in the mark filtering set indicated. + //0x00E0 reserved For future use(Set to zero) + //0xFF00 markAttachmentType If not zero, skips over all marks of attachment type different from specified. + // -------------------------------- + + + reader.BaseStream.Seek(lookupListBeginAt, SeekOrigin.Begin); + ushort lookupCount = reader.ReadUInt16(); + ushort[] lookupTableOffsets = Utils.ReadUInt16Array(reader, lookupCount); + + //---------------------------------------------- + //load each sub table + + foreach (ushort lookupTableOffset in lookupTableOffsets) + { + long lookupTablePos = lookupListBeginAt + lookupTableOffset; + reader.BaseStream.Seek(lookupTablePos, SeekOrigin.Begin); + + ushort lookupType = reader.ReadUInt16(); //Each Lookup table may contain only one type of information (LookupType) + ushort lookupFlags = reader.ReadUInt16(); + ushort subTableCount = reader.ReadUInt16(); + + //Each LookupType is defined with one or more subtables, and each subtable definition provides a different representation format + ushort[] subTableOffsets = Utils.ReadUInt16Array(reader, subTableCount); + + ushort markFilteringSet = + ((lookupFlags & 0x0010) == 0x0010) ? reader.ReadUInt16() : (ushort)0; + + ReadLookupTable(reader, + lookupTablePos, + lookupType, + lookupFlags, + subTableOffsets, //Array of offsets to SubTables-from beginning of Lookup table + markFilteringSet); + } + } + + protected abstract void ReadLookupTable(BinaryReader reader, long lookupTablePos, + ushort lookupType, ushort lookupFlags, + ushort[] subTableOffsets, ushort markFilteringSet); + protected abstract void ReadFeatureVariations(BinaryReader reader, long featureVariationsBeginAt); + + + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/IGlyphIndexList.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/IGlyphIndexList.cs new file mode 100644 index 00000000..71dc71d6 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/IGlyphIndexList.cs @@ -0,0 +1,35 @@ +//Apache2, 2016-present, WinterDev + + +namespace Typography.OpenFont.Tables +{ + /// + /// replaceable glyph index list + /// + public interface IGlyphIndexList + { + int Count { get; } + ushort this[int index] { get; } + + /// + /// remove:add_new 1:1 + /// + /// + /// + void Replace(int index, ushort newGlyphIndex); + /// + /// remove:add_new >=1:1 + /// + /// + /// + /// + void Replace(int index, int removeLen, ushort newGlyphIndex); + /// + /// remove: add_new 1:>=1 + /// + /// + /// + void Replace(int index, ushort[] newGlyphIndices); + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/JustificationTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/JustificationTable.cs new file mode 100644 index 00000000..070598f9 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/JustificationTable.cs @@ -0,0 +1,290 @@ +//MIT, 2019-present, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + /// + /// The Justification table + /// + public partial class JSTF : TableEntry + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/jstf + + public const string _N = "JSTF"; + public override string Name => _N; + JstfScriptTable[] _jsftScriptTables; + + //The Justification table(JSTF) provides font developers with additional control over glyph substitution and + //positioning in justified text. + + //Text-processing clients now have more options to expand or + //shrink word and glyph spacing so text fills the specified line length. + + protected override void ReadContentFrom(BinaryReader reader) + { + //test this with Arial font + + //JSTF header + //Type Name Description + //uint16 majorVersion Major version of the JSTF table, = 1 + //uint16 minorVersion Minor version of the JSTF table, = 0 + //uint16 jstfScriptCount Number of JstfScriptRecords in this table + //JstfScriptRecord jstfScriptRecords[jstfScriptCount] Array of JstfScriptRecords, in alphabetical order by jstfScriptTag + + //---------- + //JstfScriptRecord + //Type Name Description + //Tag jstfScriptTag 4-byte JstfScript identification + //Offset16 jstfScriptOffset Offset to JstfScript table, from beginning of JSTF Header + + long tableStartAt = reader.BaseStream.Position; + // + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort jstfScriptCount = reader.ReadUInt16(); + + JstfScriptRecord[] recs = new JstfScriptRecord[jstfScriptCount]; + for (int i = 0; i < recs.Length; ++i) + { + recs[i] = new JstfScriptRecord( + Utils.TagToString(reader.ReadUInt32()), + reader.ReadUInt16() + ); + } + + _jsftScriptTables = new JstfScriptTable[recs.Length]; + for (int i = 0; i < recs.Length; ++i) + { + JstfScriptRecord rec = recs[i]; + reader.BaseStream.Position = tableStartAt + rec.jstfScriptOffset; + + JstfScriptTable jstfScriptTable = ReadJstfScriptTable(reader); + jstfScriptTable.ScriptTag = rec.jstfScriptTag; + _jsftScriptTables[i] = jstfScriptTable; + } + } + readonly struct JstfScriptRecord + { + public readonly string jstfScriptTag; + public readonly ushort jstfScriptOffset; + public JstfScriptRecord(string jstfScriptTag, ushort jstfScriptOffset) + { + this.jstfScriptTag = jstfScriptTag; + this.jstfScriptOffset = jstfScriptOffset; + } + } + + public class JstfScriptTable + { + public ushort[] extenderGlyphs; + + public JstfLangSysRecord defaultLangSys; + public JstfLangSysRecord[] other; + + public JstfScriptTable() + { + } + public string ScriptTag { get; set; } +#if DEBUG + public override string ToString() + { + return ScriptTag; + } +#endif + } + + static JstfScriptTable ReadJstfScriptTable(BinaryReader reader) + { + //A Justification Script(JstfScript) table describes the justification information for a single script. + //It consists of an offset to a table that defines extender glyphs(extenderGlyphOffset), + //an offset to a default justification table for the script (defJstfLangSysOffset), + //and a count of the language systems that define justification data(jstfLangSysCount). + + //If a script uses the same justification information for all language systems, + //the font developer defines only the default JstfLangSys table and + //sets the jstfLangSysCount value to zero(0). + + //However, if any language system has unique justification suggestions, + //jstfLangSysCount will be a positive value, + //and the JstfScript table must include an array of records(jstfLangSysRecords), + //one for each language system.Each JstfLangSysRecord contains a language system tag(jstfLangSysTag) and + //an offset to a justification language system table(jstfLangSysOffset). + + //In the jstfLangSysRecords array, records are ordered alphabetically by jstfLangSysTag. + + //JstfScript table + //Type Name Description + //Offset16 extenderGlyphOffset Offset to ExtenderGlyph table, from beginning of JstfScript table(may be NULL) + //Offset16 defJstfLangSysOffset Offset to default JstfLangSys table, from beginning of JstfScript table(may be NULL) + //uint16 jstfLangSysCount Number of JstfLangSysRecords in this table - may be zero(0) + //JstfLangSysRecord jstfLangSysRecords[jstfLangSysCount] Array of JstfLangSysRecords, in alphabetical order by JstfLangSysTag + + JstfScriptTable jstfScriptTable = new JstfScriptTable(); + + long tableStartAt = reader.BaseStream.Position; + + ushort extenderGlyphOffset = reader.ReadUInt16(); + ushort defJstfLangSysOffset = reader.ReadUInt16(); + ushort jstfLangSysCount = reader.ReadUInt16(); + + if (jstfLangSysCount > 0) + { + JstfLangSysRecord[] recs = new JstfLangSysRecord[jstfLangSysCount]; + for (int i = 0; i < jstfLangSysCount; ++i) + { + recs[i] = ReadJstfLangSysRecord(reader); + } + jstfScriptTable.other = recs; + } + + + if (extenderGlyphOffset > 0) + { + reader.BaseStream.Position = tableStartAt + extenderGlyphOffset; + jstfScriptTable.extenderGlyphs = ReadExtenderGlyphTable(reader); + } + + if (defJstfLangSysOffset > 0) + { + reader.BaseStream.Position = tableStartAt + defJstfLangSysOffset; + jstfScriptTable.defaultLangSys = ReadJstfLangSysRecord(reader); + } + return jstfScriptTable; + } + static ushort[] ReadExtenderGlyphTable(BinaryReader reader) + { + //Extender Glyph Table + + //The Extender Glyph table(ExtenderGlyph) lists indices of glyphs, 3 + //such as Arabic kashidas, + //that a client may insert to extend the length of the line for justification. + //The table consists of a count of the extender glyphs for the script (glyphCount) and + //an array of extender glyph indices(extenderGlyphs), arranged in increasing numerical order. + + //ExtenderGlyph table + //Type Name Description + //uint16 glyphCount Number of extender glyphs in this script + //uint16 extenderGlyphs[glyphCount] Extender glyph IDs — in increasing numerical order + + ushort glyphCount = reader.ReadUInt16(); + return Utils.ReadUInt16Array(reader, glyphCount); + } + + + public struct JstfLangSysRecord + { + public JstfPriority[] jstfPriority; + + } + static JstfLangSysRecord ReadJstfLangSysRecord(BinaryReader reader) + { + //Justification Language System Table + + //The Justification Language System(JstfLangSys) table contains an array of justification suggestions, + //ordered by priority. + //A text-processing client doing justification should begin with the suggestion that has a zero(0) priority, + //and then-as necessary - apply suggestions of increasing priority until the text is justified. + + //The font developer defines the number and the meaning of the priority levels. + //Each priority level stands alone; its suggestions are not added to the previous levels. + //The JstfLangSys table consists of a count of the number of priority levels(jstfPriorityCount) and + //an array of offsets to Justification Priority tables(jstfPriorityOffsets), + //stored in priority order. + + //JstfLangSys table + + //stfLangSys table + //Type Name Description + //uint16 jstfPriorityCount Number of JstfPriority tables + //Offset16 jstfPriorityOffsets[jstfPriorityCount] Array of offsets to JstfPriority tables, from beginning of JstfLangSys table, in priority order + + long tableStartAt = reader.BaseStream.Position; + ushort jstfPriorityCount = reader.ReadUInt16(); + ushort[] jstfPriorityOffsets = Utils.ReadUInt16Array(reader, jstfPriorityCount); + + JstfPriority[] jstPriorities = new JstfPriority[jstfPriorityCount]; + + for (int i = 0; i < jstfPriorityOffsets.Length; ++i) + { + reader.BaseStream.Position = tableStartAt + jstfPriorityOffsets[i]; + jstPriorities[i] = ReadJstfPriority(reader); + } + + return new JstfLangSysRecord() { jstfPriority = jstPriorities }; + + } + + public class JstfPriority + { + //JstfPriority table + + //Type Name Description + //Offset16 shrinkageEnableGSUB Offset to shrinkage-enable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL) + //Offset16 shrinkageDisableGSUB Offset to shrinkage-disable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL) + public ushort shrinkageEnableGSUB; + public ushort shrinkageDisableGSUB; + + //Offset16 shrinkageEnableGPOS Offset to shrinkage-enable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL) + //Offset16 shrinkageDisableGPOS Offset to shrinkage-disable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL) + + public ushort shrinkageEnableGPOS; + public ushort shrinkageDisableGPOS; + + //Offset16 shrinkageJstfMax Offset to shrinkage JstfMax table, from beginning of JstfPriority table(may be NULL) + public ushort shrinkageJstfMax; + + //Offset16 extensionEnableGSUB Offset to extension-enable JstfGSUBModList table, from beginnning of JstfPriority table(may be NULL) + //Offset16 extensionDisableGSUB Offset to extension-disable JstfGSUBModList table, from beginning of JstfPriority table(may be NULL) + + public ushort extensionEnableGSUB; + public ushort extensionDisableGSUB; + + //Offset16 extensionEnableGPOS Offset to extension-enable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL) + //Offset16 extensionDisableGPOS Offset to extension-disable JstfGPOSModList table, from beginning of JstfPriority table(may be NULL) + + public ushort extensionEnableGPOS; + public ushort extensionDisableGPOS; + + //Offset16 extensionJstfMax Offset to extension JstfMax table, from beginning of JstfPriority table(may be NULL) + public ushort extensionJstfMax; + } + + static JstfPriority ReadJstfPriority(BinaryReader reader) + { + //Justification Priority Table + //A Justification Priority(JstfPriority) + //table defines justification suggestions for a single priority level. + + //Each priority level specifies whether to enable or disable GSUB and GPOS lookups or + //apply text justification lookups to shrink and extend lines of text. + + //JstfPriority has offsets to four tables with line shrinkage data: + //two are JstfGSUBModList tables for enabling and disabling glyph substitution lookups, and + //two are JstfGPOSModList tables for enabling and disabling glyph positioning lookups. + //Offsets to JstfGSUBModList and JstfGPOSModList tables also are defined for line extension. + + return new JstfPriority() + { + shrinkageEnableGSUB = reader.ReadUInt16(), + shrinkageDisableGSUB = reader.ReadUInt16(), + + shrinkageEnableGPOS = reader.ReadUInt16(), + shrinkageDisableGPOS = reader.ReadUInt16(), + + shrinkageJstfMax = reader.ReadUInt16(), + + extensionEnableGSUB = reader.ReadUInt16(), + extensionDisableGSUB = reader.ReadUInt16(), + + extensionEnableGPOS = reader.ReadUInt16(), + extensionDisableGPOS = reader.ReadUInt16(), + + extensionJstfMax = reader.ReadUInt16(), + }; + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/LigatureCaretListTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/LigatureCaretListTable.cs new file mode 100644 index 00000000..7ac02746 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/LigatureCaretListTable.cs @@ -0,0 +1,215 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //from https://docs.microsoft.com/en-us/typography/opentype/spec/gdef + //Ligature Caret List Table + //The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font. + //The table consists of an offset to a Coverage table that lists all the ligature glyphs (Coverage), + //a count of the defined ligatures (LigGlyphCount), + //and an array of offsets to LigGlyph tables (LigGlyph). + + //The array lists the LigGlyph tables, + //one for each ligature in the Coverage table, in the same order as the Coverage Index. + + //Example 4 at the end of this chapter shows a LigCaretList table. + //LigCaretList table + //Type Name Description + //Offset16 Coverage Offset to Coverage table - from beginning of LigCaretList table + //uint16 LigGlyphCount Number of ligature glyphs + //Offset16 LigGlyph[LigGlyphCount] Array of offsets to LigGlyph tables-from beginning of LigCaretList table-in Coverage Index order + + /// + /// Ligature Caret List Table, defines caret positions for all the ligatures in a font + /// + class LigCaretList + { + LigGlyph[] _ligGlyphs; + CoverageTable _coverageTable; + + public static LigCaretList CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //---- + LigCaretList ligcaretList = new LigCaretList(); + ushort coverageOffset = reader.ReadUInt16(); + ushort ligGlyphCount = reader.ReadUInt16(); + ushort[] ligGlyphOffsets = Utils.ReadUInt16Array(reader, ligGlyphCount); + LigGlyph[] ligGlyphs = new LigGlyph[ligGlyphCount]; + for (int i = 0; i < ligGlyphCount; ++i) + { + ligGlyphs[i] = LigGlyph.CreateFrom(reader, beginAt + ligGlyphOffsets[i]); + } + ligcaretList._ligGlyphs = ligGlyphs; + ligcaretList._coverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset); + return ligcaretList; + } + } + + //A Ligature Glyph table (LigGlyph) contains the caret coordinates for a single ligature glyph. + //The number of coordinate values, each defined in a separate CaretValue table, + //equals the number of components in the ligature minus one (1).*** + + //The LigGlyph table consists of a count of the number of CaretValue tables defined for the ligature (CaretCount) and + //an array of offsets to CaretValue tables (CaretValue). + + //Example 4 at the end of the chapter shows a LigGlyph table. + //LigGlyph table + //Type Name Description + //uint16 CaretCount Number of CaretValues for this ligature (components - 1) + //Offset16 CaretValue[CaretCount] Array of offsets to CaretValue tables-from beginning of LigGlyph table-in increasing coordinate order Caret Values Table + + /// + /// A Ligature Glyph table (LigGlyph) contains the caret coordinates for a single ligature glyph. + /// + class LigGlyph + { + ushort[] _caretValueOffsets; + + public static LigGlyph CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //---------- + LigGlyph ligGlyph = new LigGlyph(); + ushort caretCount = reader.ReadUInt16(); + ligGlyph._caretValueOffsets = Utils.ReadUInt16Array(reader, caretCount); + return ligGlyph; + } + } + + //A Caret Values table (CaretValues), which defines caret positions for a ligature, + //can be any of three possible formats. + //One format uses design units to define the caret position. + //The other two formats use a contour point or (in non-variable fonts) a Device table to fine-tune a caret's position at specific font sizes + //and device resolutions. + //In a variable font, the third format uses a VariationIndex table (a variant of a Device table) + //to reference variation data for adjustment of the caret position for the current variation instance, as needed. + //Caret coordinates are either X or Y values, depending upon the text direction. + + + /// + /// A Caret Values table (CaretValues) + /// + class CaretValues + { + + + } + + //------------------------- + //CaretValue Format 1 + //------------------------- + //The first format (CaretValueFormat1) consists of a format identifier (CaretValueFormat), + //followed by a single coordinate for the caret position (Coordinate). The Coordinate is in design units. + + //This format has the benefits of small size and simplicity, but the Coordinate value cannot be hinted for fine adjustments at different device resolutions. + + //Example 4 at the end of this chapter shows a CaretValueFormat1 table. + //------------------------- + //CaretValueFormat1 table: Design units only + //------------------------- + //Type Name Description + //uint16 CaretValueFormat Format identifier-format = 1 + //int16 Coordinate X or Y value, in design units + //------------------------- + //NOTE: int16 + // + // + //CaretValue Format 2 + // + //The second format (CaretValueFormat2) specifies the caret coordinate in terms of a contour point index on a specific glyph. + //During font hinting, the contour point on the glyph outline may move. + //The point's final position after hinting provides the final value for rendering a given font size. + + //The table contains a format identifier (CaretValueFormat) and a contour point index (CaretValuePoint). + + //Example 5 at the end of this chapter demonstrates a CaretValueFormat2 table. + + //------------------------- + //CaretValueFormat2 table: Contour point + //Type Name Description + //uint16 CaretValueFormat Format identifier-format = 2 + //uint16 CaretValuePoint Contour point index on glyph + //------------------------- + // + //CaretValue Format 3 + + //The third format (CaretValueFormat3) also specifies the value in design units, but, + //in non-variable fonts, it uses a Device table rather than a contour point to adjust the value. + //This format offers the advantage of fine-tuning the Coordinate value for any device resolution. + //(For more information about Device tables, see the chapter, Common Table Formats.) + + //In variable fonts, CaretValueFormat3 must be used to reference variation data to adjust caret positions for different variation instances, + //if needed. In this case, CaretValueFormat3 specifies an offset to a VariationIndex table, which is a variant of the Device table used for variations. + + // Note: While separate VariationIndex table references are required for each value that requires variation, + //two or more values that require the same variation-data values can have offsets that point to the same VariationIndex table, + //and two or more VariationIndex tables can reference the same variation data entries. + + // Note: If no VariationIndex table is used for a particular caret position value, then that value is used for all variation instances. + + //The format consists of a format identifier (CaretValueFormat), an X or Y value (Coordinate), and an offset to a Device or VariationIndex table. + + //Example 6 at the end of this chapter shows a CaretValueFormat3 table. + + //------------------------- + //CaretValueFormat3 table: Design units plus Device or VariationIndex table + //Type Name Description + //uint16 CaretValueFormat Format identifier-format = 3 + //int16 Coordinate X or Y value, in design units + //Offset16 DeviceTable Offset to Device table (non-variable font) / Variation Index table (variable font) for X or Y value-from beginning of CaretValue table + //------------------------------------------------------------------------------- + //NOTE: Offset16 + //------------------------------------------------------------------------------- + // + //Mark Attachment Class Definition Table + + //A Mark Attachment Class Definition Table defines the class to which a mark glyph may belong. + //This table uses the same format as the Class Definition table (for details, see the chapter, Common Table Formats ). + + //Example 7 in this document shows a MarkAttachClassDef table. + //Mark Glyph Sets Table + + //Mark glyph sets are used in GSUB and GPOS lookups to filter which marks in a string are considered or ignored. + //Mark glyph sets are defined in a MarkGlyphSets table, which contains offsets to individual sets each represented by a standard Coverage table: + + //--------------------------------------------------------- + //MarkGlyphSetsTable + //--------------------------------------------------------- + //Type Name Description + //uint16 MarkSetTableFormat Format identifier == 1 + //uint16 MarkSetCount Number of mark sets defined + //Offset32 Coverage [MarkSetCount] Array of offsets to mark set coverage tables. + //--------------------------------------------------------- + //Mark glyph sets are used for the same purpose as mark attachment classes, which is as filters for GSUB and GPOS lookups. + //Mark glyph sets differ from mark attachment classes, however, + //in that mark glyph sets may intersect as needed by the font developer. + //As for mark attachment classes, only one mark glyph set can be referenced in any given lookup. + + //Note that the array of offsets for the Coverage tables uses ULONG, not Offset. *** + + class MarkGlyphSetsTable + { + ushort _format; + uint[] _coverageOffset; + + public static MarkGlyphSetsTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + // + MarkGlyphSetsTable markGlyphSetsTable = new MarkGlyphSetsTable(); + markGlyphSetsTable._format = reader.ReadUInt16(); + ushort markSetCount = reader.ReadUInt16(); + uint[] coverageOffset = markGlyphSetsTable._coverageOffset = new uint[markSetCount]; + for (int i = 0; i < markSetCount; ++i) + { + //Note that the array of offsets for the Coverage tables uses ULONG + coverageOffset[i] = reader.ReadUInt32();// + } + + return markGlyphSetsTable; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/MathTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/MathTable.cs new file mode 100644 index 00000000..ffc1a890 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/MathTable.cs @@ -0,0 +1,1331 @@ +//MIT, 2018-present, WinterDev +//https://docs.microsoft.com/en-us/typography/opentype/spec/math + +using System.IO; + + +namespace Typography.OpenFont.MathGlyphs +{ + + public readonly struct MathValueRecord + { + //MathValueRecord + //Type Name Description + //int16 Value The X or Y value in design units + //Offset16 DeviceTable Offset to the device table – from the beginning of parent table.May be NULL. Suggested format for device table is 1. + public readonly short Value; + public readonly ushort DeviceTable; + public MathValueRecord(short value, ushort deviceTable) + { + this.Value = value; + this.DeviceTable = deviceTable; + } +#if DEBUG + public override string ToString() + { + if (DeviceTable == 0) + { + return Value.ToString(); + } + else + { + return Value + "," + DeviceTable; + } + + } +#endif + } + + public class MathConstants + { + //MathConstantsTable + //When selecting names for values in the MathConstants table, the following naming convention should be used: + + //Height – Specifies a distance from the main baseline. + //Kern – Represents a fixed amount of empty space to be introduced. + //Gap – Represents an amount of empty space that may need to be increased to meet certain criteria. + //Drop and Rise – Specifies the relationship between measurements of two elements to be positioned relative to each other(but not necessarily in a stack - like manner) that must meet certain criteria.For a Drop, one of the positioned elements has to be moved down to satisfy those criteria; for a Rise, the movement is upwards. + //Shift – Defines a vertical shift applied to an element sitting on a baseline. + //Dist – Defines a distance between baselines of two elements. + + /// + /// Percentage of scaling down for script level 1. + /// Suggested value: 80%. + /// + public short ScriptPercentScaleDown { get; internal set; } + /// + /// Percentage of scaling down for script level 2 (ScriptScript). + /// Suggested value: 60%. + /// + public short ScriptScriptPercentScaleDown { get; internal set; } + /// + /// Minimum height required for a delimited expression to be treated as a sub-formula. + /// Suggested value: normal line height ×1.5. + /// + public ushort DelimitedSubFormulaMinHeight { get; internal set; } + /// + /// Minimum height of n-ary operators (such as integral and summation) for formulas in display mode. + /// + public ushort DisplayOperatorMinHeight { get; internal set; } + + + /// + /// White space to be left between math formulas to ensure proper line spacing. + /// For example, for applications that treat line gap as a part of line ascender, + /// formulas with ink going above (os2.sTypoAscender + os2.sTypoLineGap - MathLeading) + /// or with ink going below os2.sTypoDescender will result in increasing line height. + /// + public MathValueRecord MathLeading { get; internal set; } + /// + /// Axis height of the font. + /// + public MathValueRecord AxisHeight { get; internal set; } + /// + /// Maximum (ink) height of accent base that does not require raising the accents. + /// Suggested: x‑height of the font (os2.sxHeight) plus any possible overshots. + /// + public MathValueRecord AccentBaseHeight { get; internal set; } + /// + ///Maximum (ink) height of accent base that does not require flattening the accents. + ///Suggested: cap height of the font (os2.sCapHeight). + /// + public MathValueRecord FlattenedAccentBaseHeight { get; internal set; } + + //--------------------------------------------------------- + /// + /// The standard shift down applied to subscript elements. + /// Positive for moving in the downward direction. + /// Suggested: os2.ySubscriptYOffset. + /// + public MathValueRecord SubscriptShiftDown { get; internal set; } + /// + /// Maximum allowed height of the (ink) top of subscripts that does not require moving subscripts further down. + /// Suggested: 4/5 x- height. + /// + public MathValueRecord SubscriptTopMax { get; internal set; } + /// + /// Minimum allowed drop of the baseline of subscripts relative to the (ink) bottom of the base. + /// Checked for bases that are treated as a box or extended shape. + /// Positive for subscript baseline dropped below the base bottom. + /// + public MathValueRecord SubscriptBaselineDropMin { get; internal set; } + /// + /// Standard shift up applied to superscript elements. + /// Suggested: os2.ySuperscriptYOffset. + /// + public MathValueRecord SuperscriptShiftUp { get; internal set; } + /// + /// Standard shift of superscripts relative to the base, in cramped style. + /// + public MathValueRecord SuperscriptShiftUpCramped { get; internal set; } + /// + /// Minimum allowed height of the (ink) bottom of superscripts that does not require moving subscripts further up. + /// Suggested: ¼ x-height. + /// + public MathValueRecord SuperscriptBottomMin { get; internal set; } + /// + /// Maximum allowed drop of the baseline of superscripts relative to the (ink) top of the base. Checked for bases that are treated as a box or extended shape. + /// Positive for superscript baseline below the base top. + /// + public MathValueRecord SuperscriptBaselineDropMax { get; internal set; } + /// + /// Minimum gap between the superscript and subscript ink. + /// Suggested: 4×default rule thickness. + /// + public MathValueRecord SubSuperscriptGapMin { get; internal set; } + /// + /// The maximum level to which the (ink) bottom of superscript can be pushed to increase the gap between + /// superscript and subscript, before subscript starts being moved down. + /// Suggested: 4/5 x-height. + /// + public MathValueRecord SuperscriptBottomMaxWithSubscript { get; internal set; } + /// + /// Extra white space to be added after each subscript and superscript. Suggested: 0.5pt for a 12 pt font. + /// + public MathValueRecord SpaceAfterScript { get; internal set; } + + //--------------------------------------------------------- + /// + /// Minimum gap between the (ink) bottom of the upper limit, and the (ink) top of the base operator. + /// + public MathValueRecord UpperLimitGapMin { get; internal set; } + /// + /// Minimum distance between baseline of upper limit and (ink) top of the base operator. + /// + public MathValueRecord UpperLimitBaselineRiseMin { get; internal set; } + /// + /// Minimum gap between (ink) top of the lower limit, and (ink) bottom of the base operator. + /// + public MathValueRecord LowerLimitGapMin { get; internal set; } + /// + /// Minimum distance between baseline of the lower limit and (ink) bottom of the base operator. + /// + public MathValueRecord LowerLimitBaselineDropMin { get; internal set; } + + //--------------------------------------------------------- + /// + /// Standard shift up applied to the top element of a stack. + /// + public MathValueRecord StackTopShiftUp { get; internal set; } + /// + /// Standard shift up applied to the top element of a stack in display style. + /// + public MathValueRecord StackTopDisplayStyleShiftUp { get; internal set; } + /// + /// Standard shift down applied to the bottom element of a stack. + /// Positive for moving in the downward direction. + /// + public MathValueRecord StackBottomShiftDown { get; internal set; } + /// + /// Standard shift down applied to the bottom element of a stack in display style. + /// Positive for moving in the downward direction. + /// + public MathValueRecord StackBottomDisplayStyleShiftDown { get; internal set; } + /// + /// Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element. + /// Suggested: 3×default rule thickness. + /// + public MathValueRecord StackGapMin { get; internal set; } + /// + /// Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the bottom element in display style. + /// Suggested: 7×default rule thickness. + /// + public MathValueRecord StackDisplayStyleGapMin { get; internal set; } + + /// + /// Standard shift up applied to the top element of the stretch stack. + /// + public MathValueRecord StretchStackTopShiftUp { get; internal set; } + /// + /// Standard shift down applied to the bottom element of the stretch stack. + /// Positive for moving in the downward direction. + /// + public MathValueRecord StretchStackBottomShiftDown { get; internal set; } + /// + /// Minimum gap between the ink of the stretched element, and the (ink) bottom of the element above. + /// Suggested: UpperLimitGapMin. + /// + public MathValueRecord StretchStackGapAboveMin { get; internal set; } + /// + /// Minimum gap between the ink of the stretched element, and the (ink) top of the element below. + /// Suggested: LowerLimitGapMin. + /// + public MathValueRecord StretchStackGapBelowMin { get; internal set; } + + + + //--------------------------------------------------------- + /// + /// Standard shift up applied to the numerator. + /// + public MathValueRecord FractionNumeratorShiftUp { get; internal set; } + /// + /// Standard shift up applied to the numerator in display style. Suggested: StackTopDisplayStyleShiftUp. + /// + public MathValueRecord FractionNumeratorDisplayStyleShiftUp { get; internal set; } + /// + /// Standard shift down applied to the denominator. Positive for moving in the downward direction. + /// + public MathValueRecord FractionDenominatorShiftDown { get; internal set; } + /// + /// Standard shift down applied to the denominator in display style. Positive for moving in the downward direction. + /// Suggested: StackBottomDisplayStyleShiftDown + /// + public MathValueRecord FractionDenominatorDisplayStyleShiftDown { get; internal set; } + /// + /// Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar. + /// Suggested: default rule thickness. + /// + public MathValueRecord FractionNumeratorGapMin { get; internal set; } + /// + /// Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the fraction bar in display style. + /// Suggested: 3×default rule thickness + /// + public MathValueRecord FractionNumDisplayStyleGapMin { get; internal set; } + /// + /// Thickness of the fraction bar. + /// Suggested: default rule thickness. + /// + public MathValueRecord FractionRuleThickness { get; internal set; } + /// + /// Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar. + /// Suggested: default rule thickness. + /// + public MathValueRecord FractionDenominatorGapMin { get; internal set; } + /// + /// Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar in display style. + /// Suggested: 3×default rule thickness + /// + public MathValueRecord FractionDenomDisplayStyleGapMin { get; internal set; } + + + + //--------------------------------------------------------- + /// + /// Horizontal distance between the top and bottom elements of a skewed fraction. + /// + public MathValueRecord SkewedFractionHorizontalGap { get; internal set; } + /// + /// Vertical distance between the ink of the top and bottom elements of a skewed fraction + /// + public MathValueRecord SkewedFractionVerticalGap { get; internal set; } + + + + //--------------------------------------------------------- + /// + /// Distance between the overbar and the (ink) top of he base. + /// Suggested: 3×default rule thickness. + /// + public MathValueRecord OverbarVerticalGap { get; internal set; } + /// + /// Thickness of overbar. + /// Suggested: default rule thickness. + /// + public MathValueRecord OverbarRuleThickness { get; internal set; } + /// + /// Extra white space reserved above the overbar. + /// Suggested: default rule thickness. + /// + public MathValueRecord OverbarExtraAscender { get; internal set; } + + + + //--------------------------------------------------------- + /// + /// Distance between underbar and (ink) bottom of the base. + /// Suggested: 3×default rule thickness. + /// + public MathValueRecord UnderbarVerticalGap { get; internal set; } + /// + /// Thickness of underbar. + /// Suggested: default rule thickness. + /// + public MathValueRecord UnderbarRuleThickness { get; internal set; } + /// + /// Extra white space reserved below the underbar. Always positive. + /// Suggested: default rule thickness. + /// + public MathValueRecord UnderbarExtraDescender { get; internal set; } + + + + //--------------------------------------------------------- + /// + /// Space between the (ink) top of the expression and the bar over it. + /// Suggested: 1¼ default rule thickness. + /// + public MathValueRecord RadicalVerticalGap { get; internal set; } + /// + /// Space between the (ink) top of the expression and the bar over it. + /// Suggested: default rule thickness + ¼ x-height. + /// + public MathValueRecord RadicalDisplayStyleVerticalGap { get; internal set; } + /// + /// Thickness of the radical rule. This is the thickness of the rule in designed or constructed radical signs. + /// Suggested: default rule thickness. + /// + public MathValueRecord RadicalRuleThickness { get; internal set; } + /// + /// Extra white space reserved above the radical. + /// Suggested: RadicalRuleThickness. + /// + public MathValueRecord RadicalExtraAscender { get; internal set; } + /// + /// Extra horizontal kern before the degree of a radical, if such is present. + /// + public MathValueRecord RadicalKernBeforeDegree { get; internal set; } + /// + /// Negative kern after the degree of a radical, if such is present. + /// Suggested: −10/18 of em + /// + public MathValueRecord RadicalKernAfterDegree { get; internal set; } + /// + /// Height of the bottom of the radical degree, + /// if such is present, in proportion to the ascender of the radical sign. + /// Suggested: 60%. + /// + public short RadicalDegreeBottomRaisePercent { get; internal set; } + + + //--------------------------------------------------------- + //ONLY this value come from "MathVariants" *** + //I expose that value on this class + public ushort MinConnectorOverlap { get; internal set; } + //--------------------------------------------------------- + } + + public class MathGlyphInfo + { + public readonly ushort GlyphIndex; + public MathGlyphInfo(ushort glyphIndex) + { + this.GlyphIndex = glyphIndex; + } + + public MathValueRecord? ItalicCorrection { get; internal set; } + public MathValueRecord? TopAccentAttachment { get; internal set; } + public bool IsShapeExtensible { get; internal set; } + + //optional + public MathKern TopLeftMathKern => _mathKernRec.TopLeft; + public MathKern TopRightMathKern => _mathKernRec.TopRight; + public MathKern BottomLeftMathKern => _mathKernRec.BottomLeft; + public MathKern BottomRightMathKern => _mathKernRec.BottomRight; + public bool HasSomeMathKern { get; private set; } + + // + MathKernInfoRecord _mathKernRec; + internal void SetMathKerns(MathKernInfoRecord mathKernRec) + { + _mathKernRec = mathKernRec; + HasSomeMathKern = true; + } + + /// + /// vertical glyph construction + /// + public MathGlyphConstruction VertGlyphConstruction; + /// + /// horizontal glyph construction + /// + public MathGlyphConstruction HoriGlyphConstruction; + + } + public class MathGlyphConstruction + { + public MathValueRecord GlyphAsm_ItalicCorrection; + public GlyphPartRecord[] GlyphAsm_GlyphPartRecords; + public MathGlyphVariantRecord[] glyphVariantRecords; + } + + + public readonly struct GlyphPartRecord + { + //Thus, a GlyphPartRecord consists of the following fields: + //1) Glyph ID for the part. + //2) Lengths of the connectors on each end of the glyph. + // The connectors are straight parts of the glyph that can be used to link it with the next or previous part. + // The connectors of neighboring parts can overlap, which provides flexibility of how these glyphs can be put together.However, the overlap should not be less than the value of MinConnectorOverlap defined in the MathVariants tables, and it should not exceed the length of either of two overlapping connectors.If the part does not have a connector on one of its sides, the corresponding length should be set to zero. + + //3) The full advance of the part. + // It is also used to determine the measurement of the result by using the following formula: + + // *** Size of Assembly = Offset of the Last Part + Full Advance of the Last Part *** + + //4) PartFlags is the last field. + // It identifies a number of parts as extenders – those parts that can be repeated(that is, multiple instances of them can be used in place of one) or skipped altogether.Usually the extenders are vertical or horizontal bars of the appropriate thickness, aligned with the rest of the assembly. + + //To ensure that the width/height is distributed equally and the symmetry of the shape is preserved, + //following steps can be used by math handling client. + + //1. Assemble all parts by overlapping connectors by maximum amount, and removing all extenders. + // This gives the smallest possible result. + + //2. Determine how much extra width/height can be distributed into all connections between neighboring parts. + // If that is enough to achieve the size goal, extend each connection equally by changing overlaps of connectors to finish the job. + //3. If all connections have been extended to minimum overlap and further growth is needed, add one of each extender, + //and repeat the process from the first step. + + //Note that for assemblies growing in vertical direction, + //the distribution of height or the result between ascent and descent is not defined. + //The math handling client is responsible for positioning the resulting assembly relative to the baseline. + + + //GlyphPartRecord Table + //Type Name Description + //uint16 Glyph Glyph ID for the part. + //uint16 StartConnectorLength Advance width/ height of the straight bar connector material, in design units, is at the beginning of the glyph, in the direction of the extension. + //uint16 EndConnectorLength Advance width/ height of the straight bar connector material, in design units, is at the end of the glyph, in the direction of the extension. + //uint16 FullAdvance Full advance width/height for this part, in the direction of the extension.In design units. + //uint16 PartFlags Part qualifiers. PartFlags enumeration currently uses only one bit: + // 0x0001 fExtender If set, the part can be skipped or repeated. + // 0xFFFE Reserved. + + public readonly ushort GlyphId; + public readonly ushort StartConnectorLength; + public readonly ushort EndConnectorLength; + public readonly ushort FullAdvance; + public readonly ushort PartFlags; + public bool IsExtender => (PartFlags & 0x0001) != 0; + + public GlyphPartRecord(ushort glyphId, ushort startConnectorLength, ushort endConnectorLength, ushort fullAdvance, ushort partFlags) + { + GlyphId = glyphId; + StartConnectorLength = startConnectorLength; + EndConnectorLength = endConnectorLength; + FullAdvance = fullAdvance; + PartFlags = partFlags; + } +#if DEBUG + public override string ToString() + { + return "glyph_id:" + GlyphId; + } +#endif + } + + + public readonly struct MathGlyphVariantRecord + { + // MathGlyphVariantRecord Table + //Type Name Description + //uint16 VariantGlyph Glyph ID for the variant. + //uint16 AdvanceMeasurement Advance width/height, in design units, of the variant, in the direction of requested glyph extension. + public readonly ushort VariantGlyph; + public readonly ushort AdvanceMeasurement; + public MathGlyphVariantRecord(ushort variantGlyph, ushort advanceMeasurement) + { + this.VariantGlyph = variantGlyph; + this.AdvanceMeasurement = advanceMeasurement; + } + +#if DEBUG + public override string ToString() + { + return "variant_glyph_id:" + VariantGlyph + ", adv:" + AdvanceMeasurement; + } +#endif + } + + public class MathKern + { + //reference =>see MathKernTable + public ushort HeightCount; + public MathValueRecord[] CorrectionHeights; + public MathValueRecord[] KernValues; + + public MathKern(ushort heightCount, MathValueRecord[] correctionHeights, MathValueRecord[] kernValues) + { + HeightCount = heightCount; + CorrectionHeights = correctionHeights; + KernValues = kernValues; + } + +#if DEBUG + public override string ToString() + { + return HeightCount.ToString(); + } +#endif + } + + readonly struct MathKernInfoRecord + { + //resolved value + public readonly MathKern TopRight; + public readonly MathKern TopLeft; + public readonly MathKern BottomRight; + public readonly MathKern BottomLeft; + public MathKernInfoRecord(MathKern topRight, + MathKern topLeft, + MathKern bottomRight, + MathKern bottomLeft) + { + TopRight = topLeft; + TopLeft = topLeft; + BottomRight = bottomRight; + BottomLeft = bottomLeft; + } + } +} + +namespace Typography.OpenFont.Tables +{ + using Typography.OpenFont.MathGlyphs; + + static class MathValueRecordReaderHelper + { + public static MathValueRecord ReadMathValueRecord(this BinaryReader reader) + { + return new MathValueRecord(reader.ReadInt16(), reader.ReadUInt16()); + } + + public static MathValueRecord[] ReadMathValueRecords(this BinaryReader reader, int count) + { + MathValueRecord[] records = new MathValueRecord[count]; + for (int i = 0; i < count; ++i) + { + records[i] = reader.ReadMathValueRecord(); + } + return records; + } + } + + readonly struct MathGlyphLoader + { + + static MathGlyphInfo GetMathGlyphOrCreateNew(MathGlyphInfo[] mathGlyphInfos, ushort glyphIndex) + { + return mathGlyphInfos[glyphIndex] ?? (mathGlyphInfos[glyphIndex] = new MathGlyphInfo(glyphIndex)); + } + + public static void LoadMathGlyph(Typeface typeface, MathTable mathTable) + { + //expand math info to each glyph in typeface + + typeface._mathTable = mathTable; + + + //expand all information to the glyph + int glyphCount = typeface.GlyphCount; + MathGlyphInfo[] mathGlyphInfos = new MathGlyphInfo[glyphCount]; + + + int index = 0; + //----------------- + //2. MathGlyphInfo + //----------------- + { //2.1 expand italic correction + MathItalicsCorrectonInfoTable italicCorrection = mathTable._mathItalicCorrectionInfo; + index = 0; //reset + if (italicCorrection.CoverageTable != null) + { + foreach (ushort glyphIndex in italicCorrection.CoverageTable.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).ItalicCorrection = italicCorrection.ItalicCorrections[index]; + index++; + } + } + } + //-------- + { + //2.2 expand top accent + MathTopAccentAttachmentTable topAccentAttachment = mathTable._mathTopAccentAttachmentTable; + index = 0; //reset + if (topAccentAttachment.CoverageTable != null) + { + foreach (ushort glyphIndex in topAccentAttachment.CoverageTable.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).TopAccentAttachment = topAccentAttachment.TopAccentAttachment[index]; + index++; + } + } + } + //-------- + { + //2.3 expand , expand shape + index = 0; //reset + if (mathTable._extendedShapeCoverageTable != null) + { + foreach (ushort glyphIndex in mathTable._extendedShapeCoverageTable.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).IsShapeExtensible = true; + index++; + } + } + } + //-------- + { + //2.4 math kern + index = 0; //reset + if (mathTable._mathKernInfoCoverage != null) + { + MathKernInfoRecord[] kernRecs = mathTable._mathKernInfoRecords; + foreach (ushort glyphIndex in mathTable._mathKernInfoCoverage.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).SetMathKerns(kernRecs[index]); + index++; + } + } + } + //----------------- + //3. MathVariants + //----------------- + { + + MathVariantsTable mathVariants = mathTable._mathVariantsTable; + + //3.1 vertical + index = 0; //reset + foreach (ushort glyphIndex in mathVariants.vertCoverage.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).VertGlyphConstruction = mathVariants.vertConstructionTables[index]; + index++; + } + // + //3.2 horizontal + index = 0;//reset + if (mathVariants.horizCoverage != null) + { + foreach (ushort glyphIndex in mathVariants.horizCoverage.GetExpandedValueIter()) + { + GetMathGlyphOrCreateNew(mathGlyphInfos, glyphIndex).HoriGlyphConstruction = mathVariants.horizConstructionTables[index]; + index++; + } + } + } + typeface.LoadMathGlyphInfos(mathGlyphInfos); + } + + } + + + + class MathTable : TableEntry + { + public const string _N = "MATH"; + public override string Name => _N; + // + internal MathConstants _mathConstTable; + + protected override void ReadContentFrom(BinaryReader reader) + { + //eg. latin-modern-math-regular.otf, asana-math.otf + + long beginAt = reader.BaseStream.Position; + //math table header + //Type Name Description + //uint16 MajorVersion Major version of the MATH table, = 1. + //uint16 MinorVersion Minor version of the MATH table, = 0. + //Offset16 MathConstants Offset to MathConstants table -from the beginning of MATH table. + //Offset16 MathGlyphInfo Offset to MathGlyphInfo table -from the beginning of MATH table. + //Offset16 MathVariants Offset to MathVariants table -from the beginning of MATH table. + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort mathConstants_offset = reader.ReadUInt16(); + ushort mathGlyphInfo_offset = reader.ReadUInt16(); + ushort mathVariants_offset = reader.ReadUInt16(); + //--------------------------------- + + //(1) + reader.BaseStream.Position = beginAt + mathConstants_offset; + ReadMathConstantsTable(reader); + // + //(2) + reader.BaseStream.Position = beginAt + mathGlyphInfo_offset; + ReadMathGlyphInfoTable(reader); + // + //(3) + reader.BaseStream.Position = beginAt + mathVariants_offset; + ReadMathVariantsTable(reader); + + //NOTE: expose MinConnectorOverlap via _mathConstTable + _mathConstTable.MinConnectorOverlap = _mathVariantsTable.MinConnectorOverlap; + } + /// + /// (1) MathConstants + /// + /// + void ReadMathConstantsTable(BinaryReader reader) + { + //MathConstants Table + + //The MathConstants table defines miscellaneous constants required to properly position elements of mathematical formulas. + //These constants belong to several groups of semantically related values such as values needed to properly position accents, + //values for positioning superscripts and subscripts, and values for positioning elements of fractions. + //The table also contains general use constants that may affect all parts of the formula, + //such as axis height and math leading.Note that most of the constants deal with the vertical positioning. + + MathConstants mc = new MathConstants(); + mc.ScriptPercentScaleDown = reader.ReadInt16(); + mc.ScriptScriptPercentScaleDown = reader.ReadInt16(); + mc.DelimitedSubFormulaMinHeight = reader.ReadUInt16(); + mc.DisplayOperatorMinHeight = reader.ReadUInt16(); + // + // + + mc.MathLeading = reader.ReadMathValueRecord(); + mc.AxisHeight = reader.ReadMathValueRecord(); + mc.AccentBaseHeight = reader.ReadMathValueRecord(); + mc.FlattenedAccentBaseHeight = reader.ReadMathValueRecord(); + + // + mc.SubscriptShiftDown = reader.ReadMathValueRecord(); + mc.SubscriptTopMax = reader.ReadMathValueRecord(); + mc.SubscriptBaselineDropMin = reader.ReadMathValueRecord(); + + // + mc.SuperscriptShiftUp = reader.ReadMathValueRecord(); + mc.SuperscriptShiftUpCramped = reader.ReadMathValueRecord(); + mc.SuperscriptBottomMin = reader.ReadMathValueRecord(); + mc.SuperscriptBaselineDropMax = reader.ReadMathValueRecord(); + // + mc.SubSuperscriptGapMin = reader.ReadMathValueRecord(); + mc.SuperscriptBottomMaxWithSubscript = reader.ReadMathValueRecord(); + mc.SpaceAfterScript = reader.ReadMathValueRecord(); + + // + mc.UpperLimitGapMin = reader.ReadMathValueRecord(); + mc.UpperLimitBaselineRiseMin = reader.ReadMathValueRecord(); + mc.LowerLimitGapMin = reader.ReadMathValueRecord(); + mc.LowerLimitBaselineDropMin = reader.ReadMathValueRecord(); + + // + mc.StackTopShiftUp = reader.ReadMathValueRecord(); + mc.StackTopDisplayStyleShiftUp = reader.ReadMathValueRecord(); + mc.StackBottomShiftDown = reader.ReadMathValueRecord(); + mc.StackBottomDisplayStyleShiftDown = reader.ReadMathValueRecord(); + mc.StackGapMin = reader.ReadMathValueRecord(); + mc.StackDisplayStyleGapMin = reader.ReadMathValueRecord(); + + // + mc.StretchStackTopShiftUp = reader.ReadMathValueRecord(); + mc.StretchStackBottomShiftDown = reader.ReadMathValueRecord(); + mc.StretchStackGapAboveMin = reader.ReadMathValueRecord(); + mc.StretchStackGapBelowMin = reader.ReadMathValueRecord(); + + // + mc.FractionNumeratorShiftUp = reader.ReadMathValueRecord(); + mc.FractionNumeratorDisplayStyleShiftUp = reader.ReadMathValueRecord(); + mc.FractionDenominatorShiftDown = reader.ReadMathValueRecord(); + mc.FractionDenominatorDisplayStyleShiftDown = reader.ReadMathValueRecord(); + mc.FractionNumeratorGapMin = reader.ReadMathValueRecord(); + mc.FractionNumDisplayStyleGapMin = reader.ReadMathValueRecord(); + mc.FractionRuleThickness = reader.ReadMathValueRecord(); + mc.FractionDenominatorGapMin = reader.ReadMathValueRecord(); + mc.FractionDenomDisplayStyleGapMin = reader.ReadMathValueRecord(); + + // + mc.SkewedFractionHorizontalGap = reader.ReadMathValueRecord(); + mc.SkewedFractionVerticalGap = reader.ReadMathValueRecord(); + + // + mc.OverbarVerticalGap = reader.ReadMathValueRecord(); + mc.OverbarRuleThickness = reader.ReadMathValueRecord(); + mc.OverbarExtraAscender = reader.ReadMathValueRecord(); + + // + mc.UnderbarVerticalGap = reader.ReadMathValueRecord(); + mc.UnderbarRuleThickness = reader.ReadMathValueRecord(); + mc.UnderbarExtraDescender = reader.ReadMathValueRecord(); + + // + mc.RadicalVerticalGap = reader.ReadMathValueRecord(); + mc.RadicalDisplayStyleVerticalGap = reader.ReadMathValueRecord(); + mc.RadicalRuleThickness = reader.ReadMathValueRecord(); + mc.RadicalExtraAscender = reader.ReadMathValueRecord(); + mc.RadicalKernBeforeDegree = reader.ReadMathValueRecord(); + mc.RadicalKernAfterDegree = reader.ReadMathValueRecord(); + mc.RadicalDegreeBottomRaisePercent = reader.ReadInt16(); + + + _mathConstTable = mc; + } + + + //-------------------------------------------------------------------------- + + /// + /// (2) MathGlyphInfo + /// + /// + void ReadMathGlyphInfoTable(BinaryReader reader) + { + + //MathGlyphInfo Table + // The MathGlyphInfo table contains positioning information that is defined on per - glyph basis. + // The table consists of the following parts: + // Offset to MathItalicsCorrectionInfo table that contains information on italics correction values. + // Offset to MathTopAccentAttachment table that contains horizontal positions for attaching mathematical accents. + // Offset to Extended Shape coverage table.The glyphs covered by this table are to be considered extended shapes. + // Offset to MathKernInfo table that provides per - glyph information for mathematical kerning. + + + // NOTE: Here, and elsewhere in the subclause – please refer to subclause 6.2.4 "Features and Lookups" for description of the coverage table formats. + + long startAt = reader.BaseStream.Position; + ushort offsetTo_MathItalicsCorrectionInfo_Table = reader.ReadUInt16(); + ushort offsetTo_MathTopAccentAttachment_Table = reader.ReadUInt16(); + ushort offsetTo_Extended_Shape_coverage_Table = reader.ReadUInt16(); + ushort offsetTo_MathKernInfo_Table = reader.ReadUInt16(); + //----------------------- + + //(2.1) + reader.BaseStream.Position = startAt + offsetTo_MathItalicsCorrectionInfo_Table; + ReadMathItalicCorrectionInfoTable(reader); + + //(2.2) + reader.BaseStream.Position = startAt + offsetTo_MathTopAccentAttachment_Table; + ReadMathTopAccentAttachment(reader); + // + + + //TODO:... + //The glyphs covered by this table are to be considered extended shapes. + //These glyphs are variants extended in the vertical direction, e.g., + //to match height of another part of the formula. + //Because their dimensions may be very large in comparison with normal glyphs in the glyph set, + //the standard positioning algorithms will not produce the best results when applied to them. + //In the vertical direction, other formula elements will be positioned not relative to those glyphs, + //but instead to the ink box of the subexpression containing them + + //.... + + //(2.3) + if (offsetTo_Extended_Shape_coverage_Table > 0) + { + //may be null, eg. found in font Linux Libertine Regular (https://sourceforge.net/projects/linuxlibertine/) + _extendedShapeCoverageTable = CoverageTable.CreateFrom(reader, startAt + offsetTo_Extended_Shape_coverage_Table); + } + + //(2.4) + if (offsetTo_MathKernInfo_Table > 0) + { + //may be null, eg. latin-modern-math.otf => not found + //we found this in Asana-math-regular + reader.BaseStream.Position = startAt + offsetTo_MathKernInfo_Table; + ReadMathKernInfoTable(reader); + } + } + + + /// + /// (2.1) + /// + internal MathItalicsCorrectonInfoTable _mathItalicCorrectionInfo; + /// + /// (2.1) + /// + /// + void ReadMathItalicCorrectionInfoTable(BinaryReader reader) + { + long beginAt = reader.BaseStream.Position; + _mathItalicCorrectionInfo = new MathItalicsCorrectonInfoTable(); + //MathItalicsCorrectionInfo Table + //Type Name Description + //Offset16 Coverage Offset to Coverage table - from the beginning of MathItalicsCorrectionInfo table. + //uint16 ItalicsCorrectionCount Number of italics correction values.Should coincide with the number of covered glyphs. + //MathValueRecord ItalicsCorrection[ItalicsCorrectionCount] Array of MathValueRecords defining italics correction values for each covered glyph. + ushort coverageOffset = reader.ReadUInt16(); + ushort italicCorrectionCount = reader.ReadUInt16(); + _mathItalicCorrectionInfo.ItalicCorrections = reader.ReadMathValueRecords(italicCorrectionCount); + //read coverage ... + if (coverageOffset > 0) + { + //may be null?, eg. found in font Linux Libertine Regular (https://sourceforge.net/projects/linuxlibertine/) + _mathItalicCorrectionInfo.CoverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset); + } + } + + + /// + /// (2.2) + /// + internal MathTopAccentAttachmentTable _mathTopAccentAttachmentTable; + /// + /// (2.2) + /// + /// + void ReadMathTopAccentAttachment(BinaryReader reader) + { + //MathTopAccentAttachment Table + + //The MathTopAccentAttachment table contains information on horizontal positioning of top math accents. + //The table consists of the following parts: + + //Coverage of glyphs for which information on horizontal positioning of math accents is provided. + //To position accents over any other glyph, its geometrical center(with respect to advance width) can be used. + + //Count of covered glyphs. + + //Array of top accent attachment points for each covered glyph, in order of coverage. + //These attachment points are to be used for finding horizontal positions of accents over characters. + //It is done by aligning the attachment point of the base glyph with the attachment point of the accent. + //Note that this is very similar to mark-to-base attachment, but here alignment only happens in the horizontal direction, + //and the vertical positions of accents are determined by different means. + + //MathTopAccentAttachment Table + //Type Name Description + //Offset16 TopAccentCoverage Offset to Coverage table - from the beginning of MathTopAccentAttachment table. + //uint16 TopAccentAttachmentCount Number of top accent attachment point values.Should coincide with the number of covered glyphs. + //MathValueRecord TopAccentAttachment[TopAccentAttachmentCount] Array of MathValueRecords defining top accent attachment points for each covered glyph. + + + long beginAt = reader.BaseStream.Position; + _mathTopAccentAttachmentTable = new MathTopAccentAttachmentTable(); + + ushort coverageOffset = reader.ReadUInt16(); + ushort topAccentAttachmentCount = reader.ReadUInt16(); + _mathTopAccentAttachmentTable.TopAccentAttachment = reader.ReadMathValueRecords(topAccentAttachmentCount); + if (coverageOffset > 0) + { + //may be null?, eg. found in font Linux Libertine Regular (https://sourceforge.net/projects/linuxlibertine/) + _mathTopAccentAttachmentTable.CoverageTable = CoverageTable.CreateFrom(reader, beginAt + coverageOffset); + } + + } + + /// + /// (2.3) + /// + internal CoverageTable _extendedShapeCoverageTable; + + + /// + /// (2.4) + /// + internal CoverageTable _mathKernInfoCoverage; + /// + /// (2.4) + /// + internal MathKernInfoRecord[] _mathKernInfoRecords; + /// + /// (2.4) + /// + /// + void ReadMathKernInfoTable(BinaryReader reader) + { + // MathKernInfo Table + + //The MathKernInfo table provides information on glyphs for which mathematical (height - dependent) kerning values are defined. + //It consists of the following fields: + + // Coverage of glyphs for which mathematical kerning information is provided. + // Count of MathKernInfoRecords.Should coincide with the number of glyphs in Coverage table. + // Array of MathKernInfoRecords for each covered glyph, in order of coverage. + + //MathKernInfo Table + //Type Name Description + //Offset16 MathKernCoverage Offset to Coverage table - from the beginning of the MathKernInfo table. + //uint16 MathKernCount Number of MathKernInfoRecords. + //MathKernInfoRecord MathKernInfoRecords[MathKernCount] Array of MathKernInfoRecords, per - glyph information for mathematical positioning of subscripts and superscripts. + + //... + //Each MathKernInfoRecord points to up to four kern tables for each of the corners around the glyph. + + long beginAt = reader.BaseStream.Position; + + ushort mathKernCoverage_offset = reader.ReadUInt16(); + ushort mathKernCount = reader.ReadUInt16(); + + + //MathKernInfoRecord Table + //Each MathKernInfoRecord points to up to four kern tables for each of the corners around the glyph. + + // //MathKernInfoRecord Table + // //Type Name Description + // //Offset16 TopRightMathKern Offset to MathKern table for top right corner - from the beginning of MathKernInfo table.May be NULL. + // //Offset16 TopLeftMathKern Offset to MathKern table for the top left corner - from the beginning of MathKernInfo table. May be NULL. + // //Offset16 BottomRightMathKern Offset to MathKern table for bottom right corner - from the beginning of MathKernInfo table. May be NULL. + // //Offset16 BottomLeftMathKern Offset to MathKern table for bottom left corner - from the beginning of MathKernInfo table. May be NULL. + + ushort[] allKernRecOffset = Utils.ReadUInt16Array(reader, 4 * mathKernCount);//*** + + //read each kern table + _mathKernInfoRecords = new MathKernInfoRecord[mathKernCount]; + int index = 0; + ushort m_kern_offset = 0; + + for (int i = 0; i < mathKernCount; ++i) + { + + //top-right + m_kern_offset = allKernRecOffset[index]; + + MathKern topRight = null, topLeft = null, bottomRight = null, bottomLeft = null; + + if (m_kern_offset > 0) + { + reader.BaseStream.Position = beginAt + m_kern_offset; + topRight = ReadMathKernTable(reader); + } + //top-left + m_kern_offset = allKernRecOffset[index + 1]; + if (m_kern_offset > 0) + { + reader.BaseStream.Position = beginAt + m_kern_offset; + topLeft = ReadMathKernTable(reader); + } + //bottom-right + m_kern_offset = allKernRecOffset[index + 2]; + if (m_kern_offset > 0) + { + reader.BaseStream.Position = beginAt + m_kern_offset; + bottomRight = ReadMathKernTable(reader); + } + //bottom-left + m_kern_offset = allKernRecOffset[index + 3]; + if (m_kern_offset > 0) + { + reader.BaseStream.Position = beginAt + m_kern_offset; + bottomLeft = ReadMathKernTable(reader); + } + + _mathKernInfoRecords[i] = new MathKernInfoRecord(topRight, topLeft, bottomRight, bottomLeft); + + index += 4;//*** + } + + //----- + _mathKernInfoCoverage = CoverageTable.CreateFrom(reader, beginAt + mathKernCoverage_offset); + + } + /// + /// (2.4) + /// + /// + /// + static MathKern ReadMathKernTable(BinaryReader reader) + { + + //The MathKern table contains adjustments to horizontal positions of subscripts and superscripts + //The kerning algorithm consists of the following steps: + + //1. Calculate vertical positions of subscripts and superscripts. + //2. Set the default horizontal position for the subscript immediately after the base glyph. + //3. Set the default horizontal position for the superscript as shifted relative to the position of the subscript by the italics correction of the base glyph. + //4. Based on the vertical positions, calculate the height of the top/ bottom for the bounding boxes of sub/superscript relative to the base glyph, and the height of the top/ bottom of the base relative to the super/ subscript.These will be the correction heights. + //5. Get the kern values corresponding to these correction heights for the appropriate corner of the base glyph and sub/superscript glyph from the appropriate MathKern tables.Kern the default horizontal positions by the minimum of sums of those values at the correction heights for the base and for the sub/superscript. + //6. If either one of the base or superscript expression has to be treated as a box not providing glyph + //MathKern Table + //Type Name Description + //uint16 HeightCount Number of heights on which the kern value changes. + //MathValueRecord CorrectionHeight[HeightCount] Array of correction heights at which the kern value changes.Sorted by the height value in design units. + //MathValueRecord KernValue[HeightCount+1] Array of kern values corresponding to heights. + + //First value is the kern value for all heights less or equal than the first height in this table. + //Last value is the value to be applied for all heights greater than the last height in this table. + //Negative values are interpreted as "move glyphs closer to each other". + + ushort heightCount = reader.ReadUInt16(); + return new MathKern(heightCount, + reader.ReadMathValueRecords(heightCount), + reader.ReadMathValueRecords(heightCount + 1) + ); + } + + + //-------------------------------------------------------------------------- + + /// + /// (3) + /// + internal MathVariantsTable _mathVariantsTable; + /// + /// (3) MathVariants + /// + /// + void ReadMathVariantsTable(BinaryReader reader) + { + //MathVariants Table + + //The MathVariants table solves the following problem: + //given a particular default glyph shape and a certain width or height, + //find a variant shape glyph(or construct created by putting several glyph together) + //that has the required measurement. + //This functionality is needed for growing the parentheses to match the height of the expression within, + //growing the radical sign to match the height of the expression under the radical, + //stretching accents like tilde when they are put over several characters, + //for stretching arrows, horizontal curly braces, and so forth. + + //The MathVariants table consists of the following fields: + + + // Count and coverage of glyph that can grow in the vertical direction. + // Count and coverage of glyphs that can grow in the horizontal direction. + // MinConnectorOverlap defines by how much two glyphs need to overlap with each other when used to construct a larger shape. + // Each glyph to be used as a building block in constructing extended shapes will have a straight part at either or both ends. + // This connector part is used to connect that glyph to other glyphs in the assembly. + // These connectors need to overlap to compensate for rounding errors and hinting corrections at a lower resolution. + // The MinConnectorOverlap value tells how much overlap is necessary for this particular font. + + // Two arrays of offsets to MathGlyphConstruction tables: + // one array for glyphs that grow in the vertical direction, + // and the other array for glyphs that grow in the horizontal direction. + // The arrays must be arranged in coverage order and have specified sizes. + + + //MathVariants Table + //Type Name Description + //uint16 MinConnectorOverlap Minimum overlap of connecting glyphs during glyph construction, in design units. + //Offset16 VertGlyphCoverage Offset to Coverage table - from the beginning of MathVariants table. + //Offset16 HorizGlyphCoverage Offset to Coverage table - from the beginning of MathVariants table. + //uint16 VertGlyphCount Number of glyphs for which information is provided for vertically growing variants. + //uint16 HorizGlyphCount Number of glyphs for which information is provided for horizontally growing variants. + //Offset16 VertGlyphConstruction[VertGlyphCount] Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in vertical direction. + //Offset16 HorizGlyphConstruction[HorizGlyphCount] Array of offsets to MathGlyphConstruction tables - from the beginning of the MathVariants table, for shapes growing in horizontal direction. + + _mathVariantsTable = new MathVariantsTable(); + + long beginAt = reader.BaseStream.Position; + // + _mathVariantsTable.MinConnectorOverlap = reader.ReadUInt16(); + // + ushort vertGlyphCoverageOffset = reader.ReadUInt16(); + ushort horizGlyphCoverageOffset = reader.ReadUInt16(); + ushort vertGlyphCount = reader.ReadUInt16(); + ushort horizGlyphCount = reader.ReadUInt16(); + ushort[] vertGlyphConstructions = Utils.ReadUInt16Array(reader, vertGlyphCount); + ushort[] horizonGlyphConstructions = Utils.ReadUInt16Array(reader, horizGlyphCount); + // + + if (vertGlyphCoverageOffset > 0) + { + _mathVariantsTable.vertCoverage = CoverageTable.CreateFrom(reader, beginAt + vertGlyphCoverageOffset); + } + + if (horizGlyphCoverageOffset > 0) + { + //may be null?, eg. found in font Linux Libertine Regular (https://sourceforge.net/projects/linuxlibertine/) + _mathVariantsTable.horizCoverage = CoverageTable.CreateFrom(reader, beginAt + horizGlyphCoverageOffset); + } + + //read math construction table + + //(3.1) + //vertical + var vertGlyphConstructionTables = _mathVariantsTable.vertConstructionTables = new MathGlyphConstruction[vertGlyphCount]; + for (int i = 0; i < vertGlyphCount; ++i) + { + reader.BaseStream.Position = beginAt + vertGlyphConstructions[i]; + vertGlyphConstructionTables[i] = ReadMathGlyphConstructionTable(reader); + } + + //(3.2) + //horizon + var horizGlyphConstructionTables = _mathVariantsTable.horizConstructionTables = new MathGlyphConstruction[horizGlyphCount]; + for (int i = 0; i < horizGlyphCount; ++i) + { + reader.BaseStream.Position = beginAt + horizonGlyphConstructions[i]; + horizGlyphConstructionTables[i] = ReadMathGlyphConstructionTable(reader); + } + } + + + /// + /// (3.1, 3.2) + /// + /// + /// + MathGlyphConstruction ReadMathGlyphConstructionTable(BinaryReader reader) + { + + //MathGlyphConstruction Table + //The MathGlyphConstruction table provides information on finding or assembling extended variants for one particular glyph. + //It can be used for shapes that grow in both horizontal and vertical directions. + + //The first entry is the offset to the GlyphAssembly table that specifies how the shape for this glyph can be assembled + //from parts found in the glyph set of the font. + //If no such assembly exists, this offset will be set to NULL. + + //The MathGlyphConstruction table also contains the count and array of ready-made glyph variants for the specified glyph. + //Each variant consists of the glyph index and this glyph’s measurement in the direction of extension(vertical or horizontal). + + //Note that it is quite possible that both the GlyphAssembly table and some variants are defined for a particular glyph. + //For example, the font may specify several variants for curly braces of different sizes, + //and a general mechanism of how larger versions of curly braces can be constructed by stacking parts found in the glyph set. + //First attempt is made to find glyph among provided variants. + // + //However, if the required size is bigger than all glyph variants provided, + //the general mechanism can be employed to typeset the curly braces as a glyph assembly. + + + //MathGlyphConstruction Table + //Type Name Description + //Offset16 GlyphAssembly Offset to GlyphAssembly table for this shape - from the beginning of MathGlyphConstruction table.May be NULL. + //uint16 VariantCount Count of glyph growing variants for this glyph. + //MathGlyphVariantRecord MathGlyphVariantRecord [VariantCount] MathGlyphVariantRecords for alternative variants of the glyphs. + + long beginAt = reader.BaseStream.Position; + + var glyphConstructionTable = new MathGlyphConstruction(); + + ushort glyphAsmOffset = reader.ReadUInt16(); + ushort variantCount = reader.ReadUInt16(); + + var variantRecords = glyphConstructionTable.glyphVariantRecords = new MathGlyphVariantRecord[variantCount]; + + for (int i = 0; i < variantCount; ++i) + { + variantRecords[i] = new MathGlyphVariantRecord( + reader.ReadUInt16(), + reader.ReadUInt16() + ); + } + + + //read glyph asm table + if (glyphAsmOffset > 0)//may be NULL + { + reader.BaseStream.Position = beginAt + glyphAsmOffset; + FillGlyphAssemblyInfo(reader, glyphConstructionTable); + } + return glyphConstructionTable; + } + /// + /// (3.1, 3.2,) + /// + /// + /// + static void FillGlyphAssemblyInfo(BinaryReader reader, MathGlyphConstruction glyphConstruction) + { + //since MathGlyphConstructionTable: GlyphAssembly is 1:1 + //--------- + //GlyphAssembly Table + //The GlyphAssembly table specifies how the shape for a particular glyph can be constructed from parts found in the glyph set. + //The table defines the italics correction of the resulting assembly, and a number of parts that have to be put together to form the required shape. + + //GlyphAssembly + //Type Name Description + //MathValueRecord ItalicsCorrection Italics correction of this GlyphAssembly.Should not depend on the assembly size. + //uint16 PartCount Number of parts in this assembly. + //GlyphPartRecord PartRecords[PartCount] Array of part records, + // from left to right (for assemblies that extend horizontally) and + // bottom to top(for assemblies that extend vertically).. + + //The result of the assembly process is an array of glyphs with an offset specified for each of those glyphs. + //When drawn consecutively at those offsets, the glyphs should combine correctly and produce the required shape. + + //The offsets in the direction of growth (advance offsets), as well as the number of parts labeled as extenders, + //are determined based on the size requirement for the resulting assembly. + + //Note that the glyphs comprising the assembly should be designed so that they align properly in the direction that is orthogonal to the direction of growth. + + + glyphConstruction.GlyphAsm_ItalicCorrection = reader.ReadMathValueRecord(); + ushort partCount = reader.ReadUInt16(); + var partRecords = glyphConstruction.GlyphAsm_GlyphPartRecords = new GlyphPartRecord[partCount]; + for (int i = 0; i < partCount; ++i) + { + partRecords[i] = new GlyphPartRecord( + reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadUInt16() + ); + } + } + } + + class MathItalicsCorrectonInfoTable + { + //MathItalicsCorrectonInfo Table + //The MathItalicsCorrectionInfo table contains italics correction values for slanted glyphs used in math typesetting.The table consists of the following parts: + + // Coverage of glyphs for which the italics correction values are provided.It is assumed to be zero for all other glyphs. + // Count of covered glyphs. + // Array of italic correction values for each covered glyph, in order of coverage.The italics correction is the measurement of how slanted the glyph is, and how much its top part protrudes to the right. For example, taller letters tend to have larger italics correction, and a V will probably have larger italics correction than an L. + + //Italics correction can be used in the following situations: + + // When a run of slanted characters is followed by a straight character (such as an operator or a delimiter), the italics correction of the last glyph is added to its advance width. + // When positioning limits on an N-ary operator (e.g., integral sign), the horizontal position of the upper limit is moved to the right by ½ of the italics correction, while the position of the lower limit is moved to the left by the same distance. + // When positioning superscripts and subscripts, their default horizontal positions are also different by the amount of the italics correction of the preceding glyph. + + public MathValueRecord[] ItalicCorrections; + public CoverageTable CoverageTable; + + } + class MathTopAccentAttachmentTable + { + public MathValueRecord[] TopAccentAttachment; + public CoverageTable CoverageTable; + } + + + class MathVariantsTable + { + public ushort MinConnectorOverlap; + public CoverageTable vertCoverage; + public CoverageTable horizCoverage; + public MathGlyphConstruction[] vertConstructionTables; + public MathGlyphConstruction[] horizConstructionTables; + } + + + + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptList.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptList.cs new file mode 100644 index 00000000..31ad19b9 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptList.cs @@ -0,0 +1,57 @@ +//Apache2, 2016-present, WinterDev + +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + public class ScriptList : Dictionary + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2 + // The ScriptList identifies the scripts in a font, + // each of which is represented by a Script table that contains script and language-system data. + // Language system tables reference features, which are defined in the FeatureList. + // Each feature table references the lookup data defined in the LookupList that describes how, when, and where to implement the feature. + private ScriptList() { } + public new ScriptTable this[uint tagName] => TryGetValue(tagName, out ScriptTable ret) ? ret : null; + + + public static ScriptList CreateFrom(BinaryReader reader, long beginAt) + { + + // ScriptList table + // Type Name Description + // uint16 scriptCount Number of ScriptRecords + // ScriptRecord scriptRecords[scriptCount] Array of ScriptRecords,listed alphabetically by ScriptTag + // + // ScriptRecord + // Type Name Description + // Tag scriptTag 4-byte ScriptTag identifier + // Offset16 scriptOffset Offset to Script table-from beginning of ScriptList + + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + + ushort scriptCount = reader.ReadUInt16(); + ScriptList scriptList = new ScriptList(); + + // Read records (tags and table offsets) + uint[] scriptTags = new uint[scriptCount]; + ushort[] scriptOffsets = new ushort[scriptCount]; + for (int i = 0; i < scriptCount; ++i) + { + scriptTags[i] = reader.ReadUInt32(); + scriptOffsets[i] = reader.ReadUInt16(); + } + + // Read each table and add it to the dictionary + for (int i = 0; i < scriptCount; ++i) + { + ScriptTable scriptTable = ScriptTable.CreateFrom(reader, beginAt + scriptOffsets[i]); + scriptTable.scriptTag = scriptTags[i]; + scriptList.Add(scriptTags[i], scriptTable); + } + + return scriptList; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptTable.cs new file mode 100644 index 00000000..65f66f54 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.AdvancedLayout/ScriptTable.cs @@ -0,0 +1,173 @@ +//Apache2, 2016-present, WinterDev +//https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#script-table-and-language-system-record + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //Script Table and Language System Record + //A Script table identifies each language system that defines how to use the glyphs in a script for a particular language. + //It also references a default language system that defines how to use the script's glyphs in the absence of language-specific knowledge. + + //A Script table begins with an offset to the Default Language System table (DefaultLangSys), + //which defines the set of features that regulate the default behavior of the script. + //Next, Language System Count (LangSysCount) defines the number of language systems (excluding the DefaultLangSys) that use the script. + //In addition, an array of Language System Records (LangSysRecord) defines each language system (excluding the default) + //with an identification tag (LangSysTag) and an offset to a Language System table (LangSys). + //The LangSysRecord array stores the records alphabetically by LangSysTag. + + //If no language-specific script behavior is defined, the LangSysCount is set to zero (0), and no LangSysRecords are allocated. + //----------------------- + //Script table + //Type Name Description + //Offset16 defaultLangSys Offset to DefaultLangSys table-from beginning of Script table-may be NULL + //uint16 langSysCount Number of LangSysRecords for this script-excluding the DefaultLangSys + //LangSysRecord langSysRecords[langSysCount] Array of LangSysRecords-listed alphabetically by LangSysTag + + //----------------------- + //LangSysRecord + //Type Name Description + //Tag langSysTag 4-byte LangSysTag identifier + //Offset16 langSysOffset Offset to LangSys table-from beginning of Script table + //----------------------- + // + //Language System Table + //----------------------- + //The Language System table (LangSys) identifies language-system features + //used to render the glyphs in a script. (The LookupOrder offset is reserved for future use.) + + //Optionally, a LangSys table may define a Required Feature Index (ReqFeatureIndex) to specify one feature as required + //within the context of a particular language system. For example, in the Cyrillic script, + //the Serbian language system always renders certain glyphs differently than the Russian language system. + + //Only one feature index value can be tagged as the ReqFeatureIndex. + //This is not a functional limitation, however, because the feature and lookup definitions in OpenType + //Layout are structured so that one feature table can reference many glyph substitution and positioning lookups. + //When no required features are defined, then the ReqFeatureIndex is set to 0xFFFF. + + //All other features are optional. For each optional feature, + //a zero-based index value references a record (FeatureRecord) in the FeatureRecord array, + //which is stored in a Feature List table (FeatureList). + //The feature indices themselves (excluding the ReqFeatureIndex) are stored in arbitrary order in the FeatureIndex array. + //The FeatureCount specifies the total number of features listed in the FeatureIndex array. + + //Features are specified in full in the FeatureList table, FeatureRecord, and Feature table, + //which are described later in this chapter. + //Example 2 at the end of this chapter shows a Script table, LangSysRecord, and LangSys table used for contextual positioning in the Arabic script. + + //--------------------- + //LangSys table + //Type Name Description + //Offset16 lookupOrder = NULL (reserved for an offset to a reordering table) + //uint16 requiredFeatureIndex Index of a feature required for this language system- if no required features = 0xFFFF + //uint16 featureIndexCount Number of FeatureIndex values for this language system-excludes the required feature + //uint16 featureIndices[featureIndexCount] Array of indices into the FeatureList-in arbitrary order + //--------------------- + public class ScriptTable + { + public uint scriptTag { get; internal set; } + public LangSysTable defaultLang { get; private set; }// be NULL + public LangSysTable[] langSysTables { get; private set; } + + public string ScriptTagName => Utils.TagToString(this.scriptTag); + + public static ScriptTable CreateFrom(BinaryReader reader, long beginAt) + { + reader.BaseStream.Seek(beginAt, SeekOrigin.Begin); + //--------------- + //Script table + //Type Name Description + //Offset16 defaultLangSys Offset to DefaultLangSys table-from beginning of Script table-may be NULL + //uint16 langSysCount Number of LangSysRecords for this script-excluding the DefaultLangSys + //LangSysRecord langSysRecords[langSysCount] Array of LangSysRecords-listed alphabetically by LangSysTag + + //--------------- + ScriptTable scriptTable = new ScriptTable(); + ushort defaultLangSysOffset = reader.ReadUInt16(); + ushort langSysCount = reader.ReadUInt16(); + LangSysTable[] langSysTables = scriptTable.langSysTables = new LangSysTable[langSysCount]; + for (int i = 0; i < langSysCount; ++i) + { + //----------------------- + //LangSysRecord + //Type Name Description + //Tag langSysTag 4-byte LangSysTag identifier + //Offset16 langSysOffset Offset to LangSys table-from beginning of Script table + //----------------------- + + langSysTables[i] = new LangSysTable( + reader.ReadUInt32(), // 4-byte LangSysTag identifier + reader.ReadUInt16()); //offset + } + + //----------- + if (defaultLangSysOffset > 0) + { + scriptTable.defaultLang = new LangSysTable(0, defaultLangSysOffset); + reader.BaseStream.Seek(beginAt + defaultLangSysOffset, SeekOrigin.Begin); + scriptTable.defaultLang.ReadFrom(reader); + } + + + //----------- + //read actual content of each table + for (int i = 0; i < langSysCount; ++i) + { + LangSysTable langSysTable = langSysTables[i]; + reader.BaseStream.Seek(beginAt + langSysTable.offset, SeekOrigin.Begin); + langSysTable.ReadFrom(reader); + } + + return scriptTable; + } + +#if DEBUG + public override string ToString() + { + return Utils.TagToString(this.scriptTag); + } +#endif + + public class LangSysTable + { + //The Language System table (LangSys) identifies language-system features + //used to render the glyphs in a script. (The LookupOrder offset is reserved for future use.) + // + public uint langSysTagIden { get; private set; } + internal readonly ushort offset; + + // + public ushort[] featureIndexList { get; private set; } + public ushort RequiredFeatureIndex { get; private set; } + + public LangSysTable(uint langSysTagIden, ushort offset) + { + this.offset = offset; + this.langSysTagIden = langSysTagIden; + } + public void ReadFrom(BinaryReader reader) + { + //--------------------- + //LangSys table + //Type Name Description + //Offset16 lookupOrder = NULL (reserved for an offset to a reordering table) + //uint16 requiredFeatureIndex Index of a feature required for this language system- if no required features = 0xFFFF + //uint16 featureIndexCount Number of FeatureIndex values for this language system-excludes the required feature + //uint16 featureIndices[featureIndexCount] Array of indices into the FeatureList-in arbitrary order + //--------------------- + + ushort lookupOrder = reader.ReadUInt16();//reserve + RequiredFeatureIndex = reader.ReadUInt16(); + ushort featureIndexCount = reader.ReadUInt16(); + featureIndexList = Utils.ReadUInt16Array(reader, featureIndexCount); + + } + public bool HasRequireFeature => RequiredFeatureIndex != 0xFFFF; + public string LangSysTagIdenString => (langSysTagIden == 0) ? "" : Utils.TagToString(langSysTagIden); +#if DEBUG + public override string ToString() => LangSysTagIdenString; +#endif + + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontGlyphSource.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontGlyphSource.cs new file mode 100644 index 00000000..7e148f40 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontGlyphSource.cs @@ -0,0 +1,42 @@ +//MIT, 2019-present, WinterDev +using System; + +namespace Typography.OpenFont.Tables +{ + class BitmapFontGlyphSource + { + readonly CBLC _cblc; //bitmap locator + CBDT _cbdt; + public BitmapFontGlyphSource(CBLC cblc) => _cblc = cblc; + + /// + /// load new bitmap embeded data + /// + /// + public void LoadCBDT(CBDT cbdt) => _cbdt = cbdt; + + /// + /// clear and remove existing bitmap embeded data + /// + public void UnloadCBDT() + { + if (_cbdt != null) + { + _cbdt.RemoveOldMemoryStreamAndReaders(); + _cbdt = null; + } + } + + public void CopyBitmapContent(Glyph glyph, System.IO.Stream outputStream) => _cbdt.CopyBitmapContent(glyph, outputStream); + + public Glyph[] BuildGlyphList() + { + Glyph[] glyphs = _cblc.BuildGlyphList(); + for (int i = 0; i < glyphs.Length; ++i) + { + _cbdt.FillGlyphInfo(glyphs[i]); + } + return glyphs; + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontsCommon.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontsCommon.cs new file mode 100644 index 00000000..c70467ce --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/BitmapFontsCommon.cs @@ -0,0 +1,851 @@ +//MIT, 2019-present, WinterDev +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables.BitmapFonts +{ + + //from + //https://docs.microsoft.com/en-us/typography/opentype/spec/eblc + //https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt + //https://docs.microsoft.com/en-us/typography/opentype/spec/cblc + //https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt + + struct SbitLineMetrics + { + public sbyte ascender; + public sbyte descender; + public byte widthMax; + + public sbyte caretSlopeNumerator; + public sbyte caretSlopeDenominator; + public sbyte caretOffset; + + public sbyte minOriginSB; + public sbyte minAdvanceSB; + + public sbyte maxBeforeBL; + public sbyte minAfterBL; + + public sbyte pad1; + public sbyte pad2; + + } + class BitmapSizeTable + { + public uint indexSubTableArrayOffset; + public uint indexTablesSize; + public uint numberOfIndexSubTables; + public uint colorRef; + + public SbitLineMetrics hori; + public SbitLineMetrics vert; + + public ushort startGlyphIndex; + public ushort endGlyphIndex; + + public byte ppemX; + public byte ppemY; + public byte bitDepth; + + //bitDepth + //Value Description + //1 black/white + //2 4 levels of gray + //4 16 levels of gray + //8 256 levels of gray + + public sbyte flags; + + //----- + //reconstructed + public IndexSubTableBase[] indexSubTables; + // + static void ReadSbitLineMetrics(BinaryReader reader, ref SbitLineMetrics lineMetric) + { + //read 12 bytes ... + + lineMetric.ascender = (sbyte)reader.ReadByte(); + lineMetric.descender = (sbyte)reader.ReadByte(); + lineMetric.widthMax = reader.ReadByte(); + + lineMetric.caretSlopeNumerator = (sbyte)reader.ReadByte(); + lineMetric.caretSlopeDenominator = (sbyte)reader.ReadByte(); + lineMetric.caretOffset = (sbyte)reader.ReadByte(); + + lineMetric.minOriginSB = (sbyte)reader.ReadByte(); + lineMetric.minAdvanceSB = (sbyte)reader.ReadByte(); + + lineMetric.maxBeforeBL = (sbyte)reader.ReadByte(); + lineMetric.minAfterBL = (sbyte)reader.ReadByte(); + + lineMetric.pad1 = (sbyte)reader.ReadByte(); + lineMetric.pad2 = (sbyte)reader.ReadByte(); + } + + public static BitmapSizeTable ReadBitmapSizeTable(BinaryReader reader) + { + + //EBLC's BitmapSize Table (https://docs.microsoft.com/en-us/typography/opentype/spec/eblc) + //Type Name Description + //Offset32 indexSubTableArrayOffset Offset to IndexSubtableArray, from beginning of EBLC. + //uint32 indexTablesSize Number of bytes in corresponding index subtables and array. + //uint32 numberOfIndexSubTables There is an IndexSubtable for each range or format change. + //uint32 colorRef Not used; set to 0. + //SbitLineMetrics hori Line metrics for text rendered horizontally. + //SbitLineMetrics vert Line metrics for text rendered vertically. + //uint16 startGlyphIndex Lowest glyph index for this size. + //uint16 endGlyphIndex Highest glyph index for this size. + //uint8 ppemX Horizontal pixels per em. + //uint8 ppemY Vertical pixels per em. + //uint8 bitDepth The Microsoft rasterizer v.1.7 or greater supports the following bitDepth values, as described below: 1, 2, 4, and 8. + //int8 flags Vertical or horizontal(see Bitmap Flags, below). + + + //CBLC's BitmapSize Table (https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) + //Type Name Description + //Offset32 indexSubTableArrayOffset Offset to index subtable from beginning of CBLC. + //uint32 indexTablesSize Number of bytes in corresponding index subtables and array. + //uint32 numberofIndexSubTables There is an index subtable for each range or format change. + //uint32 colorRef Not used; set to 0. + //SbitLineMetrics hori Line metrics for text rendered horizontally. + //SbitLineMetrics vert Line metrics for text rendered vertically. + //uint16 startGlyphIndex Lowest glyph index for this size. + //uint16 endGlyphIndex Highest glyph index for this size. + //uint8 ppemX Horizontal pixels per em. + //uint8 ppemY Vertical pixels per em. + //uint8 bitDepth In addtition to already defined bitDepth values 1, 2, 4, and 8 + // supported by existing implementations, the value of 32 is used to + // identify color bitmaps with 8 bit per pixel RGBA channels. + //int8 flags Vertical or horizontal(see the Bitmap Flags section of the EBLC table chapter). + + + //The indexSubTableArrayOffset is the offset from the beginning of + //the CBLC table to the indexSubTableArray. + + //Each strike has one of these arrays to support various formats and + //discontiguous ranges of bitmaps.The indexTablesSize is + //the total number of bytes in the indexSubTableArray and + //the associated indexSubTables. + //The numberOfIndexSubTables is a count of the indexSubTables for this strike. + + + //The rest of the CBLC table structure is identical to one already defined for EBLC. + + + BitmapSizeTable bmpSizeTable = new BitmapSizeTable(); + + bmpSizeTable.indexSubTableArrayOffset = reader.ReadUInt32(); + bmpSizeTable.indexTablesSize = reader.ReadUInt32(); + bmpSizeTable.numberOfIndexSubTables = reader.ReadUInt32(); + bmpSizeTable.colorRef = reader.ReadUInt32(); + + BitmapSizeTable.ReadSbitLineMetrics(reader, ref bmpSizeTable.hori); + BitmapSizeTable.ReadSbitLineMetrics(reader, ref bmpSizeTable.vert); + + + bmpSizeTable.startGlyphIndex = reader.ReadUInt16(); + bmpSizeTable.endGlyphIndex = reader.ReadUInt16(); + bmpSizeTable.ppemX = reader.ReadByte(); + bmpSizeTable.ppemY = reader.ReadByte(); + bmpSizeTable.bitDepth = reader.ReadByte(); + bmpSizeTable.flags = (sbyte)reader.ReadByte(); + + return bmpSizeTable; + } + } + + + + readonly struct IndexSubTableArray + { + public readonly ushort firstGlyphIndex; + public readonly ushort lastGlyphIndex; + public readonly uint additionalOffsetToIndexSubtable; + public IndexSubTableArray(ushort firstGlyphIndex, ushort lastGlyphIndex, uint additionalOffsetToIndexSubtable) + { + this.firstGlyphIndex = firstGlyphIndex; + this.lastGlyphIndex = lastGlyphIndex; + this.additionalOffsetToIndexSubtable = additionalOffsetToIndexSubtable; + } +#if DEBUG + public override string ToString() + { + return "[" + firstGlyphIndex + "-" + lastGlyphIndex + "]"; + } +#endif + } + + readonly struct IndexSubHeader + { + public readonly ushort indexFormat; + public readonly ushort imageFormat; + public readonly uint imageDataOffset; + + public IndexSubHeader(ushort indexFormat, + ushort imageFormat, uint imageDataOffset) + { + this.indexFormat = indexFormat; + this.imageFormat = imageFormat; + this.imageDataOffset = imageDataOffset; + } + +#if DEBUG + public override string ToString() + { + return indexFormat + "," + imageFormat; + } +#endif + } + abstract class IndexSubTableBase + { + public IndexSubHeader header; + + public abstract int SubTypeNo { get; } + public ushort firstGlyphIndex; + public ushort lastGlyphIndex; + + public static IndexSubTableBase CreateFrom(BitmapSizeTable bmpSizeTable, BinaryReader reader) + { + //read IndexSubHeader + //IndexSubHeader + //Type Name Description + //uint16 indexFormat Format of this IndexSubTable. + //uint16 imageFormat Format of EBDT image data. + //Offset32 imageDataOffset Offset to image data in EBDT table. + + //There are currently five different formats used for the IndexSubTable, + //depending upon the size and type of bitmap data in the glyph ID range. + + //Apple 'bloc' tables support only formats 1 through 3. + + //The choice of which IndexSubTable format to use is up to the font manufacturer, + //but should be made with the aim of minimizing the size of the font file. + //Ranges of glyphs with variable metrics — that is, + //where glyphs may differ from each other in bounding box height, width, side bearings or + //advance — must use format 1, 3 or 4. + + //Ranges of glyphs with constant metrics can save space by using format 2 or 5, + //which keep a single copy of the metrics information in the IndexSubTable rather + //than a copy per glyph in the EBDT table. + + //In some monospaced fonts it makes sense to store extra white space around + //some of the glyphs to keep all metrics identical, thus permitting the use of format 2 or 5. + + IndexSubHeader header = new IndexSubHeader( + reader.ReadUInt16(), + reader.ReadUInt16(), + reader.ReadUInt32() + ); + + switch (header.indexFormat) + { + case 1: + + //IndexSubTable1: variable - metrics glyphs with 4 - byte offsets + //Type Name Description + //IndexSubHeader header Header info. + //Offset32 offsetArray[] offsetArray[glyphIndex] + imageDataOffset = glyphData sizeOfArray = (lastGlyph - firstGlyph + 1) + 1 + 1 pad if needed + { + int nElem = (bmpSizeTable.endGlyphIndex - bmpSizeTable.startGlyphIndex + 1); + uint[] offsetArray = Utils.ReadUInt32Array(reader, nElem); + //check 16 bit align padd + IndexSubTable1 subTable = new IndexSubTable1(); + subTable.header = header; + subTable.offsetArray = offsetArray; + return subTable; + } + case 2: + //IndexSubTable2: all glyphs have identical metrics + //Type Name Description + //IndexSubHeader header Header info. + //uint32 imageSize All the glyphs are of the same size. + //BigGlyphMetrics bigMetrics All glyphs have the same metrics; glyph data may be compressed, byte-aligned, or bit-aligned. + { + IndexSubTable2 subtable = new IndexSubTable2(); + subtable.header = header; + subtable.imageSize = reader.ReadUInt32(); + BigGlyphMetrics.ReadBigGlyphMetric(reader, ref subtable.BigGlyphMetrics); + return subtable; + } + + case 3: + //IndexSubTable3: variable - metrics glyphs with 2 - byte offsets + //Type Name Description + //IndexSubHeader header Header info. + //Offset16 offsetArray[] offsetArray[glyphIndex] + imageDataOffset = glyphData sizeOfArray = (lastGlyph - firstGlyph + 1) + 1 + 1 pad if needed + { + int nElem = (bmpSizeTable.endGlyphIndex - bmpSizeTable.startGlyphIndex + 1); + ushort[] offsetArray = Utils.ReadUInt16Array(reader, nElem); + //check 16 bit align padd + IndexSubTable3 subTable = new IndexSubTable3(); + subTable.header = header; + subTable.offsetArray = offsetArray; + return subTable; + } + case 4: + //IndexSubTable4: variable - metrics glyphs with sparse glyph codes + //Type Name Description + //IndexSubHeader header Header info. + //uint32 numGlyphs Array length. + //GlyphIdOffsetPair glyphArray[numGlyphs + 1] One per glyph. + { + IndexSubTable4 subTable = new IndexSubTable4(); + subTable.header = header; + + uint numGlyphs = reader.ReadUInt32(); + GlyphIdOffsetPair[] glyphArray = subTable.glyphArray = new GlyphIdOffsetPair[numGlyphs + 1]; + for (int i = 0; i <= numGlyphs; ++i) //*** + { + glyphArray[i] = new GlyphIdOffsetPair(reader.ReadUInt16(), reader.ReadUInt16()); + } + return subTable; + } + case 5: + //IndexSubTable5: constant - metrics glyphs with sparse glyph codes + //Type Name Description + //IndexSubHeader header Header info. + //uint32 imageSize All glyphs have the same data size. + //BigGlyphMetrics bigMetrics All glyphs have the same metrics. + //uint32 numGlyphs Array length. + //uint16 glyphIdArray[numGlyphs] One per glyph, sorted by glyph ID. + { + IndexSubTable5 subTable = new IndexSubTable5(); + subTable.header = header; + + subTable.imageSize = reader.ReadUInt32(); + BigGlyphMetrics.ReadBigGlyphMetric(reader, ref subTable.BigGlyphMetrics); + subTable.glyphIdArray = Utils.ReadUInt16Array(reader, (int)reader.ReadUInt32()); + return subTable; + } + + } + + //The size of the EBDT image data can be calculated from the IndexSubTable information. + //For the constant-metrics formats(2 and 5) the image data size is constant, + //and is given in the imageSize field.For the variable metrics formats(1, 3, and 4) + //image data must be stored contiguously and in glyph ID order, + //so the image data size may be calculated by subtracting the offset for + //the current glyph from the offset of the next glyph. + + //Because of this, it is necessary to store one extra element in the offsetArray pointing + //just past the end of the range’s image data. + //This will allow the correct calculation of the image data size for the last glyph in the range. + + //Contiguous, or nearly contiguous, + //ranges of glyph IDs are handled best by formats 1, 2, and 3 which + //store an offset for every glyph ID in the range. + //Very sparse ranges of glyph IDs should use format 4 or 5 which explicitly + //call out the glyph IDs represented in the range. + //A small number of missing glyphs can be efficiently represented in formats 1 or 3 by having + //the offset for the missing glyph be followed by the same offset for + //the next glyph, thus indicating a data size of zero. + + //The only difference between formats 1 and 3 is + //the size of the offsetArray elements: format 1 uses uint32s while format 3 uses uint16s. + //Therefore format 1 can cover a greater range(> 64k bytes) + //while format 3 saves more space in the EBLC table. + //Since the offsetArray elements are added to the imageDataOffset base address in the IndexSubHeader, + //a very large set of glyph bitmap data could be addressed by splitting it into multiple ranges, + //each less than 64k bytes in size, + //allowing the use of the more efficient format 3. + + //The EBLC table specification requires 16 - bit alignment for all subtables. + //This occurs naturally for IndexSubTable formats 1, 2, and 4, + //but may not for formats 3 and 5, + //since they include arrays of type uint16. + //When there is an odd number of elements in these arrays + //**it is necessary to add an extra padding element to maintain proper alignment. + + + return null; + } + + public abstract void BuildGlyphList(List glyphList); + } + /// + /// IndexSubTable1: variable - metrics glyphs with 4 - byte offsets + /// + class IndexSubTable1 : IndexSubTableBase + { + public override int SubTypeNo => 1; + public uint[] offsetArray; + + public override void BuildGlyphList(List glyphList) + { + int n = 0; + for (ushort i = firstGlyphIndex; i <= lastGlyphIndex; ++i) + { + glyphList.Add(new Glyph(i, header.imageDataOffset + offsetArray[n], 0, header.imageFormat)); + n++; + } + } + } + /// + /// IndexSubTable2: all glyphs have identical metrics + /// + class IndexSubTable2 : IndexSubTableBase + { + public override int SubTypeNo => 2; + public uint imageSize; + public BigGlyphMetrics BigGlyphMetrics = new BigGlyphMetrics(); + public override void BuildGlyphList(List glyphList) + { + uint incrementalOffset = 0;//TODO: review this + for (ushort n = firstGlyphIndex; n <= lastGlyphIndex; ++n) + { + glyphList.Add(new Glyph(n, header.imageDataOffset + incrementalOffset, imageSize, header.imageFormat)); + incrementalOffset += imageSize; + } + } + } + /// + /// IndexSubTable3: variable - metrics glyphs with 2 - byte offsets + /// + class IndexSubTable3 : IndexSubTableBase + { + public override int SubTypeNo => 3; + public ushort[] offsetArray; + public override void BuildGlyphList(List glyphList) + { + int n = 0; + for (ushort i = firstGlyphIndex; i <= lastGlyphIndex; ++i) + { + glyphList.Add(new Glyph(i, header.imageDataOffset + offsetArray[n++], 0, header.imageFormat)); + } + } + } + /// + /// IndexSubTable4: variable - metrics glyphs with sparse glyph codes + /// + class IndexSubTable4 : IndexSubTableBase + { + public override int SubTypeNo => 4; + public GlyphIdOffsetPair[] glyphArray; + public override void BuildGlyphList(List glyphList) + { + for (int i = 0; i < glyphArray.Length; ++i) + { + GlyphIdOffsetPair pair = glyphArray[i]; + glyphList.Add(new Glyph(pair.glyphId, header.imageDataOffset + pair.offset, 0, header.imageFormat)); + } + } + } + /// + /// IndexSubTable5: constant - metrics glyphs with sparse glyph codes + /// + class IndexSubTable5 : IndexSubTableBase + { + public override int SubTypeNo => 5; + public uint imageSize; + public BigGlyphMetrics BigGlyphMetrics = new BigGlyphMetrics(); + + public ushort[] glyphIdArray; + public override void BuildGlyphList(List glyphList) + { + uint incrementalOffset = 0;//TODO: review this + for (int i = 0; i < glyphIdArray.Length; ++i) + { + glyphList.Add(new Glyph(glyphIdArray[i], header.imageDataOffset + incrementalOffset, imageSize, header.imageFormat)); + incrementalOffset += imageSize; + } + } + + } + //GlyphIdOffsetPair record: + //Type Name Description + //uint16 glyphID Glyph ID of glyph present. + //Offset16 offset Location in EBDT. + + readonly struct GlyphIdOffsetPair + { + public readonly ushort glyphId; + public readonly ushort offset; + public GlyphIdOffsetPair(ushort glyphId, ushort offset) + { + this.glyphId = glyphId; + this.offset = offset; + } + } + + + //BigGlyphMetrics + //Type Name + //uint8 height + //uint8 width + //int8 horiBearingX + //int8 horiBearingY + //uint8 horiAdvance + //int8 vertBearingX + //int8 vertBearingY + //uint8 vertAdvance + + struct BigGlyphMetrics + { + public byte height; + public byte width; + + public sbyte horiBearingX; + public sbyte horiBearingY; + public byte horiAdvance; + + public sbyte vertBearingX; + public sbyte vertBearingY; + public byte vertAdvance; + + public const int SIZE = 8; //size of BigGlyphMetrics + + public static void ReadBigGlyphMetric(BinaryReader reader, ref BigGlyphMetrics output) + { + + output.height = reader.ReadByte(); + output.width = reader.ReadByte(); + + output.horiBearingX = (sbyte)reader.ReadByte(); + output.horiBearingY = (sbyte)reader.ReadByte(); + output.horiAdvance = reader.ReadByte(); + + output.vertBearingX = (sbyte)reader.ReadByte(); + output.vertBearingY = (sbyte)reader.ReadByte(); + output.vertAdvance = reader.ReadByte(); + } + } + + //SmallGlyphMetrics + //Type Name + //uint8 height + //uint8 width + //int8 bearingX + //int8 bearingY + //uint8 advance + struct SmallGlyphMetrics + { + public byte height; + public byte width; + public sbyte bearingX; + public sbyte bearingY; + public byte advance; + + public const int SIZE = 5; //size of SmallGlyphMetrics + public static void ReadSmallGlyphMetric(BinaryReader reader, out SmallGlyphMetrics output) + { + output = new SmallGlyphMetrics(); + output.height = reader.ReadByte(); + output.width = reader.ReadByte(); + + output.bearingX = (sbyte)reader.ReadByte(); + output.bearingY = (sbyte)reader.ReadByte(); + output.advance = reader.ReadByte(); + } + } + + + //------------ + + abstract class GlyphBitmapDataFormatBase + { + public abstract int FormatNumber { get; } + public abstract void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph); + public abstract void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, System.IO.Stream outputStream); + } + + + /// + /// Format 1: small metrics, byte-aligned data + /// + class GlyphBitmapDataFmt1 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 1; + public SmallGlyphMetrics smallGlyphMetrics; + //Glyph bitmap format 1 consists of small metrics records(either horizontal or vertical + //depending on the flags field of the BitmapSize table within the EBLC table) + //followed by byte aligned bitmap data. + + //The bitmap data begins with the most significant bit of the + //first byte corresponding to the top-left pixel of the bounding box, + //proceeding through succeeding bits moving left to right. + //The data for each row is padded to a byte boundary, + //so the next row begins with the most significant bit of a new byte. + + //1 bits correspond to black, and 0 bits to white. + + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + /// + /// Format 2: small metrics, bit-aligned data + /// + class GlyphBitmapDataFmt2 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 2; + + + //Glyph bitmap format 2 is the same as format 1 except + //that the bitmap data is bit aligned. + + //This means that the data for a new row will begin with the bit immediately + //following the last bit of the previous row. + //The start of each glyph must be byte aligned, + //so the last row of a glyph may require padding. + + //This format takes a little more time to parse, but saves file space compared to format 1. + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + //format 3 Obsolete + //format 4: not support in OpenFont + + //Format 5: metrics in EBLC, bit-aligned image data only + class GlyphBitmapDataFmt5 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 5; + + //Glyph bitmap format 5 is similar to format 2 except + //that no metrics information is included, just the bit aligned data. + //This format is for use with EBLC indexSubTable format 2 or format 5, + //which will contain the metrics information for all glyphs. It works well for Kanji fonts. + + //The rasterizer recalculates + //sbit metrics for Format 5 bitmap data, + //allowing Windows to report correct ABC widths, + //even if the bitmaps have white space on either side of the bitmap image. + //This allows fonts to store monospaced bitmap glyphs in the efficient Format 5 + //without breaking Windows GetABCWidths call. + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + /// + /// Format 6: big metrics, byte-aligned data + /// + class GlyphBitmapDataFmt6 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 6; + public BigGlyphMetrics bigMetrics; + + //Format 6: big metrics, byte-aligned data + //Type Name Description + //BigGlyphMetrics bigMetrics Metrics information for the glyph + //uint8 imageData[variable] Byte-aligned bitmap data + + //Glyph bitmap format 6 is the same as format 1 except that is uses big glyph metrics instead of small. + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + /// + /// Format7: big metrics, bit-aligned data + /// + class GlyphBitmapDataFmt7 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 7; + + public BigGlyphMetrics bigMetrics; + + // + //Type Name Description + //BigGlyphMetrics bigMetrics Metrics information for the glyph + //uint8 imageData[variable] Bit-aligned bitmap data + //Glyph bitmap format 7 is the same as format 2 except that is uses big glyph metrics instead of small. + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + + //EbdtComponent Record + + //The EbdtComponent record is used in glyph bitmap data formats 8 and 9. + //Type Name Description + //uint16 glyphID Component glyph ID + //int8 xOffset Position of component left + //int8 yOffset Position of component top + + //The EbdtComponent record contains the glyph ID of the component, which can be used to look up the location of component glyph data in the EBLC table, as well as xOffset and yOffset values, which specify where to position the top-left corner of the component in the composite.Nested composites (a composite of composites) are allowed, and the number of nesting levels is determined by implementation stack space. + + struct EbdtComponent + { + public ushort glyphID; + public sbyte xOffset; + public sbyte yOffset; + } + + + /// + /// Format 8: small metrics, component data + /// + class GlyphBitmapDataFmt8 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 8; + + public SmallGlyphMetrics smallMetrics; + public byte pad; + public EbdtComponent[] components; + //Format 8: small metrics, component data + //Type Name Description + //SmallGlyphMetrics smallMetrics Metrics information for the glyph + //uint8 pad Pad to 16-bit boundary + //uint16 numComponents Number of components + //EbdtComponent components[numComponents] Array of EbdtComponent records + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + + /// + /// Format 9: + /// + class GlyphBitmapDataFmt9 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 9; + public BigGlyphMetrics bigMetrics; + public EbdtComponent[] components; + + //Format 9: big metrics, component data + //Type Name Description + //BigGlyphMetrics bigMetrics Metrics information for the glyph + //uint16 numComponents Number of components + //EbdtComponent components[numComponents] Array of EbdtComponent records + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + throw new NotImplementedException(); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } + + //Glyph bitmap formats 8 and 9 are used for composite bitmaps. + //For accented characters and other composite glyphs + //it may be more efficient to store + //a copy of each component separately, + //and then use a composite description to construct the finished glyph. + + //The composite formats allow for any number of components, + //and allow the components to be positioned anywhere in the finished glyph. + //Format 8 uses small metrics, and format 9 uses big metrics. + + + //------------ + //for CBDT... + + /// + /// Format 17: small metrics, PNG image data + /// + class GlyphBitmapDataFmt17 : GlyphBitmapDataFormatBase + { + public override int FormatNumber => 17; + + //Format 17: small metrics, PNG image data + //Type Name Description + //smallGlyphMetrics glyphMetrics Metrics information for the glyph + //uint32 dataLen Length of data in bytes + //uint8 data[dataLen] Raw PNG data + + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + SmallGlyphMetrics.ReadSmallGlyphMetric(reader, out SmallGlyphMetrics smallGlyphMetric); + + bitmapGlyph.BitmapGlyphAdvanceWidth = smallGlyphMetric.advance; + bitmapGlyph.Bounds = new Bounds(0, 0, smallGlyphMetric.width, smallGlyphMetric.height); + + //then + //byte[] buff = reader.ReadBytes((int)dataLen); + //System.IO.File.WriteAllBytes("testBitmapGlyph_" + glyph.GlyphIndex + ".png", buff); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + //only read raw png data + reader.BaseStream.Position += SmallGlyphMetrics.SIZE; + uint dataLen = reader.ReadUInt32(); + byte[] rawPngData = reader.ReadBytes((int)dataLen); + outputStream.Write(rawPngData, 0, rawPngData.Length); + } + } + + /// + /// Format 18: big metrics, PNG image data + /// + class GlyphBitmapDataFmt18 : GlyphBitmapDataFormatBase + { + //Format 18: big metrics, PNG image data + //Type Name Description + //bigGlyphMetrics glyphMetrics Metrics information for the glyph + //uint32 dataLen Length of data in bytes + //uint8 data[dataLen] Raw PNG data + public override int FormatNumber => 18; + + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + BigGlyphMetrics bigGlyphMetric = new BigGlyphMetrics(); + BigGlyphMetrics.ReadBigGlyphMetric(reader, ref bigGlyphMetric); + uint dataLen = reader.ReadUInt32(); + + bitmapGlyph.BitmapGlyphAdvanceWidth = bigGlyphMetric.horiAdvance; + bitmapGlyph.Bounds = new Bounds(0, 0, bigGlyphMetric.width, bigGlyphMetric.height); + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + reader.BaseStream.Position += BigGlyphMetrics.SIZE; + uint dataLen = reader.ReadUInt32(); + byte[] rawPngData = reader.ReadBytes((int)dataLen); + outputStream.Write(rawPngData, 0, rawPngData.Length); + } + } + + class GlyphBitmapDataFmt19 : GlyphBitmapDataFormatBase + { + //Format 19: metrics in CBLC table, PNG image data + //Type Name Description + //uint32 dataLen Length of data in bytes + //uint8 data[dataLen] Raw PNG data + public override int FormatNumber => 19; + public override void FillGlyphInfo(BinaryReader reader, Glyph bitmapGlyph) + { + //no glyph info to fill + //TODO::.... + } + public override void ReadRawBitmap(BinaryReader reader, Glyph bitmapGlyph, Stream outputStream) + { + throw new NotImplementedException(); + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBDT.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBDT.cs new file mode 100644 index 00000000..187bad21 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBDT.cs @@ -0,0 +1,110 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; +using Typography.OpenFont.Tables.BitmapFonts; + +namespace Typography.OpenFont.Tables +{ + //test font=> NotoColorEmoji.ttf + + //from https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt + + //Table structure + + //The CBDT table is used to embed color bitmap glyph data. It is used together with the CBLC table, + //which provides embedded bitmap locators. + //The formats of these two tables are backward compatible with the EBDT and EBLC tables + //used for embedded monochrome and grayscale bitmaps. + + //The CBDT table begins with a header containing simply the table version number. + //Type Name Description + //uint16 majorVersion Major version of the CBDT table, = 3. + //uint16 minorVersion Minor version of the CBDT table, = 0. + + //Note that the first version of the CBDT table is 3.0. + + //The rest of the CBDT table is a collection of bitmap data. + //The data can be presented in three possible formats, + //indicated by information in the CBLC table. + //Some of the formats contain metric information plus image data, + //and other formats contain only the image data. Long word alignment is not required for these subtables; + //byte alignment is sufficient. + + class CBDT : TableEntry, IDisposable + { + public const string _N = "CBDT"; + public override string Name => _N; + + readonly GlyphBitmapDataFmt17 _format17 = new GlyphBitmapDataFmt17(); + readonly GlyphBitmapDataFmt18 _format18 = new GlyphBitmapDataFmt18(); + readonly GlyphBitmapDataFmt19 _format19 = new GlyphBitmapDataFmt19(); + + + System.IO.MemoryStream _ms; //sub-stream contains image data + Typography.OpenFont.ByteOrderSwappingBinaryReader _binReader; + + public void Dispose() + { + RemoveOldMemoryStreamAndReaders(); + } + + public void RemoveOldMemoryStreamAndReaders() + { + try + { + if (_binReader != null) + { + ((System.IDisposable)_binReader).Dispose(); + _binReader = null; + } + if (_ms != null) + { + _ms.Dispose(); + _ms = null; + } + } + catch (Exception ex) + { + // + } + } + protected override void ReadContentFrom(BinaryReader reader) + { + + //we copy data from the input mem stream + //and store inside this table for later use. + RemoveOldMemoryStreamAndReaders(); + + //------------------- + byte[] data = reader.ReadBytes((int)this.Header.Length);//*** + _ms = new MemoryStream(data); + _binReader = new ByteOrderSwappingBinaryReader(_ms); + } + public void FillGlyphInfo(Glyph glyph) + { + //int srcOffset, int srcLen, int srcFormat, + _binReader.BaseStream.Position = glyph.BitmapStreamOffset; + switch (glyph.BitmapFormat) + { + case 17: _format17.FillGlyphInfo(_binReader, glyph); break; + case 18: _format18.FillGlyphInfo(_binReader, glyph); break; + case 19: _format19.FillGlyphInfo(_binReader, glyph); break; + default: + throw new OpenFontNotSupportedException(); + } + } + public void CopyBitmapContent(Glyph glyph, System.IO.Stream outputStream) + { + //1 + _binReader.BaseStream.Position = glyph.BitmapStreamOffset; + switch (glyph.BitmapFormat) + { + case 17: _format17.ReadRawBitmap(_binReader, glyph, outputStream); break; + case 18: _format18.ReadRawBitmap(_binReader, glyph, outputStream); break; + case 19: _format19.ReadRawBitmap(_binReader, glyph, outputStream); break; + default: + throw new OpenFontNotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBLC.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBLC.cs new file mode 100644 index 00000000..89b272e4 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/CBLC.cs @@ -0,0 +1,133 @@ +//MIT, 2019-present, WinterDev +using System; +using System.Collections.Generic; +using System.IO; + +using Typography.OpenFont.Tables.BitmapFonts; + +namespace Typography.OpenFont.Tables +{ + //test font=> NotoColorEmoji.ttf + + //from https://docs.microsoft.com/en-us/typography/opentype/spec/cblc + + //Table structure + + //The CBLC table provides embedded bitmap locators. + //It is used together with the CBDT table, which provides embedded, + //color bitmap glyph data. + //The formats of these two tables are backward compatible with the EBDT and EBLC tables + //used for embedded monochrome and grayscale bitmaps. + + //The CBLC table begins with a header containing the table version and number of strikes. + //CblcHeader + //Type Name Description + //uint16 majorVersion Major version of the CBLC table, = 3. + //uint16 minorVersion Minor version of the CBLC table, = 0. + //uint32 numSizes Number of BitmapSize tables + + //Note that the first version of the CBLC table is 3.0. + + //The CblcHeader is followed immediately by the BitmapSize table array(s). + //The numSizes in the CblcHeader indicates the number of BitmapSize tables in the array. + //Each strike is defined by one BitmapSize table. + + /// + /// embeded bitmap locator + /// + class CBLC : TableEntry + { + BitmapSizeTable[] _bmpSizeTables; + + public const string _N = "CBLC"; + public override string Name => _N; + + protected override void ReadContentFrom(BinaryReader reader) + { + long cblcBeginPos = reader.BaseStream.Position; + ushort majorVersion = reader.ReadUInt16(); //3 + ushort minorVersion = reader.ReadUInt16(); //0 + uint numSizes = reader.ReadUInt32(); + + //The CblcHeader is followed immediately by the BitmapSize table array(s). + //The numSizes in the CblcHeader indicates the number of BitmapSize tables in the array. + //Each strike is defined by one BitmapSize table. + BitmapSizeTable[] bmpSizeTables = new BitmapSizeTable[numSizes]; + for (int i = 0; i < numSizes; ++i) + { + bmpSizeTables[i] = BitmapSizeTable.ReadBitmapSizeTable(reader); + } + _bmpSizeTables = bmpSizeTables; + + // + //------- + //IndexSubTableArray + //Type Name Description + //uint16 firstGlyphIndex First glyph ID of this range. + //uint16 lastGlyphIndex Last glyph ID of this range(inclusive). + //Offset32 additionalOffsetToIndexSubtable Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + + //After determining the strike, + //the rasterizer searches this array for the range containing the given glyph ID. + //When the range is found, the additionalOffsetToIndexSubtable is added to the indexSubTableArrayOffset + //to get the offset of the IndexSubTable in the EBLC. + + //The first indexSubTableArray is located after the last bitmapSizeSubTable entry. + //Then the IndexSubTables for the strike follow. + //Another IndexSubTableArray(if more than one strike) and + //its IndexSubTableArray are next. + + //The EBLC continues with an array and IndexSubTables for each strike. + //We now have the offset to the IndexSubTable. + //All IndexSubTable formats begin with an IndexSubHeader which identifies the IndexSubTable format, + //the format of the EBDT image data, + //and the offset from the beginning of the EBDT table to the beginning of the image data for this range. + + for (int n = 0; n < numSizes; ++n) + { + BitmapSizeTable bmpSizeTable = bmpSizeTables[n]; + uint numberofIndexSubTables = bmpSizeTable.numberOfIndexSubTables; + + // + IndexSubTableArray[] indexSubTableArrs = new IndexSubTableArray[numberofIndexSubTables]; + for (uint i = 0; i < numberofIndexSubTables; ++i) + { + indexSubTableArrs[i] = new IndexSubTableArray( + reader.ReadUInt16(), //First glyph ID of this range. + reader.ReadUInt16(), //Last glyph ID of this range (inclusive). + reader.ReadUInt32());//Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + } + + //--- + IndexSubTableBase[] subTables = new IndexSubTableBase[numberofIndexSubTables]; + bmpSizeTable.indexSubTables = subTables; + for (uint i = 0; i < numberofIndexSubTables; ++i) + { + IndexSubTableArray indexSubTableArr = indexSubTableArrs[i]; + reader.BaseStream.Position = cblcBeginPos + bmpSizeTable.indexSubTableArrayOffset + indexSubTableArr.additionalOffsetToIndexSubtable; + + IndexSubTableBase result = subTables[i] = IndexSubTableBase.CreateFrom(bmpSizeTable, reader); + result.firstGlyphIndex = indexSubTableArr.firstGlyphIndex; + result.lastGlyphIndex = indexSubTableArr.lastGlyphIndex; + } + } + } + public Glyph[] BuildGlyphList() + { + List glyphs = new List(); + int numSizes = _bmpSizeTables.Length; + for (int n = 0; n < numSizes; ++n) + { + BitmapSizeTable bmpSizeTable = _bmpSizeTables[n]; + uint numberofIndexSubTables = bmpSizeTable.numberOfIndexSubTables; + for (uint i = 0; i < numberofIndexSubTables; ++i) + { + bmpSizeTable.indexSubTables[i].BuildGlyphList(glyphs); + } + } + return glyphs.ToArray(); + } + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBDT.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBDT.cs new file mode 100644 index 00000000..e32b5b12 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBDT.cs @@ -0,0 +1,64 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //from https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt + + //The EBDT table is used to embed monochrome or grayscale bitmap glyph data. + //It is used together with the EBLC table, + //which provides embedded bitmap locators, + //and the EBSC table, which provides embedded bitmap scaling information. + + //OpenType embedded bitmaps are also called “sbits” (for “scaler bitmaps”). + //A set of bitmaps for a face at a given size is called a strike. + + //The EBLC table identifies the sizes and glyph ranges of the sbits, + //and keeps offsets to glyph bitmap data in indexSubTables. + + //The EBDT table then stores the glyph bitmap data, + //in a number of different possible formats. + //Glyph metrics information may be stored in either the EBLC or EBDT table, + //depending upon the indexSubTable and glyph bitmap data formats. + + //The EBSC table identifies sizes that will be handled by scaling up or scaling down other sbit sizes. + + + //The EBDT table is a super set of Apple’s Apple Advanced Typography (AAT) 'bdat' table. + + + /// + /// Embedded Bitmap Data Table + /// + class EBDT : TableEntry + { + public const string _N = "EBDT"; + public override string Name => _N; + + protected override void ReadContentFrom(BinaryReader reader) + { + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + + //The rest of the EBDT table is a collection of bitmap data. + //The data can be in a number of possible formats, + //indicated by information in the EBLC table. + + //Some of the formats contain metric information plus image data, + //and other formats contain only the image data. + //Long word alignment is not required for these sub tables; + //byte alignment is sufficient. + + //There are also two different formats for glyph metrics: + //big glyph metrics and small glyph metrics. + //Big glyph metrics define metrics information + //for both horizontal and vertical layouts. + //This is important in fonts(such as Kanji) where both types of layout may be used. + //Small glyph metrics define metrics information for one layout direction only. + //Which direction applies, horizontal or vertical, is determined by the flags field in the BitmapSize + //tables within the EBLC table. + + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBLC.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBLC.cs new file mode 100644 index 00000000..79e1edfc --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBLC.cs @@ -0,0 +1,116 @@ +//MIT, 2017-present, WinterDev +//MIT, 2015, Michael Popoloski, WinterDev + +using System; +using System.IO; +using Typography.OpenFont.Tables.BitmapFonts; + +namespace Typography.OpenFont.Tables +{ + /// + /// EBLC : Embedded bitmap location data + /// + class EBLC : TableEntry + { + public const string _N = "EBLC"; + public override string Name => _N; + // + //from https://docs.microsoft.com/en-us/typography/opentype/spec/eblc + //EBLC - Embedded Bitmap Location Table + //---------------------------------------------- + //The EBLC provides embedded bitmap locators.It is used together with the EDBTtable, which provides embedded, monochrome or grayscale bitmap glyph data, and the EBSC table, which provided embedded bitmap scaling information. + //OpenType embedded bitmaps are called 'sbits' (for “scaler bitmaps”). A set of bitmaps for a face at a given size is called a strike. + //The 'EBLC' table identifies the sizes and glyph ranges of the sbits, and keeps offsets to glyph bitmap data in indexSubTables.The 'EBDT' table then stores the glyph bitmap data, also in a number of different possible formats.Glyph metrics information may be stored in either the 'EBLC' or 'EBDT' table, depending upon the indexSubTable and glyph bitmap formats. The 'EBSC' table identifies sizes that will be handled by scaling up or scaling down other sbit sizes. + //The 'EBLC' table uses the same format as the Apple Apple Advanced Typography (AAT) 'bloc' table. + //The 'EBLC' table begins with a header containing the table version and number of strikes.An OpenType font may have one or more strikes embedded in the 'EBDT' table. + //---------------------------------------------- + //eblcHeader + //---------------------------------------------- + //Type Name Description + //uint16 majorVersion Major version of the EBLC table, = 2. + //uint16 minorVersion Minor version of the EBLC table, = 0. + //uint32 numSizes Number of bitmapSizeTables + //---------------------------------------------- + //Note that the first version of the EBLC table is 2.0. + //The eblcHeader is followed immediately by the bitmapSizeTable array(s). + //The numSizes in the eblcHeader indicates the number of bitmapSizeTables in the array. + //Each strike is defined by one bitmapSizeTable. + + BitmapSizeTable[] _bmpSizeTables; + protected override void ReadContentFrom(BinaryReader reader) + { + // load each strike table + long eblcBeginPos = reader.BaseStream.Position; + // + ushort versionMajor = reader.ReadUInt16(); + ushort versionMinor = reader.ReadUInt16(); + uint numSizes = reader.ReadUInt32(); + + if (numSizes > MAX_BITMAP_STRIKES) + throw new Exception("Too many bitmap strikes in font."); + + //---------------- + var bmpSizeTables = new BitmapSizeTable[numSizes]; + for (int i = 0; i < numSizes; i++) + { + bmpSizeTables[i] = BitmapSizeTable.ReadBitmapSizeTable(reader); + } + _bmpSizeTables = bmpSizeTables; + + // + //------- + //IndexSubTableArray + //Type Name Description + //uint16 firstGlyphIndex First glyph ID of this range. + //uint16 lastGlyphIndex Last glyph ID of this range(inclusive). + //Offset32 additionalOffsetToIndexSubtable Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + + //After determining the strike, + //the rasterizer searches this array for the range containing the given glyph ID. + //When the range is found, the additionalOffsetToIndexSubtable is added to the indexSubTableArrayOffset + //to get the offset of the IndexSubTable in the EBLC. + + //The first indexSubTableArray is located after the last bitmapSizeSubTable entry. + //Then the IndexSubTables for the strike follow. + //Another IndexSubTableArray(if more than one strike) and + //its IndexSubTableArray are next. + + //The EBLC continues with an array and IndexSubTables for each strike. + //We now have the offset to the IndexSubTable. + //All IndexSubTable formats begin with an IndexSubHeader which identifies the IndexSubTable format, + //the format of the EBDT image data, + //and the offset from the beginning of the EBDT table to the beginning of the image data for this range. + + for (int n = 0; n < numSizes; ++n) + { + BitmapSizeTable bmpSizeTable = bmpSizeTables[n]; + uint numberofIndexSubTables = bmpSizeTable.numberOfIndexSubTables; + + // + IndexSubTableArray[] indexSubTableArrs = new IndexSubTableArray[numberofIndexSubTables]; + for (uint i = 0; i < numberofIndexSubTables; ++i) + { + indexSubTableArrs[i] = new IndexSubTableArray( + reader.ReadUInt16(), //First glyph ID of this range. + reader.ReadUInt16(), //Last glyph ID of this range (inclusive). + reader.ReadUInt32());//Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + } + + //--- + IndexSubTableBase[] subTables = new IndexSubTableBase[numberofIndexSubTables]; + bmpSizeTable.indexSubTables = subTables; + for (uint i = 0; i < numberofIndexSubTables; ++i) + { + IndexSubTableArray indexSubTableArr = indexSubTableArrs[i]; + reader.BaseStream.Position = eblcBeginPos + bmpSizeTable.indexSubTableArrayOffset + indexSubTableArr.additionalOffsetToIndexSubtable; + + subTables[i] = IndexSubTableBase.CreateFrom(bmpSizeTable, reader); + } + } + } + + + + const int MAX_BITMAP_STRIKES = 1024; + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBSC.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBSC.cs new file mode 100644 index 00000000..e661380e --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/EBSC.cs @@ -0,0 +1,39 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + //from + //https://docs.microsoft.com/en-us/typography/opentype/spec/ebsc + + //The EBSC table provides a mechanism for describing embedded bitmaps + //which are created by scaling other embedded bitmaps. + //While this is the sort of thing that outline font technologies were invented to avoid, + //there are cases (small sizes of Kanji, for example) + //where scaling a bitmap produces a more legible font + //than scan-converting an outline. + + //For this reason the EBSC table allows a font to define a bitmap strike + //as a scaled version of another strike. + + //The EBSC table is used together with the EBDT table, + //which provides embedded monochrome or grayscale bitmap data, + //and the EBLC table, which provides embedded bitmap locators. + + + /// + /// EBSC — Embedded Bitmap Scaling Table + /// + class EBSC : TableEntry + { + public const string _N = "EBSC"; + public override string Name => _N; + + protected override void ReadContentFrom(BinaryReader reader) + { + + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/SvgTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/SvgTable.cs new file mode 100644 index 00000000..99e464d1 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.BitmapAndSvgFonts/SvgTable.cs @@ -0,0 +1,195 @@ +//Apache2, 2017-present, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; +namespace Typography.OpenFont.Tables +{ + class SvgTable : TableEntry + { + public const string _N = "SVG "; //with 1 whitespace *** + public override string Name => _N; + // + //https://docs.microsoft.com/en-us/typography/opentype/spec/svg + //OpenType fonts with either TrueType or CFF outlines may also contain an optional 'SVG ' table, + //which allows some or all glyphs in the font to be defined with color, gradients, or animation. + + + Dictionary _dicSvgEntries; + SvgDocumentEntry[] _entries; //TODO: review again + protected override void ReadContentFrom(BinaryReader reader) + { + long svgTableStartAt = reader.BaseStream.Position; + //SVG Main Header + //Type Name Description + //uint16 version Table version(starting at 0). Set to 0. + //Offset32 svgDocIndexOffset Offset(relative to the start of the SVG table) to the SVG Documents Index.Must be non - zero. + //uint32 reserved Set to 0. + //----------- + ushort version = reader.ReadUInt16(); + uint offset32 = reader.ReadUInt32(); + uint reserved = reader.ReadUInt32(); + //------- + + + //------- + //SVG Document Index + //The SVG Document Index is a set of SVG documents, each of which defines one or more glyph descriptions. + //Type Name Description + //uint16 numEntries Number of SVG Document Index Entries.Must be non - zero. + //SVG Document Index Entry entries[numEntries] Array of SVG Document Index Entries. + // + long svgDocIndexStartAt = svgTableStartAt + offset32; + reader.BaseStream.Seek(svgDocIndexStartAt, SeekOrigin.Begin); + // + ushort numEntries = reader.ReadUInt16(); + // + //SVG Document Index Entry + //Each SVG Document Index Entry specifies a range[startGlyphID, endGlyphID], inclusive, + //of glyph IDs and the location of its associated SVG document in the SVG table. + //Type Name Description + //uint16 startGlyphID The first glyph ID in the range described by this index entry. + //uint16 endGlyphID The last glyph ID in the range described by this index entry. Must be >= startGlyphID. + //Offset32 svgDocOffset Offset from the beginning of the SVG Document Index to an SVG document.Must be non - zero. + //uint32 svgDocLength Length of the SVG document.Must be non - zero. + + //Index entries must be arranged in order of increasing startGlyphID. + // + //...this specification requires that the SVG documents be either plain-text or gzip-encoded [RFC1952]. + //The encoding of the (uncompressed) SVG document must be UTF-8. + //In both cases, svgDocLength encodes the length of the encoded data, not the decoded document. + + _entries = new SvgDocumentEntry[numEntries]; + for (int i = 0; i < numEntries; ++i) + { + _entries[i] = new SvgDocumentEntry() + { + startGlyphID = reader.ReadUInt16(), + endGlyphID = reader.ReadUInt16(), + svgDocOffset = reader.ReadUInt32(), + svgDocLength = reader.ReadUInt32() + }; + } + + //TODO: review lazy load + for (int i = 0; i < numEntries; ++i) + { + //read data + SvgDocumentEntry entry = _entries[i]; + + if (entry.endGlyphID - entry.startGlyphID > 0) + { + //TODO review here again + throw new System.NotSupportedException(); + } + + reader.BaseStream.Seek(svgDocIndexStartAt + entry.svgDocOffset, SeekOrigin.Begin); + + if (entry.svgDocLength == 0) + { + throw new System.NotSupportedException(); + } + + // + byte[] svgData = reader.ReadBytes((int)entry.svgDocLength); + _entries[i].svgBuffer = svgData; + +#if DEBUG + if (svgData.Length == 0) { throw new OpenFontNotSupportedException(); } +#endif + + if (svgData[0] == (byte)'<') + { + //should be plain-text +#if DEBUG + //string svgDataString = System.Text.Encoding.UTF8.GetString(svgData); + //dbugSaveAsHtml("svg" + i + ".html", svgDataString); +#endif + } + else + { + _entries[i].compressed = true; + } + } + } + + public void UnloadSvgData() + { + if (_dicSvgEntries != null) + { + _dicSvgEntries.Clear(); + _dicSvgEntries = null; + } + + _entries = null; + } + + public bool ReadSvgContent(ushort glyphIndex, System.Text.StringBuilder outputStBuilder) + { + if (_dicSvgEntries == null) + { + //create index + _dicSvgEntries = new Dictionary(); + for (int i = 0; i < _entries.Length; ++i) + { + _dicSvgEntries.Add(_entries[i].startGlyphID, i); + } + } + + if (_dicSvgEntries.TryGetValue(glyphIndex, out int entryIndex)) + { + SvgDocumentEntry docEntry = _entries[entryIndex]; + + if (docEntry.compressed) + { + throw new OpenFontNotSupportedException(); + //TODO: decompress this + } + + outputStBuilder.Append(System.Text.Encoding.UTF8.GetString(docEntry.svgBuffer)); + return true; + } + return false; + } +#if DEBUG + static void dbugSaveAsHtml(string filename, string originalGlyphSvg) + { + //xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" + //test save the svg + //save as html document for test + System.Text.StringBuilder stbuilder = new System.Text.StringBuilder(); + stbuilder.Append(""); + + //TODO: add exact SVG reader here + //to view in WebBrowser -> we do Y-flip + string modified = originalGlyphSvg.Replace("xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">", + "xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"800\" height=\"1600\">" + ).Replace("", ""); + stbuilder.Append(modified); + stbuilder.Append(""); + File.WriteAllText(filename, stbuilder.ToString()); + } +#endif + struct SvgDocumentEntry + { + public ushort startGlyphID; + public ushort endGlyphID; + public uint svgDocOffset; + public uint svgDocLength; + + public byte[] svgBuffer; + public bool compressed; + +#if DEBUG + public override string ToString() + { + return "startGlyphID:" + startGlyphID + "," + + "endGlyphID:" + endGlyphID + "," + + "svgDocOffset:" + svgDocOffset + "," + + "svgDocLength:" + svgDocLength; + } +#endif + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFF.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFF.cs new file mode 100644 index 00000000..4b355ce6 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFF.cs @@ -0,0 +1,2183 @@ +//Apache2, 2018, apache/pdfbox Authors ( https://github.com/apache/pdfbox) +// +//Apache PDFBox +//Copyright 2014 The Apache Software Foundation + +//This product includes software developed at +//The Apache Software Foundation(http://www.apache.org/). + +//Based on source code originally developed in the PDFBox and +//FontBox projects. + +//Copyright (c) 2002-2007, www.pdfbox.org + +//Based on source code originally developed in the PaDaF project. +//Copyright (c) 2010 Atos Worldline SAS + +//Includes the Adobe Glyph List +//Copyright 1997, 1998, 2002, 2007, 2010 Adobe Systems Incorporated. + +//Includes the Zapf Dingbats Glyph List +//Copyright 2002, 2010 Adobe Systems Incorporated. + +//Includes OSXAdapter +//Copyright (C) 2003-2007 Apple, Inc., All Rights Reserved + +//---------------- +//Adobe's The Compact Font Format Specification +//from http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf + +//Type1CharString Format spec: +//https://www-cdf.fnal.gov/offline/PostScript/T1_SPEC.PDF + +//Type2CharString Format spec: +//http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + +//------------------------------------------------------------------ +//many areas are ported from Java code +//Apache2, 2018-present, WinterDev + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + + +namespace Typography.OpenFont.CFF +{ + //from: The Compact Font Format Specification (https://www-cdf.fnal.gov/offline/PostScript/5176.CFF.pdf) + //....CFF + //allows multiple fonts to be stored together in a unit called a FontSet. + + //Principal space savings are a result of using a + //compact binary representation for most of the informa- + //tion, sharing of common data between fonts, and + //defaulting frequently occurring data. + + //The CFF format is designed to be used in conjunction with + //Type 2 charstrings for the character description procedures + //(see Adobe Technical Note #5177: “The Type 2 Charstring + //Format”). + + + + class Cff1FontSet + { + internal string[] _fontNames; + internal List _fonts = new List(); + internal string[] _uniqueStringTable; + // + internal const int N_STD_STRINGS = 390; + internal static readonly string[] s_StdStrings = new string[] { + //Appendix A: Standard Strings + ".notdef", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quoteright", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "quoteleft", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "exclamdown", + "cent", + "sterling", + "fraction", + "yen", + "florin", + "section", + "currency", + "quotesingle", + "quotedblleft", + "guillemotleft", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "endash", + "dagger", + "daggerdbl", + "periodcentered", + "paragraph", + "bullet", + "quotesinglbase", + "quotedblbase", + "quotedblright", + "guillemotright", + "ellipsis", + "perthousand", + "questiondown", + "grave", + "acute", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "dieresis", + "ring", + "cedilla", + "hungarumlaut", + "ogonek", + "caron", + "emdash", + "AE", + "ordfeminine", + "Lslash", + "Oslash", + "OE", + "ordmasculine", + "ae", + "dotlessi", + "lslash", + "oslash", + "oe", + "germandbls", + "onesuperior", + "logicalnot", + "mu", + "trademark", + "Eth", + "onehalf", + "plusminus", + "Thorn", + "onequarter", + "divide", + "brokenbar", + "degree", + "thorn", + "threequarters", + "twosuperior", + "registered", + "minus", + "eth", + "multiply", + "threesuperior", + "copyright", + "Aacute", + "Acircumflex", + "Adieresis", + "Agrave", + "Aring", + "Atilde", + "Ccedilla", + "Eacute", + "Ecircumflex", + "Edieresis", + "Egrave", + "Iacute", + "Icircumflex", + "Idieresis", + "Igrave", + "Ntilde", + "Oacute", + "Ocircumflex", + "Odieresis", + "Ograve", + "Otilde", + "Scaron", + "Uacute", + "Ucircumflex", + "Udieresis", + "Ugrave", + "Yacute", + "Ydieresis", + "Zcaron", + "aacute", + "acircumflex", + "adieresis", + "agrave", + "aring", + "atilde", + "ccedilla", + "eacute", + "ecircumflex", + "edieresis", + "egrave", + "iacute", + "icircumflex", + "idieresis", + "igrave", + "ntilde", + "oacute", + "ocircumflex", + "odieresis", + "ograve", + "otilde", + "scaron", + "uacute", + "ucircumflex", + "udieresis", + "ugrave", + "yacute", + "ydieresis", + "zcaron", + "exclamsmall", + "Hungarumlautsmall", + "dollaroldstyle", + "dollarsuperior", + "ampersandsmall", + "Acutesmall", + "parenleftsuperior", + "parenrightsuperior", + "twodotenleader", + "onedotenleader", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "questionsmall", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "isuperior", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "rsuperior", + "ssuperior", + "tsuperior", + "ff", + "ffi", + "ffl", + "parenleftinferior", + "parenrightinferior", + "Circumflexsmall", + "hyphensuperior", + "Gravesmall", + "Asmall", + "Bsmall", + "Csmall", + "Dsmall", + "Esmall", + "Fsmall", + "Gsmall", + "Hsmall", + "Ismall", + "Jsmall", + "Ksmall", + "Lsmall", + "Msmall", + "Nsmall", + "Osmall", + "Psmall", + "Qsmall", + "Rsmall", + "Ssmall", + "Tsmall", + "Usmall", + "Vsmall", + "Wsmall", + "Xsmall", + "Ysmall", + "Zsmall", + "colonmonetary", + "onefitted", + "rupiah", + "Tildesmall", + "exclamdownsmall", + "centoldstyle", + "Lslashsmall", + "Scaronsmall", + "Zcaronsmall", + "Dieresissmall", + "Brevesmall", + "Caronsmall", + "Dotaccentsmall", + "Macronsmall", + "figuredash", + "hypheninferior", + "Ogoneksmall", + "Ringsmall", + "Cedillasmall", + "questiondownsmall", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "zerosuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", + "Agravesmall", + "Aacutesmall", + "Acircumflexsmall", + "Atildesmall", + "Adieresissmall", + "Aringsmall", + "AEsmall", + "Ccedillasmall", + "Egravesmall", + "Eacutesmall", + "Ecircumflexsmall", + "Edieresissmall", + "Igravesmall", + "Iacutesmall", + "Icircumflexsmall", + "Idieresissmall", + "Ethsmall", + "Ntildesmall", + "Ogravesmall", + "Oacutesmall", + "Ocircumflexsmall", + "Otildesmall", + "Odieresissmall", + "OEsmall", + "Oslashsmall", + "Ugravesmall", + "Uacutesmall", + "Ucircumflexsmall", + "Udieresissmall", + "Yacutesmall", + "Thornsmall", + "Ydieresissmall", + "001.000", + "001.001", + "001.002", + "001.003", + "Black", + "Bold", + "Book", + "Light", + "Medium", + "Regular", + "Roman", + "Semibold" };//390 + + } + + class FontDict + { + public int FontName; + public int PrivateDicSize; + public int PrivateDicOffset; + public List LocalSubr; + public FontDict(int dictSize, int dictOffset) + { + PrivateDicSize = dictSize; + PrivateDicOffset = dictOffset; + } + + } + + public class Cff1Font + { + internal string FontName { get; set; } + internal Glyph[] _glyphs; + + internal List _localSubrRawBufferList; + internal List _globalSubrRawBufferList; + + internal int _defaultWidthX; + internal int _nominalWidthX; + internal List _cidFontDict; + + public string Version { get; set; } //CFF SID + public string Notice { get; set; }//CFF SID + public string CopyRight { get; set; }//CFF SID + public string FullName { get; set; }//CFF SID + public string FamilyName { get; set; }//CFF SID + public string Weight { get; set; }//CFF SID + public double UnderlinePosition { get; set; } + public double UnderlineThickness { get; set; } + public double[] FontBBox { get; set; } +#if DEBUG + public Cff1Font() + { + } + +#endif + + internal IEnumerable GetGlyphNameIter() + { + int j = _glyphs.Length; +#if DEBUG + if (j > ushort.MaxValue) { throw new OpenFontNotSupportedException(); } +#endif + for (int i = 0; i < j; ++i) + { + Glyph cff1Glyph = _glyphs[i]; + yield return new GlyphNameMap((ushort)i, cff1Glyph._cff1GlyphData.Name); + } + + } + } + public class Cff1GlyphData + { + internal Cff1GlyphData() + { + } + + public string Name { get; internal set; } + public ushort SIDName { get; internal set; } + internal Type2Instruction[] GlyphInstructions { get; set; } + +#if DEBUG + public ushort dbugGlyphIndex { get; internal set; } + public override string ToString() + { + StringBuilder stbuilder = new StringBuilder(); + stbuilder.Append(dbugGlyphIndex); + if (Name != null) + { + stbuilder.Append(" "); + stbuilder.Append(Name); + } + return stbuilder.ToString(); + } +#endif + } + + class Cff1Parser + { + //from: Adobe's The Compact Font Format Specification, version1.0, Dec 2003 + + //Table 2 CFF Data Types + //Name Range Description + //Card8 0 – 255 1-byte unsigned number + //Card16 0 – 65535 2-byte unsigned number + //Offset varies 1, 2, 3, or 4 byte offset(specified by OffSize field) + //OffSize 1–4 1-byte unsigned number specifies the + // size of an Offset field or fields + //SID 0 – 64999 2-byte string identifier + //----------------- + + //Table 1 CFF Data Layout + //Entry Comments + //Header – + //Name INDEX – + //Top DICT INDEX – + //String INDEX – + //Global Subr INDEX – + //Encodings – + //Charsets – + //FDSelect CIDFonts only + //CharStrings INDEX per-font + //Font DICT INDEX per-font, CIDFonts only + //Private DICT per-font + //Local Subr INDEX per-font or per-Private DICT for CIDFonts + //Copyright and Trademark - + // Notices + //----------------- + + + //from Apache's PDF box/FontBox + //@author Villu Ruusmann + + BinaryReader _reader; + + Cff1FontSet _cff1FontSet; + Cff1Font _currentCff1Font; + + List _topDic; + + long _cffStartAt; + + int _charStringsOffset; + int _charsetOffset; + int _encodingOffset; + + public void ParseAfterHeader(long cffStartAt, BinaryReader reader) + { + _cffStartAt = cffStartAt; + _cff1FontSet = new Cff1FontSet(); + _cidFontInfo = new CIDFontInfo(); + _reader = reader; + + // + ReadNameIndex(); + ReadTopDICTIndex(); + ReadStringIndex(); + ResolveTopDictInfo(); + ReadGlobalSubrIndex(); + + //---------------------- + ReadFDSelect(); + ReadFDArray(); + + + ReadPrivateDict(); + + + ReadCharStringsIndex(); + ReadCharsets(); + ReadEncodings(); + + //... + } + + public Cff1FontSet ResultCff1FontSet => _cff1FontSet; + // + void ReadNameIndex() + { + //7. Name INDEX + //This contains the PostScript language names(FontName or + //CIDFontName) of all the fonts in the FontSet stored in an INDEX + //structure.The font names are sorted, thereby permitting a + //binary search to be performed when locating a specific font + //within a FontSet. The sort order is based on character codes + //treated as 8 - bit unsigned integers. A given font name precedes + // another font name having the first name as its prefix.There + // must be at least one entry in this INDEX, i.e.the FontSet must + // contain at least one font. + + //For compatibility with client software, such as PostScript + //interpreters and Acrobat®, font names should be no longer + //than 127 characters and should not contain any of the following + //ASCII characters: [, ], (, ), {, }, <, >, /, %, null(NUL), space, tab, + //carriage return, line feed, form feed.It is recommended that + //font names be restricted to the printable ASCII subset, codes 33 + //through 126.Adobe Type Manager® (ATM®) software imposes + //a further restriction on the font name length of 63 characters. + + //Note 3 + //For compatibility with earlier PostScript + //interpreters, see Technical Note + //#5088, “Font Naming Issues.” + + //A font may be deleted from a FontSet without removing its data + //by setting the first byte of its name in the Name INDEX to 0 + //(NUL).This kind of deletion offers a simple way to handle font + //upgrades without rebuilding entire fontsets.Binary search + //software must detect deletions and restart the search at the + //previous or next name in the INDEX to ensure that all + //appropriate names are matched. + + CffIndexOffset[] nameIndexElems = ReadIndexDataOffsets(); + if (nameIndexElems == null) return; + // + + int count = nameIndexElems.Length; + string[] fontNames = new string[count]; + for (int i = 0; i < count; ++i) + { + //read each FontName or CIDFontName + CffIndexOffset indexElem = nameIndexElems[i]; + //TODO: review here again, + //check if we need to set _reader.BaseStream.Position or not + fontNames[i] = Encoding.UTF8.GetString(_reader.ReadBytes(indexElem.len), 0, indexElem.len); + } + + // + _cff1FontSet._fontNames = fontNames; + + + //TODO: review here + //in this version + //count ==1 + if (count != 1) + { + throw new OpenFontNotSupportedException(); + } + _currentCff1Font = new Cff1Font(); + _currentCff1Font.FontName = fontNames[0]; + _cff1FontSet._fonts.Add(_currentCff1Font); + } + + void ReadTopDICTIndex() + { + //8. Top DICT INDEX + //This contains the top - level DICTs of all the fonts in the FontSet + //stored in an INDEX structure.Objects contained within this + //INDEX correspond to those in the Name INDEX in both order + //and number. Each object is a DICT structure that corresponds to + //the top-level dictionary of a PostScript font. + //A font is identified by an entry in the Name INDEX and its data + //is accessed via the corresponding Top DICT + CffIndexOffset[] offsets = ReadIndexDataOffsets(); + + //9. Top DICT Data + //The names of the Top DICT operators shown in + //Table 9 are, where possible, the same as the corresponding Type 1 dict key. + //Operators that have no corresponding Type1 dict key are noted + //in the table below along with a default value, if any. (Several + //operators have been derived from FontInfo dict keys but have + //been grouped together with the Top DICT operators for + //simplicity.The keys from the FontInfo dict are indicated in the + //Default, notes column of Table 9) + int count = offsets.Length; + if (count > 1) + { + //temp... + //TODO: review here again + throw new OpenFontNotSupportedException(); + } + for (int i = 0; i < count; ++i) + { + //read DICT data + + List dicData = ReadDICTData(offsets[i].len); + _topDic = dicData; + } + + + } + + string[] _uniqueStringTable; + struct CIDFontInfo + { + public string ROS_Register; + public string ROS_Ordering; + public string ROS_Supplement; + + public double CIDFontVersion; + public int CIDFountCount; + public int FDSelect; + public int FDArray; + + public int fdSelectFormat; + public FDRange3[] fdRanges; + + } + + CIDFontInfo _cidFontInfo; + + void ReadStringIndex() + { + //10 String INDEX + + + //All the strings, with the exception of the FontName and + //CIDFontName strings which appear in the Name INDEX, used by + //different fonts within the FontSet are collected together into an + //INDEX structure and are referenced by a 2 - byte unsigned + //number called a string identifier or SID. + + + //Only unique strings are stored in the table + //thereby removing duplication across fonts. + + //Further space saving is obtained by allocating commonly + //occurring strings to predefined SIDs. + //These strings, known as the standard strings, + //describe all the names used in the ISOAdobe and + //Expert character sets along with a few other strings + //common to Type 1 fonts. + + //A complete list of standard strings is given in Appendix A + + //The client program will contain an array of standard strings with + //nStdStrings elements. + //Thus, the standard strings take SIDs in the + //range 0 to(nStdStrings –1). + + //The first string in the String INDEX + //corresponds to the SID whose value is equal to nStdStrings, the + //first non - standard string, and so on. + + //When the client needs to + //determine the string that corresponds to a particular SID it + //performs the following: test if SID is in standard range then + //fetch from internal table, + //otherwise, fetch string from the String + //INDEX using a value of(SID – nStdStrings) as the index. + + + CffIndexOffset[] offsets = ReadIndexDataOffsets(); + if (offsets == null) return; + // + + _uniqueStringTable = new string[offsets.Length]; + + byte[] buff = new byte[512];//reusable + + for (int i = 0; i < offsets.Length; ++i) + { + int len = offsets[i].len; + //TODO: review here again, + //check if we need to set _reader.BaseStream.Position or not + //TODO: Is Charsets.ISO_8859_1 Encoding supported in .netcore + if (len < buff.Length) + { + int actualRead = _reader.Read(buff, 0, len); +#if DEBUG + if (actualRead != len) + { + throw new OpenFontNotSupportedException(); + } +#endif + _uniqueStringTable[i] = Encoding.UTF8.GetString(buff, 0, len); + } + else + { + _uniqueStringTable[i] = Encoding.UTF8.GetString(_reader.ReadBytes(len), 0, len); + } + + } + + _cff1FontSet._uniqueStringTable = _uniqueStringTable; + } + string GetSid(int sid) + { + if (sid <= Cff1FontSet.N_STD_STRINGS) + { + //use standard name + //TODO: review here + return Cff1FontSet.s_StdStrings[sid]; + } + else + { + if (sid - Cff1FontSet.N_STD_STRINGS - 1 < _uniqueStringTable.Length) + { + return _uniqueStringTable[sid - Cff1FontSet.N_STD_STRINGS - 1]; + } + else + { + //skip this, + //eg. found in CID font, + //we should provide this info later + + return null; + } + } + } + + void ResolveTopDictInfo() + { + + //translate top-dic*** + foreach (CffDataDicEntry entry in _topDic) + { + switch (entry._operator.Name) + { + default: + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("topdic:" + entry._operator.Name); +#endif + } + break; + case "XUID": break;//nothing + case "version": + _currentCff1Font.Version = GetSid((int)entry.operands[0]._realNumValue); + break; + case "Notice": + _currentCff1Font.Notice = GetSid((int)entry.operands[0]._realNumValue); + break; + case "Copyright": + _currentCff1Font.CopyRight = GetSid((int)entry.operands[0]._realNumValue); + break; + case "FullName": + _currentCff1Font.FullName = GetSid((int)entry.operands[0]._realNumValue); + break; + case "FamilyName": + _currentCff1Font.FamilyName = GetSid((int)entry.operands[0]._realNumValue); + break; + case "Weight": + _currentCff1Font.Weight = GetSid((int)entry.operands[0]._realNumValue); + break; + case "UnderlinePosition": + _currentCff1Font.UnderlinePosition = entry.operands[0]._realNumValue; + break; + case "UnderlineThickness": + _currentCff1Font.UnderlineThickness = entry.operands[0]._realNumValue; + break; + case "FontBBox": + _currentCff1Font.FontBBox = new double[] { + entry.operands[0]._realNumValue, + entry.operands[1]._realNumValue, + entry.operands[2]._realNumValue, + entry.operands[3]._realNumValue}; + break; + case "CharStrings": + _charStringsOffset = (int)entry.operands[0]._realNumValue; + break; + case "charset": + _charsetOffset = (int)entry.operands[0]._realNumValue; + break; + case "Encoding": + _encodingOffset = (int)entry.operands[0]._realNumValue; + break; + case "Private": + //private DICT size and offset + _privateDICTLen = (int)entry.operands[0]._realNumValue; + _privateDICTOffset = (int)entry.operands[1]._realNumValue; + break; + case "ROS": + //http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf + //A CFF CIDFont has the CIDFontName in the Name INDEX and a corresponding Top DICT. + //The Top DICT begins with ROS operator which specifies the Registry-Ordering - Supplement for the font. + //This will indicate to a CFF parser that special CID processing should be applied to this font. Specifically: + + //ROS operator combines the Registry, Ordering, and Supplement keys together. + + //see Adobe Cmap resource , https://github.com/adobe-type-tools/cmap-resources + + _cidFontInfo.ROS_Register = GetSid((int)entry.operands[0]._realNumValue); + _cidFontInfo.ROS_Ordering = GetSid((int)entry.operands[1]._realNumValue); + _cidFontInfo.ROS_Supplement = GetSid((int)entry.operands[2]._realNumValue); + + break; + case "CIDFontVersion": + _cidFontInfo.CIDFontVersion = entry.operands[0]._realNumValue; + break; + case "CIDCount": + _cidFontInfo.CIDFountCount = (int)entry.operands[0]._realNumValue; + break; + case "FDSelect": + _cidFontInfo.FDSelect = (int)entry.operands[0]._realNumValue; + break; + case "FDArray": + _cidFontInfo.FDArray = (int)entry.operands[0]._realNumValue; + break; + } + } + } + void ReadGlobalSubrIndex() + { + //16. Local / Global Subrs INDEXes + //Both Type 1 and Type 2 charstrings support the notion of + //subroutines or subrs. + + //A subr is typically a sequence of charstring + //bytes representing a sub - program that occurs in more than one + // place in a font’s charstring data. + + //This subr may be stored once + //but referenced many times from within one or more charstrings + //by the use of the call subr operator whose operand is the + //number of the subr to be called. + + //The subrs are local to a particular font and + //cannot be shared between fonts. + + //Type 2 charstrings also permit global subrs which function in the same + //way but are called by the call gsubr operator and may be shared + //across fonts. + + //Local subrs are stored in an INDEX structure which is located via + //the offset operand of the Subrs operator in the Private DICT. + //A font without local subrs has no Subrs operator in the Private DICT. + + //Global subrs are stored in an INDEX structure which follows the + //String INDEX. A FontSet without any global subrs is represented + //by an empty Global Subrs INDEX. + _currentCff1Font._globalSubrRawBufferList = ReadSubrBuffer(); + } + + void ReadLocalSubrs() + { + _currentCff1Font._localSubrRawBufferList = ReadSubrBuffer(); + } + + void ReadEncodings() + { + //Encoding data is located via the offset operand to the + //Encoding operator in the Top DICT. + + //Only one Encoding operator can be + //specified per font except for CIDFonts which specify no + //encoding. + + //A glyph’s encoding is specified by a 1 - byte code that + //permits values in the range 0 - 255. + + + //Each encoding is described by a format-type identifier byte + //followed by format-specific data.Two formats are currently + //defined as specified in Tables 11(Format 0) and 12(Format 1). + byte format = _reader.ReadByte(); + switch (format) + { + default: +#if DEBUG + System.Diagnostics.Debug.WriteLine("cff_parser_read_encodings:" + format); +#endif + break; + case 0: + ReadFormat0Encoding(); + break; + case 1: + ReadFormat1Encoding(); + break; + + } + //TODO: ... + } + void ReadCharsets() + { + //Charset data is located via the offset operand to the + //charset operator in the Top DICT. + + //Each charset is described by a format- + //type identifier byte followed by format-specific data. + //Three formats are currently defined as shown in Tables + //17, 18, and 20. + + _reader.BaseStream.Position = _cffStartAt + _charsetOffset; + //TODO: ... + byte format = _reader.ReadByte(); + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 0: + ReadCharsetsFormat0(); + break; + case 1: + ReadCharsetsFormat1(); + break; + case 2: + ReadCharsetsFormat2(); + break; + } + } + void ReadCharsetsFormat0() + { + //Table 17: Format 0 + //Type Name Description + //Card8 format =0 + //SID glyph[nGlyphs-1] Glyph name array + + //Each element of the glyph array represents the name of the + //corresponding glyph. This format should be used when the SIDs + //are in a fairly random order. The number of glyphs (nGlyphs) is + //the value of the count field in the + //CharStrings INDEX. (There is + //one less element in the glyph name array than nGlyphs because + //the .notdef glyph name is omitted.) + + Glyph[] cff1Glyphs = _currentCff1Font._glyphs; + int nGlyphs = cff1Glyphs.Length; + for (int i = 1; i < nGlyphs; ++i) + { + Cff1GlyphData d = cff1Glyphs[i]._cff1GlyphData; + d.Name = GetSid(d.SIDName = _reader.ReadUInt16()); + } + } + void ReadCharsetsFormat1() + { + //Table 18 Format 1 + //Type Name Description + //Card8 format =1 + //struct Range1[] Range1 array (see Table 19) + + //Table 19 Range1 Format (Charset) + //Type Name Description + //SID first First glyph in range + //Card8 nLeft Glyphs left in range(excluding first) + + + //Each Range1 describes a group of sequential SIDs. The number + //of ranges is not explicitly specified in the font. Instead, software + //utilizing this data simply processes ranges until all glyphs in the + //font are covered. This format is particularly suited to charsets + //that are well ordered + + // throw new OpenFontNotSupportedException(); + Glyph[] cff1Glyphs = _currentCff1Font._glyphs; + int nGlyphs = cff1Glyphs.Length; + for (int i = 1; i < nGlyphs;) + { + int sid = _reader.ReadUInt16();// First glyph in range + int count = _reader.ReadByte() + 1;//since it not include first elem + do + { + Cff1GlyphData d = cff1Glyphs[i]._cff1GlyphData; + d.Name = GetSid(d.SIDName = (ushort)sid); + + count--; + i++; + sid++; + } while (count > 0); + } + } + void ReadCharsetsFormat2() + { + + //note:eg, Adobe's source-code-pro font + + + //Table 20 Format 2 + //Type Name Description + //Card8 format 2 + //struct Range2[] Range2 array (see Table 21) + // + //----------------------------------------------- + //Table 21 Range2 Format + //Type Name Description + //SID first First glyph in range + //Card16 nLeft Glyphs left in range (excluding first) + //----------------------------------------------- + + //Format 2 differs from format 1 only in the size of the nLeft field in each range. + //This format is most suitable for fonts with a large well - ordered charset — for example, for Asian CIDFonts. + + Glyph[] cff1Glyphs = _currentCff1Font._glyphs; + int nGlyphs = cff1Glyphs.Length; + for (int i = 1; i < nGlyphs;) + { + int sid = _reader.ReadUInt16();// First glyph in range + int count = _reader.ReadUInt16() + 1;//since it not include first elem + do + { + Cff1GlyphData d = cff1Glyphs[i]._cff1GlyphData; + d.Name = GetSid(d.SIDName = (ushort)sid); + + count--; + i++; + sid++; + } while (count > 0); + } + } + void ReadFDSelect() + { + //http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf + //19. FDSelect + + //The FDSelect associates an FD(Font DICT) with a glyph by + //specifying an FD index for that glyph. The FD index is used to + //access one of the Font DICTs stored in the Font DICT INDEX. + + //FDSelect data is located via the offset operand to the + //FDSelect operator in the Top DICT.FDSelect data specifies a format - type + //identifier byte followed by format-specific data.Two formats + //are currently defined, as shown in Tables 27 and 28. + //TODO: ... + + + //FDSelect 12 37 number –, FDSelect offset + if (_cidFontInfo.FDSelect == 0) { return; } + + // + //Table 27 Format 0 + //------------ + //Type Name Description + //------------ + //Card8 format =0 + //Card8 fd[nGlyphs] FD selector array + + //Each element of the fd array(fds) represents the FD index of the corresponding glyph. + //This format should be used when the FD indexes are in a fairly random order. + //The number of glyphs(nGlyphs) is the value of the count field in the CharStrings INDEX. + //(This format is identical to charset format 0 except that the.notdef glyph is included in this case.) + + + //Table 28 Format 3 + //------------ + //Type Name Description + //------------ + //Card8 format =3 + //Card16 nRanges Number of ranges + //struct Range3[nRanges] Range3 array (see Table 29) + //Card16 sentinel Sentinel GID (see below) + //------------ + + + //Table 29 Range3 + //------------ + //Type Name Description + //------------ + //Card16 first First glyph index in range + //Card8 fd FD index for all glyphs in range + + //Each Range3 describes a group of sequential GIDs that have the same FD index. + //Each range includes GIDs from the ‘first’ GID up to, but not including, + //the ‘first’ GID of the next range element. + //Thus, elements of the Range3 array are ordered by increasing ‘first’ GIDs. + //The first range must have a ‘first’ GID of 0. + //A sentinel GID follows the last range element and serves to delimit the last range in the array. + //(The sentinel GID is set equal to the number of glyphs in the font. + //That is, its value is 1 greater than the last GID in the font.) + //This format is particularly suited to FD indexes that are well ordered(the usual case). + + _reader.BaseStream.Position = _cffStartAt + _cidFontInfo.FDSelect; + byte format = _reader.ReadByte(); + + switch (format) + { + default: + throw new OpenFontNotSupportedException(); + case 3: + { + ushort nRanges = _reader.ReadUInt16(); + FDRange3[] ranges = new FDRange3[nRanges + 1]; + + _cidFontInfo.fdSelectFormat = 3; + _cidFontInfo.fdRanges = ranges; + for (int i = 0; i < nRanges; ++i) + { + ranges[i] = new FDRange3(_reader.ReadUInt16(), _reader.ReadByte()); + } + + //end with //sentinel + ranges[nRanges] = new FDRange3(_reader.ReadUInt16(), 0);//sentinel +#if DEBUG + +#endif + + + } + break; + } + } + + readonly struct FDRange3 + { + public readonly ushort first; + public readonly byte fd; + public FDRange3(ushort first, byte fd) + { + this.first = first; + this.fd = fd; + } +#if DEBUG + public override string ToString() + { + return "first:" + first + ",fd:" + fd; + } +#endif + } + + + + void ReadFDArray() + { + if (_cidFontInfo.FDArray == 0) { return; } + _reader.BaseStream.Position = _cffStartAt + _cidFontInfo.FDArray; + + CffIndexOffset[] offsets = ReadIndexDataOffsets(); + + List fontDicts = new List(); + _currentCff1Font._cidFontDict = fontDicts; + + for (int i = 0; i < offsets.Length; ++i) + { + //read DICT data + List dic = ReadDICTData(offsets[i].len); + //translate + + int offset = 0; + int size = 0; + int name = 0; + + foreach (CffDataDicEntry entry in dic) + { + switch (entry._operator.Name) + { + default: throw new OpenFontNotSupportedException(); + case "FontName": + name = (int)entry.operands[0]._realNumValue; + break; + case "Private": //private dic + size = (int)entry.operands[0]._realNumValue; + offset = (int)entry.operands[1]._realNumValue; + break; + } + } + + FontDict fontdict = new FontDict(size, offset); + fontdict.FontName = name; + fontDicts.Add(fontdict); + } + //----------------- + + foreach (FontDict fdict in fontDicts) + { + _reader.BaseStream.Position = _cffStartAt + fdict.PrivateDicOffset; + + List dicData = ReadDICTData(fdict.PrivateDicSize); + + if (dicData.Count > 0) + { + //interpret the values of private dict + foreach (CffDataDicEntry dicEntry in dicData) + { + switch (dicEntry._operator.Name) + { + case "Subrs": + { + int localSubrsOffset = (int)dicEntry.operands[0]._realNumValue; + _reader.BaseStream.Position = _cffStartAt + fdict.PrivateDicOffset + localSubrsOffset; + fdict.LocalSubr = ReadSubrBuffer(); + } + break; + case "defaultWidthX": + + break; + case "nominalWidthX": + + break; + default: + { + +#if DEBUG + System.Diagnostics.Debug.WriteLine("cff_pri_dic:" + dicEntry._operator.Name); +#endif + + } + break; + } + } + } + } + } + + struct FDRangeProvider + { + //helper class + + FDRange3[] _ranges; + ushort _currentGlyphIndex; + ushort _endGlyphIndexLim; + byte _selectedFdArray; + FDRange3 _currentRange; + int _currentSelectedRangeIndex; + + public FDRangeProvider(FDRange3[] ranges) + { + _ranges = ranges; + _currentGlyphIndex = 0; + _currentSelectedRangeIndex = 0; + + if (ranges != null) + { + _currentRange = ranges[0]; + _endGlyphIndexLim = ranges[1].first; + } + else + { + //empty + _currentRange = new FDRange3(); + _endGlyphIndexLim = 0; + } + _selectedFdArray = 0; + } + public byte SelectedFDArray => _selectedFdArray; + public void SetCurrentGlyphIndex(ushort index) + { + //find proper range for selected index + if (index >= _currentRange.first && index < _endGlyphIndexLim) + { + //ok, in current range + _selectedFdArray = _currentRange.fd; + } + else + { + //move to next range + _currentSelectedRangeIndex++; + _currentRange = _ranges[_currentSelectedRangeIndex]; + + _endGlyphIndexLim = _ranges[_currentSelectedRangeIndex + 1].first; + if (index >= _currentRange.first && index < _endGlyphIndexLim) + { + _selectedFdArray = _currentRange.fd; + + } + else + { + throw new OpenFontNotSupportedException(); + } + + } + _currentGlyphIndex = index; + } + } + + void ReadCharStringsIndex() + { + //14. CharStrings INDEX + + //This contains the charstrings of all the glyphs in a font stored in + //an INDEX structure. + + //Charstring objects contained within this + //INDEX are accessed by GID. + + //The first charstring(GID 0) must be + //the.notdef glyph. + + //The number of glyphs available in a font may + //be determined from the count field in the INDEX. + + // + + //The format of the charstring data, and therefore the method of + //interpretation, is specified by the + //CharstringType operator in the Top DICT. + + //The CharstringType operator has a default value + //of 2 indicating the Type 2 charstring format which was designed + //in conjunction with CFF. + + //Type 1 charstrings are documented in + //the “Adobe Type 1 Font Format” published by Addison - Wesley. + + //Type 2 charstrings are described in Adobe Technical Note #5177: + //“Type 2 Charstring Format.” Other charstring types may also be + //supported by this method. + + _reader.BaseStream.Position = _cffStartAt + _charStringsOffset; + CffIndexOffset[] offsets = ReadIndexDataOffsets(); + + +#if DEBUG + if (offsets.Length > ushort.MaxValue) { throw new OpenFontNotSupportedException(); } +#endif + int glyphCount = offsets.Length; + //assume Type2 + //TODO: review here + + Glyph[] glyphs = new Glyph[glyphCount]; + _currentCff1Font._glyphs = glyphs; + Type2CharStringParser type2Parser = new Type2CharStringParser(); + type2Parser.SetCurrentCff1Font(_currentCff1Font); + +#if DEBUG + double total = 0; +#endif + + //cid font or not + + var fdRangeProvider = new FDRangeProvider(_cidFontInfo.fdRanges); + bool isCidFont = _cidFontInfo.fdRanges != null; + + for (int i = 0; i < glyphCount; ++i) + { + CffIndexOffset offset = offsets[i]; + byte[] buffer = _reader.ReadBytes(offset.len); +#if DEBUG + //check + byte lastByte = buffer[offset.len - 1]; + if (lastByte != (byte)Type2Operator1.endchar && + lastByte != (byte)Type2Operator1.callgsubr && + lastByte != (byte)Type2Operator1.callsubr) + { + //5177.Type2 + //Note 6 The charstring itself may end with a call(g)subr; the subroutine must + //then end with an endchar operator + //endchar + throw new Exception("invalid end byte?"); + } +#endif + //now we can parse the raw glyph instructions + + Cff1GlyphData glyphData = new Cff1GlyphData(); +#if DEBUG + type2Parser.dbugCurrentGlyphIndex = (ushort)i; +#endif + + if (isCidFont) + { + //select proper local private dict + fdRangeProvider.SetCurrentGlyphIndex((ushort)i); + type2Parser.SetCidFontDict(_currentCff1Font._cidFontDict[fdRangeProvider.SelectedFDArray]); + } + + Type2GlyphInstructionList instList = type2Parser.ParseType2CharString(buffer); + if (instList != null) + { + //use compact form or not + + if (_useCompactInstruction) + { + //this is our extension, + //if you don't want compact version + //just use original + + glyphData.GlyphInstructions = _instCompacter.Compact(instList.InnerInsts); + +#if DEBUG + total += glyphData.GlyphInstructions.Length / (float)instList.InnerInsts.Count; +#endif + + } + else + { + glyphData.GlyphInstructions = instList.InnerInsts.ToArray(); + + } + } + glyphs[i] = new Glyph(glyphData, (ushort)i); + } + +#if DEBUG + if (_useCompactInstruction) + { + double avg = total / glyphCount; + System.Diagnostics.Debug.WriteLine("cff instruction compact avg:" + avg + "%"); + } +#endif + + } + //--------------- + bool _useCompactInstruction = true; + Type2InstructionCompacter _instCompacter = new Type2InstructionCompacter(); + + + void ReadFormat0Encoding() + { + + //Table 11: Format 0 + //Type Name Description + //Card8 format = 0 + //Card8 nCodes Number of encoded glyphs + //Card8 code[nCodes] Code array + //------- + //Each element of the code array represents the encoding for the + //corresponding glyph.This format should be used when the + //codes are in a fairly random order + + //we have read format field( 1st field) .. + //so start with 2nd field + + int nCodes = _reader.ReadByte(); + byte[] codes = _reader.ReadBytes(nCodes); + + } + void ReadFormat1Encoding() + { + //Table 12 Format 1 + //Type Name Description + //Card8 format = 1 + //Card8 nRanges Number of code ranges + //struct Range1[nRanges] Range1 array(see Table 13) + //-------------- + int nRanges = _reader.ReadByte(); + + + + + //Table 13 Range1 Format(Encoding) + //Type Name Description + //Card8 first First code in range + //Card8 nLeft Codes left in range(excluding first) + //-------------- + //Each Range1 describes a group of sequential codes. For + //example, the codes 51 52 53 54 55 could be represented by the + //Range1: 51 4, and a perfectly ordered encoding of 256 codes can + //be described with the Range1: 0 255. + + //This format is particularly suited to encodings that are well ordered. + + + //A few fonts have multiply - encoded glyphs which are not + //supported directly by any of the above formats. This situation is + //indicated by setting the high - order bit in the format byte and + //supplementing the encoding, regardless of format type, as + //shown in Table 14. + + + //Table 14 Supplemental Encoding Data + //Type Name Description + //Card8 nSups Number of supplementary mappings + //struct Supplement[nSups] Supplementary encoding array(see Table 15 below) + + + //Table 15 Supplement Format + //Type Name Description + //Card8 code Encoding + //SID glyph Name + } + + + int _privateDICTOffset; + int _privateDICTLen; + void ReadPrivateDict() + { + //per-font + if (_privateDICTLen == 0) { return; } + // + _reader.BaseStream.Position = _cffStartAt + _privateDICTOffset; + List dicData = ReadDICTData(_privateDICTLen); + + if (dicData.Count > 0) + { + //interpret the values of private dict + foreach (CffDataDicEntry dicEntry in dicData) + { + switch (dicEntry._operator.Name) + { + case "Subrs": + { + int localSubrsOffset = (int)dicEntry.operands[0]._realNumValue; + _reader.BaseStream.Position = _cffStartAt + _privateDICTOffset + localSubrsOffset; + ReadLocalSubrs(); + } + break; + case "defaultWidthX": + _currentCff1Font._defaultWidthX = (int)dicEntry.operands[0]._realNumValue; + break; + case "nominalWidthX": + _currentCff1Font._nominalWidthX = (int)dicEntry.operands[0]._realNumValue; + break; + default: + { + +#if DEBUG + System.Diagnostics.Debug.WriteLine("cff_pri_dic:" + dicEntry._operator.Name); +#endif + + } + break; + } + } + } + + } + + List ReadSubrBuffer() + { + CffIndexOffset[] offsets = ReadIndexDataOffsets(); + if (offsets == null) return null; + // + int nsubrs = offsets.Length; + List rawBufferList = new List(); + + for (int i = 0; i < nsubrs; ++i) + { + CffIndexOffset offset = offsets[i]; + byte[] charStringBuffer = _reader.ReadBytes(offset.len); + rawBufferList.Add(charStringBuffer); + } + return rawBufferList; + } + + + + + + + List ReadDICTData(int len) + { + //4. DICT Data + + //Font dictionary data comprising key-value pairs is represented + //in a compact tokenized format that is similar to that used to + //represent Type 1 charstrings. + + //Dictionary keys are encoded as 1- or 2-byte operators and dictionary values are encoded as + //variable-size numeric operands that represent either integer or + //real values. + + //----------------------------- + //A DICT is simply a sequence of + //operand(s)/operator bytes concatenated together. + int endBefore = (int)(_reader.BaseStream.Position + len); + List dicData = new List(); + while (_reader.BaseStream.Position < endBefore) + { + CffDataDicEntry dicEntry = ReadEntry(); + dicData.Add(dicEntry); + } + return dicData; + } + + + + CffDataDicEntry ReadEntry() + { + //----------------------------- + //An operator is preceded by the operand(s) that + //specify its value. + //-------------------------------- + + + //----------------------------- + //Operators and operands may be distinguished by inspection of + //their first byte: + //0–21 specify operators and + //28, 29, 30, and 32–254 specify operands(numbers). + //Byte values 22–27, 31, and 255 are reserved. + + //An operator may be preceded by up to a maximum of 48 operands + + CffDataDicEntry dicEntry = new CffDataDicEntry(); + List operands = new List(); + + while (true) + { + byte b0 = _reader.ReadByte(); + + if (b0 >= 0 && b0 <= 21) + { + //operators + dicEntry._operator = ReadOperator(b0); + break; //**break after found operator + } + else if (b0 == 28 || b0 == 29) + { + int num = ReadIntegerNumber(b0); + operands.Add(new CffOperand(num, OperandKind.IntNumber)); + } + else if (b0 == 30) + { + double num = ReadRealNumber(); + operands.Add(new CffOperand(num, OperandKind.RealNumber)); + } + else if (b0 >= 32 && b0 <= 254) + { + int num = ReadIntegerNumber(b0); + operands.Add(new CffOperand(num, OperandKind.IntNumber)); + } + else + { + throw new OpenFontNotSupportedException("invalid DICT data b0 byte: " + b0); + } + } + + dicEntry.operands = operands.ToArray(); + return dicEntry; + } + + CFFOperator ReadOperator(byte b0) + { + //read operator key + byte b1 = 0; + if (b0 == 12) + { + //2 bytes + b1 = _reader.ReadByte(); + } + //get registered operator by its key + return CFFOperator.GetOperatorByKey(b0, b1); + } + + readonly StringBuilder _sbForReadRealNumber = new StringBuilder(); + double ReadRealNumber() + { + //from https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + // A real number operand is provided in addition to integer + //operands.This operand begins with a byte value of 30 followed + //by a variable-length sequence of bytes.Each byte is composed + //of two 4 - bit nibbles asdefined in Table 5. + + //The first nibble of a + //pair is stored in the most significant 4 bits of a byte and the + //second nibble of a pair is stored in the least significant 4 bits of a byte + + StringBuilder sb = _sbForReadRealNumber; + sb.Length = 0;//reset + + bool done = false; + bool exponentMissing = false; + while (!done) + { + int b = _reader.ReadByte(); + + int nb_0 = (b >> 4) & 0xf; + int nb_1 = (b) & 0xf; + + for (int i = 0; !done && i < 2; ++i) + { + int nibble = (i == 0) ? nb_0 : nb_1; + + switch (nibble) + { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + case 0x8: + case 0x9: + sb.Append(nibble); + exponentMissing = false; + break; + case 0xa: + sb.Append("."); + break; + case 0xb: + sb.Append("E"); + exponentMissing = true; + break; + case 0xc: + sb.Append("E-"); + exponentMissing = true; + break; + case 0xd: + break; + case 0xe: + sb.Append("-"); + break; + case 0xf: + done = true; + break; + default: + throw new Exception("IllegalArgumentException"); + } + } + } + if (exponentMissing) + { + // the exponent is missing, just append "0" to avoid an exception + // not sure if 0 is the correct value, but it seems to fit + // see PDFBOX-1522 + sb.Append("0"); + } + if (sb.Length == 0) + { + return 0d; + } + + + //TODO: use TryParse + + if (!double.TryParse(sb.ToString(), + System.Globalization.NumberStyles.Number | System.Globalization.NumberStyles.AllowExponent, + System.Globalization.CultureInfo.InvariantCulture, out double value)) + { + throw new OpenFontNotSupportedException(); + } + return value; + } + int ReadIntegerNumber(byte b0) + { + if (b0 == 28) + { + return _reader.ReadInt16(); + } + else if (b0 == 29) + { + return _reader.ReadInt32(); + } + else if (b0 >= 32 && b0 <= 246) + { + return b0 - 139; + } + else if (b0 >= 247 && b0 <= 250) + { + int b1 = _reader.ReadByte(); + return (b0 - 247) * 256 + b1 + 108; + } + else if (b0 >= 251 && b0 <= 254) + { + int b1 = _reader.ReadByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + else + { + throw new Exception(); + } + } + + + + + CffIndexOffset[] ReadIndexDataOffsets() + { + + //INDEX Data + //An INDEX is an array of variable-sized objects.It comprises a + //header, an offset array, and object data. + //The offset array specifies offsets within the object data. + //An object is retrieved by + //indexing the offset array and fetching the object at the + //specified offset. + //The object’s length can be determined by subtracting its offset + //from the next offset in the offset array. + //An additional offset is added at the end of the offset array so the + //length of the last object may be determined. + //The INDEX format is shown in Table 7 + + //Table 7 INDEX Format + //Type Name Description + //Card16 count Number of objects stored in INDEX + //OffSize offSize Offset array element size + //Offset offset[count + 1] Offset array(from byte preceding object data) + //Card8 data[] Object data + + //Offsets in the offset array are relative to the byte that precedes + //the object data. Therefore the first element of the offset array + //is always 1. (This ensures that every object has a corresponding + //offset which is always nonzero and permits the efficient + //implementation of dynamic object loading.) + + //An empty INDEX is represented by a count field with a 0 value + //and no additional fields.Thus, the total size of an empty INDEX + //is 2 bytes. + + //Note 2 + //An INDEX may be skipped by jumping to the offset specified by the last + //element of the offset array + + + ushort count = _reader.ReadUInt16(); + if (count == 0) + { + return null; + } + + int offSize = _reader.ReadByte(); // + int[] offsets = new int[count + 1]; + CffIndexOffset[] indexElems = new CffIndexOffset[count]; + for (int i = 0; i <= count; ++i) + { + offsets[i] = _reader.ReadOffset(offSize); + } + for (int i = 0; i < count; ++i) + { + indexElems[i] = new CffIndexOffset(offsets[i], offsets[i + 1] - offsets[i]); + } + return indexElems; + } + + + readonly struct CffIndexOffset + { + /// + /// start offset + /// + readonly int startOffset; + public readonly int len; + + public CffIndexOffset(int startOffset, int len) + { + this.startOffset = startOffset; + this.len = len; + } +#if DEBUG + public override string ToString() + { + return "offset:" + startOffset + ",len:" + len; + } +#endif + } + + } + + + static class CFFBinaryReaderExtension + { + + public static int ReadOffset(this BinaryReader reader, int offsetSize) + { + switch (offsetSize) + { + default: throw new OpenFontNotSupportedException(); + case 1: + return reader.ReadByte(); + case 2: + return (reader.ReadByte() << 8) | (reader.ReadByte() << 0); + case 3: + return (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | (reader.ReadByte() << 0); + case 4: + return (reader.ReadByte() << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | (reader.ReadByte() << 0); + } + } + } + + class CffDataDicEntry + { + public CffOperand[] operands; + public CFFOperator _operator; + + +#if DEBUG + public override string ToString() + { + + StringBuilder stbuilder = new StringBuilder(); + int j = operands.Length; + for (int i = 0; i < j; ++i) + { + if (i > 0) + { + stbuilder.Append(" "); + } + stbuilder.Append(operands[i].ToString()); + } + + stbuilder.Append(" "); + stbuilder.Append(_operator.ToString()); + return stbuilder.ToString(); + } +#endif + } + + + enum OperandKind + { + IntNumber, + RealNumber + } + + + readonly struct CffOperand + { + public readonly OperandKind _kind; + public readonly double _realNumValue; + public CffOperand(double number, OperandKind kind) + { + _kind = kind; + _realNumValue = number; + } +#if DEBUG + public override string ToString() + { + switch (_kind) + { + case OperandKind.IntNumber: + return ((int)_realNumValue).ToString(); + default: + return _realNumValue.ToString(); + } + } +#endif + + } + + + enum OperatorOperandKind + { + SID, + Boolean, + Number, + Array, + Delta, + + //compound + NumberNumber, + SID_SID_Number, + } + + class CFFOperator + { + + readonly byte _b0; + readonly byte _b1; + readonly OperatorOperandKind _operatorOperandKind; + + //b0 the first byte of a two byte value + //b1 the second byte of a two byte value + private CFFOperator(string name, byte b0, byte b1, OperatorOperandKind operatorOperandKind) + { + _b0 = b0; + _b1 = b1; + this.Name = name; + _operatorOperandKind = operatorOperandKind; + } + public string Name { get; } + + public static CFFOperator GetOperatorByKey(byte b0, byte b1) + { + s_registered_Operators.TryGetValue((b1 << 8) | b0, out CFFOperator found); + return found; + } + + + static Dictionary s_registered_Operators = new Dictionary(); + static void Register(byte b0, byte b1, string operatorName, OperatorOperandKind opopKind) + { + s_registered_Operators.Add((b1 << 8) | b0, new CFFOperator(operatorName, b0, b1, opopKind)); + } + static void Register(byte b0, string operatorName, OperatorOperandKind opopKind) + { + s_registered_Operators.Add(b0, new CFFOperator(operatorName, b0, 0, opopKind)); + } + static CFFOperator() + { + //Table 9: Top DICT Operator Entries + Register(0, "version", OperatorOperandKind.SID); + Register(1, "Notice", OperatorOperandKind.SID); + Register(12, 0, "Copyright", OperatorOperandKind.SID); + Register(2, "FullName", OperatorOperandKind.SID); + Register(3, "FamilyName", OperatorOperandKind.SID); + Register(4, "Weight", OperatorOperandKind.SID); + Register(12, 1, "isFixedPitch", OperatorOperandKind.Boolean); + Register(12, 2, "ItalicAngle", OperatorOperandKind.Number); + Register(12, 3, "UnderlinePosition", OperatorOperandKind.Number); + Register(12, 4, "UnderlineThickness", OperatorOperandKind.Number); + Register(12, 5, "PaintType", OperatorOperandKind.Number); + Register(12, 6, "CharstringType", OperatorOperandKind.Number); //default value 2 + Register(12, 7, "FontMatrix", OperatorOperandKind.Array); + Register(13, "UniqueID", OperatorOperandKind.Number); + Register(5, "FontBBox", OperatorOperandKind.Array); + Register(12, 8, "StrokeWidth", OperatorOperandKind.Number); + Register(14, "XUID", OperatorOperandKind.Array); + Register(15, "charset", OperatorOperandKind.Number); + Register(16, "Encoding", OperatorOperandKind.Number); + Register(17, "CharStrings", OperatorOperandKind.Number); + Register(18, "Private", OperatorOperandKind.NumberNumber); + Register(12, 20, "SyntheticBase", OperatorOperandKind.Number); + Register(12, 21, "PostScript", OperatorOperandKind.SID); + Register(12, 22, "BaseFontName", OperatorOperandKind.SID); + Register(12, 23, "BaseFontBlend", OperatorOperandKind.SID); + + //Table 10: CIDFont Operator Extensions + Register(12, 30, "ROS", OperatorOperandKind.SID_SID_Number); + Register(12, 31, "CIDFontVersion", OperatorOperandKind.Number); + Register(12, 32, "CIDFontRevision", OperatorOperandKind.Number); + Register(12, 33, "CIDFontType", OperatorOperandKind.Number); + Register(12, 34, "CIDCount", OperatorOperandKind.Number); + Register(12, 35, "UIDBase", OperatorOperandKind.Number); + Register(12, 36, "FDArray", OperatorOperandKind.Number); + Register(12, 37, "FDSelect", OperatorOperandKind.Number); + Register(12, 38, "FontName", OperatorOperandKind.SID); + + //Table 23: Private DICT Operators + Register(6, "BlueValues", OperatorOperandKind.Delta); + Register(7, "OtherBlues", OperatorOperandKind.Delta); + Register(8, "FamilyBlues", OperatorOperandKind.Delta); + Register(9, "FamilyOtherBlues", OperatorOperandKind.Delta); + Register(12, 9, "BlueScale", OperatorOperandKind.Number); + Register(12, 10, "BlueShift", OperatorOperandKind.Number); + Register(12, 11, "BlueFuzz", OperatorOperandKind.Number); + Register(10, "StdHW", OperatorOperandKind.Number); + Register(11, "StdVW", OperatorOperandKind.Number); + Register(12, 12, "StemSnapH", OperatorOperandKind.Delta); + Register(12, 13, "StemSnapV", OperatorOperandKind.Delta); + Register(12, 14, "ForceBold", OperatorOperandKind.Boolean); + + //reserved 12 15//https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + //reserved 12 16//https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + + Register(12, 17, "LanguageGroup", OperatorOperandKind.Number); //https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + Register(12, 18, "ExpansionFactor", OperatorOperandKind.Number); //https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + Register(12, 19, "initialRandomSeed", OperatorOperandKind.Number); //https://typekit.files.wordpress.com/2013/05/5176.cff.pdf + + Register(19, "Subrs", OperatorOperandKind.Number); + Register(20, "defaultWidthX", OperatorOperandKind.Number); + Register(21, "nominalWidthX", OperatorOperandKind.Number); + } + +#if DEBUG + public override string ToString() + { + return this.Name; + } +#endif + } + + + class Cff2Parser + { + + //https://docs.microsoft.com/en-us/typography/opentype/spec/cff2 + //Table 1: CFF2 Data Layout + //Entry Comments + //Header Fixed location + //Top DICT Fixed location + //Global Subr INDEX Fixed location + //VariationStore + //FDSelect Present only if there is more than one Font DICT in the Font DICT INDEX. + //Font DICT INDEX + //Array of Font DICT Included in Font DICT INDEX. + //Private DICT One per Font DICT. + public void ParseAfterHeader(BinaryReader reader) + { + + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFFTable.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFFTable.cs new file mode 100644 index 00000000..58d44e04 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CFFTable.cs @@ -0,0 +1,116 @@ +//Apache2, 2018, Apache/PDFBox Authors ( https://github.com/apache/pdfbox) +// +// +//Apache PDFBox +//Copyright 2014 The Apache Software Foundation + +//This product includes software developed at +//The Apache Software Foundation(http://www.apache.org/). + +//Based on source code originally developed in the PDFBox and +//FontBox projects. + +//Copyright (c) 2002-2007, www.pdfbox.org + +//Based on source code originally developed in the PaDaF project. +//Copyright (c) 2010 Atos Worldline SAS + +//Includes the Adobe Glyph List +//Copyright 1997, 1998, 2002, 2007, 2010 Adobe Systems Incorporated. + +//Includes the Zapf Dingbats Glyph List +//Copyright 2002, 2010 Adobe Systems Incorporated. + +//Includes OSXAdapter +//Copyright (C) 2003-2007 Apple, Inc., All Rights Reserved + +//---------------- +//ccf1 spec from http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf + +//------------------------------------------------------------------ +//Apache2, 2018-present, WinterDev + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Typography.OpenFont.CFF; + +namespace Typography.OpenFont.Tables +{ + + //from https://docs.microsoft.com/en-us/typography/opentype/spec/cff + //This table contains a Compact Font Format font representation (also known as a PostScript Type 1, or CIDFont) + //and is structured according to + //Adobe Technical Note #5176: “The Compact Font Format Specification,” (https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5176.CFF.pdf) + //and + //Adobe Technical Note #5177: “Type 2 Charstring Format.” (https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf) + + + //... + // ... glyph data is accessed through the CharStrings INDEX of the CFF table. + + //... + //The CFF Top DICT must specify a CharstringType value of 2. + //The numGlyphs field in the 'maxp' table must be the same as the number of entries in the CFF's CharStrings INDEX. + //The OpenType font glyph index is the same as the CFF glyph index for all glyphs in the font. + + //Note that, in an OpenType font collection file, a single 'CFF ' table can be shared across multiple fonts; + //names used by applications must be those provided in the 'name' table, not the Name INDEX entry. + //The CFF Top DICT must specify a CharstringType value of 2. + //The numGlyphs field in the 'maxp' table must be the same as the number of entries in the CFF’s CharStrings INDEX. + //The OpenType font glyph index is the same as the CFF glyph index for all glyphs in the font. + + class CFFTable : TableEntry + { + public const string _N = "CFF ";//4 chars, left 1 blank whitespace + public override string Name => _N; + // + + Cff1FontSet _cff1FontSet; + // + internal Cff1FontSet Cff1FontSet => _cff1FontSet; + protected override void ReadContentFrom(BinaryReader reader) + { + long startAt = reader.BaseStream.Position; + // + // + //Table 8 Header Format + //Type Name Description + //Card8 major Format major version(starting at 1) + //Card8 minor Format minor version(starting at 0) + //Card8 hdrSize Header size(bytes) + //OffSize offSize Absolute offset(0) size + byte[] header = reader.ReadBytes(4); + byte major = header[0]; + byte minor = header[1]; + byte hdrSize = header[2]; + byte offSize = header[3]; + ////--------- + //name index + + switch (major) + { + default: throw new OpenFontNotSupportedException(); + case 1: + { + Cff1Parser cff1 = new Cff1Parser(); + cff1.ParseAfterHeader(startAt, reader); + _cff1FontSet = cff1.ResultCff1FontSet; + } + break; + case 2: + { + Cff2Parser cff2 = new Cff2Parser(); + cff2.ParseAfterHeader(reader); + } + break; + } + } + + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CffEvaluationEngine.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CffEvaluationEngine.cs new file mode 100644 index 00000000..00b02160 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/CffEvaluationEngine.cs @@ -0,0 +1,1419 @@ +//Apache2, 2018, apache/pdfbox Authors ( https://github.com/apache/pdfbox) +//MIT, 2018-present, WinterDev + +using System; +using System.Collections.Generic; +using System.Diagnostics; + + +namespace Typography.OpenFont.CFF +{ + + public class CffEvaluationEngine + { + + float _scale = 1;//default + readonly Stack _evalStackPool = new Stack(); + + class PxScaleGlyphTx : IGlyphTranslator + { + readonly float _scale; + readonly IGlyphTranslator _tx; + + bool _is_contour_opened; + + public PxScaleGlyphTx(float scale, IGlyphTranslator tx) + { + _scale = scale; + _tx = tx; + } + + public void BeginRead(int contourCount) + { + _tx.BeginRead(contourCount); + } + + public void CloseContour() + { + _is_contour_opened = false; + _tx.CloseContour(); + } + + public void Curve3(float x1, float y1, float x2, float y2) + { + _is_contour_opened = true; + _tx.Curve3(x1 * _scale, y1 * _scale, x2 * _scale, y2 * _scale); + } + + public void Curve4(float x1, float y1, float x2, float y2, float x3, float y3) + { + _is_contour_opened = true; + _tx.Curve4(x1 * _scale, y1 * _scale, x2 * _scale, y2 * _scale, x3 * _scale, y3 * _scale); + } + + public void EndRead() + { + _tx.EndRead(); + } + + public void LineTo(float x1, float y1) + { + _is_contour_opened = true; + _tx.LineTo(x1 * _scale, y1 * _scale); + } + + public void MoveTo(float x0, float y0) + { + _tx.MoveTo(x0 * _scale, y0 * _scale); + } + // + + public bool IsContourOpened => _is_contour_opened; + } + + public CffEvaluationEngine() + { + + } + public void Run(IGlyphTranslator tx, Cff1GlyphData glyphData, float scale = 1) + { + Run(tx, glyphData.GlyphInstructions, scale); + } + internal void Run(IGlyphTranslator tx, Type2Instruction[] instructionList, float scale = 1) + { + + //all fields are set to new values*** + + _scale = scale; + + double currentX = 0, currentY = 0; + + + var scaleTx = new PxScaleGlyphTx(scale, tx); + // + scaleTx.BeginRead(0);//unknown contour count + // + Run(scaleTx, instructionList, ref currentX, ref currentY); + // + + // + //some cff end without closing the latest contour? + + if (scaleTx.IsContourOpened) + { + scaleTx.CloseContour(); + } + + scaleTx.EndRead(); + + } + void Run(IGlyphTranslator tx, Type2Instruction[] instructionList, ref double currentX, ref double currentY) + { + //recursive *** + + Type2EvaluationStack evalStack = GetFreeEvalStack(); //** +#if DEBUG + //evalStack.dbugGlyphIndex = instructionList.dbugGlyphIndex; +#endif + evalStack._currentX = currentX; + evalStack._currentY = currentY; + evalStack.GlyphTranslator = tx; + + for (int i = 0; i < instructionList.Length; ++i) + { + Type2Instruction inst = instructionList[i]; + //---------- + //this part is our extension to the original + int merge_flags = inst.Op >> 6;//upper 2 bits is our extension flags + switch (merge_flags) + { + case 0: //nothing + break; + case 1: + evalStack.Push(inst.Value); + break; + case 2: + evalStack.Push((short)(inst.Value >> 16)); + evalStack.Push((short)(inst.Value >> 0)); + break; + case 3: + evalStack.Push((sbyte)(inst.Value >> 24)); + evalStack.Push((sbyte)(inst.Value >> 16)); + evalStack.Push((sbyte)(inst.Value >> 8)); + evalStack.Push((sbyte)(inst.Value >> 0)); + break; + } + //---------- + switch ((OperatorName)((inst.Op & 0b111111)))//we use only 6 lower bits for op_name + { + default: throw new OpenFontNotSupportedException(); + case OperatorName.GlyphWidth: + //TODO: + break; + case OperatorName.LoadInt: + evalStack.Push(inst.Value); + break; + case OperatorName.LoadSbyte4: + //4 consecutive sbyte + evalStack.Push((sbyte)(inst.Value >> 24)); + evalStack.Push((sbyte)(inst.Value >> 16)); + evalStack.Push((sbyte)(inst.Value >> 8)); + evalStack.Push((sbyte)(inst.Value >> 0)); + break; + case OperatorName.LoadSbyte3: + evalStack.Push((sbyte)(inst.Value >> 24)); + evalStack.Push((sbyte)(inst.Value >> 16)); + evalStack.Push((sbyte)(inst.Value >> 8)); + break; + case OperatorName.LoadShort2: + evalStack.Push((short)(inst.Value >> 16)); + evalStack.Push((short)(inst.Value >> 0)); + break; + case OperatorName.LoadFloat: + evalStack.Push(inst.ReadValueAsFixed1616()); + break; + case OperatorName.endchar: + evalStack.EndChar(); + break; + case OperatorName.flex: evalStack.Flex(); break; + case OperatorName.hflex: evalStack.H_Flex(); break; + case OperatorName.hflex1: evalStack.H_Flex1(); break; + case OperatorName.flex1: evalStack.Flex1(); break; + //------------------------- + //4.4: Arithmetic Operators + case OperatorName.abs: evalStack.Op_Abs(); break; + case OperatorName.add: evalStack.Op_Add(); break; + case OperatorName.sub: evalStack.Op_Sub(); break; + case OperatorName.div: evalStack.Op_Div(); break; + case OperatorName.neg: evalStack.Op_Neg(); break; + case OperatorName.random: evalStack.Op_Random(); break; + case OperatorName.mul: evalStack.Op_Mul(); break; + case OperatorName.sqrt: evalStack.Op_Sqrt(); break; + case OperatorName.drop: evalStack.Op_Drop(); break; + case OperatorName.exch: evalStack.Op_Exch(); break; + case OperatorName.index: evalStack.Op_Index(); break; + case OperatorName.roll: evalStack.Op_Roll(); break; + case OperatorName.dup: evalStack.Op_Dup(); break; + + //------------------------- + //4.5: Storage Operators + case OperatorName.put: evalStack.Put(); break; + case OperatorName.get: evalStack.Get(); break; + //------------------------- + //4.6: Conditional + case OperatorName.and: evalStack.Op_And(); break; + case OperatorName.or: evalStack.Op_Or(); break; + case OperatorName.not: evalStack.Op_Not(); break; + case OperatorName.eq: evalStack.Op_Eq(); break; + case OperatorName.ifelse: evalStack.Op_IfElse(); break; + // + case OperatorName.rlineto: evalStack.R_LineTo(); break; + case OperatorName.hlineto: evalStack.H_LineTo(); break; + case OperatorName.vlineto: evalStack.V_LineTo(); break; + case OperatorName.rrcurveto: evalStack.RR_CurveTo(); break; + case OperatorName.hhcurveto: evalStack.HH_CurveTo(); break; + case OperatorName.hvcurveto: evalStack.HV_CurveTo(); break; + case OperatorName.rcurveline: evalStack.R_CurveLine(); break; + case OperatorName.rlinecurve: evalStack.R_LineCurve(); break; + case OperatorName.vhcurveto: evalStack.VH_CurveTo(); break; + case OperatorName.vvcurveto: evalStack.VV_CurveTo(); break; + //------------------------------------------------------------------- + case OperatorName.rmoveto: evalStack.R_MoveTo(); break; + case OperatorName.hmoveto: evalStack.H_MoveTo(); break; + case OperatorName.vmoveto: evalStack.V_MoveTo(); break; + //------------------------------------------------------------------- + //4.3 Hint Operators + case OperatorName.hstem: evalStack.H_Stem(); break; + case OperatorName.vstem: evalStack.V_Stem(); break; + case OperatorName.vstemhm: evalStack.V_StemHM(); break; + case OperatorName.hstemhm: evalStack.H_StemHM(); break; + //-------------------------- + case OperatorName.hintmask1: evalStack.HintMask1(inst.Value); break; + case OperatorName.hintmask2: evalStack.HintMask2(inst.Value); break; + case OperatorName.hintmask3: evalStack.HintMask3(inst.Value); break; + case OperatorName.hintmask4: evalStack.HintMask4(inst.Value); break; + case OperatorName.hintmask_bits: evalStack.HintMaskBits(inst.Value); break; + //------------------------------ + case OperatorName.cntrmask1: evalStack.CounterSpaceMask1(inst.Value); break; + case OperatorName.cntrmask2: evalStack.CounterSpaceMask2(inst.Value); break; + case OperatorName.cntrmask3: evalStack.CounterSpaceMask3(inst.Value); break; + case OperatorName.cntrmask4: evalStack.CounterSpaceMask4(inst.Value); break; + case OperatorName.cntrmask_bits: evalStack.CounterSpaceMaskBits(inst.Value); break; + + //------------------------- + //4.7: Subroutine Operators + case OperatorName._return: + { + //*** + //don't forget to return _evalStack's currentX, currentY to prev evl context + currentX = evalStack._currentX; + currentY = evalStack._currentY; + evalStack.Ret(); + } + break; + //should not occur!-> since we replace this in parsing step + case OperatorName.callgsubr: + case OperatorName.callsubr: + throw new OpenFontNotSupportedException(); + } + } + + + ReleaseEvalStack(evalStack);//**** + } + + Type2EvaluationStack GetFreeEvalStack() + { + if (_evalStackPool.Count > 0) + { + return _evalStackPool.Pop(); + } + else + { + return new Type2EvaluationStack(); + } + } + void ReleaseEvalStack(Type2EvaluationStack evalStack) + { + evalStack.Reset(); + _evalStackPool.Push(evalStack); + } + } + + class Type2EvaluationStack + { + + internal double _currentX; + internal double _currentY; + + double[] _argStack = new double[50]; + int _currentIndex = 0; //current stack index + + IGlyphTranslator _glyphTranslator; +#if DEBUG + + public int dbugGlyphIndex; +#endif + public Type2EvaluationStack() + { + } + + public void Reset() + { + _currentIndex = 0; + _currentX = _currentY = 0; + _glyphTranslator = null; + } + public IGlyphTranslator GlyphTranslator + { + get => _glyphTranslator; + set => _glyphTranslator = value; + } + public void Push(double value) + { + _argStack[_currentIndex] = value; + _currentIndex++; + } + public void Push(int value) + { + _argStack[_currentIndex] = value; + _currentIndex++; + } + //Many operators take their arguments from the bottom-most + //entries in the Type 2 argument stack; this behavior is indicated + //by the stack bottom symbol ‘| -’ appearing to the left of the first + //argument.Operators that clear the argument stack are + //indicated by the stack bottom symbol ‘| -’ in the result position + //of the operator definition + + //[NOTE4]: + //The first stack - clearing operator, which must be one of... + + //hstem, hstemhm, vstem, vstemhm, cntrmask, + //hintmask, hmoveto, vmoveto, rmoveto, or endchar, + + //... + //takes an additional argument — the width(as + //described earlier), which may be expressed as zero or one numeric + //argument + + //------------------------- + //4.1: Path Construction Operators + + /// + /// rmoveto + /// + public void R_MoveTo() + { + //|- dx1 dy1 rmoveto(21) |- + + //moves the current point to + //a position at the relative coordinates(dx1, dy1) + //see [NOTE4] +#if DEBUG + if (_currentIndex != 2) + { + throw new OpenFontNotSupportedException(); + } +#endif + + + _glyphTranslator.CloseContour(); + _glyphTranslator.MoveTo((float)(_currentX += _argStack[0]), (float)(_currentY += _argStack[1])); + + _currentIndex = 0; //clear stack + } + + /// + /// hmoveto + /// + public void H_MoveTo() + { + //|- dx1 hmoveto(22) |- + + //moves the current point + //dx1 units in the horizontal direction + //see [NOTE4] + + _glyphTranslator.MoveTo((float)(_currentX += _argStack[0]), (float)_currentY); + _currentIndex = 0; //clear stack + } + public void V_MoveTo() + { + //|- dy1 vmoveto (4) |- + //moves the current point + //dy1 units in the vertical direction. + //see [NOTE4] +#if DEBUG + if (_currentIndex > 1) + { + throw new OpenFontNotSupportedException(); + } +#endif + _glyphTranslator.MoveTo((float)_currentX, (float)(_currentY += _argStack[0])); + _currentIndex = 0; //clear stack + } + public void R_LineTo() + { + //|- {dxa dya}+ rlineto (5) |- + + //appends a line from the current point to + //a position at the relative coordinates dxa, dya. + + //Additional rlineto operations are + //performed for all subsequent argument pairs. + + //The number of + //lines is determined from the number of arguments on the stack +#if DEBUG + if ((_currentIndex % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + for (int i = 0; i < _currentIndex;) + { + _glyphTranslator.LineTo((float)(_currentX += _argStack[i]), (float)(_currentY += _argStack[i + 1])); + i += 2; + } + _currentIndex = 0; //clear stack + } + public void H_LineTo() + { + + //|- dx1 {dya dxb}* hlineto (6) |- + //|- {dxa dyb}+ hlineto (6) |- + + //appends a horizontal line of length + //dx1 to the current point. + + //With an odd number of arguments, subsequent argument pairs + //are interpreted as alternating values of + //dy and dx, for which additional lineto + //operators draw alternating vertical and + //horizontal lines. + + //With an even number of arguments, the + //arguments are interpreted as alternating horizontal and + //vertical lines. The number of lines is determined from the + //number of arguments on the stack. + + //first elem + int i = 0; + if ((_currentIndex % 2) != 0) + { + //|- dx1 {dya dxb}* hlineto (6) |- + //odd number + _glyphTranslator.LineTo((float)(_currentX += _argStack[i]), (float)_currentY); + i++; + for (; i < _currentIndex;) + { + _glyphTranslator.LineTo((float)(_currentX), (float)(_currentY += _argStack[i])); + _glyphTranslator.LineTo((float)(_currentX += _argStack[i + 1]), (float)(_currentY)); + i += 2; + } + } + else + { + //even number + //|- {dxa dyb}+ hlineto (6) |- + for (; i < _currentIndex;) + { + //line to + _glyphTranslator.LineTo((float)(_currentX += _argStack[i]), (float)(_currentY)); + _glyphTranslator.LineTo((float)(_currentX), (float)(_currentY += _argStack[i + 1])); + // + i += 2; + } + } + _currentIndex = 0; //clear stack + } + public void V_LineTo() + { + //|- dy1 {dxa dyb}* vlineto (7) |- + //|- {dya dxb}+ vlineto (7) |- + + //appends a vertical line of length + //dy1 to the current point. + + //With an odd number of arguments, subsequent argument pairs are + //interpreted as alternating values of dx and dy, for which additional + //lineto operators draw alternating horizontal and + //vertical lines. + + //With an even number of arguments, the + //arguments are interpreted as alternating vertical and + //horizontal lines. The number of lines is determined from the + //number of arguments on the stack. + //first elem + int i = 0; + if ((_currentIndex % 2) != 0) + { + //|- dy1 {dxa dyb}* vlineto (7) |- + //odd number + _glyphTranslator.LineTo((float)_currentX, (float)(_currentY += _argStack[i])); + i++; + + for (; i < _currentIndex;) + { + //line to + _glyphTranslator.LineTo((float)(_currentX += _argStack[i]), (float)(_currentY)); + _glyphTranslator.LineTo((float)(_currentX), (float)(_currentY += _argStack[i + 1])); + // + i += 2; + } + } + else + { + //even number + //|- {dya dxb}+ vlineto (7) |- + for (; i < _currentIndex;) + { + //line to + _glyphTranslator.LineTo((float)(_currentX), (float)(_currentY += _argStack[i])); + _glyphTranslator.LineTo((float)(_currentX += _argStack[i + 1]), (float)(_currentY)); + // + i += 2; + } + } + _currentIndex = 0; //clear stack + } + + public void RR_CurveTo() + { + + //|- {dxa dya dxb dyb dxc dyc}+ rrcurveto (8) |- + + //appends a Bézier curve, defined by dxa...dyc, to the current point. + + //For each subsequent set of six arguments, an additional + //curve is appended to the current point. + + //The number of curve segments is determined from + //the number of arguments on the number stack and + //is limited only by the size of the number stack + + + //All Bézier curve path segments are drawn using six arguments, + //dxa, dya, dxb, dyb, dxc, dyc; where dxa and dya are relative to + //the current point, and all subsequent arguments are relative to + //the previous point.A number of the curve operators take + //advantage of the situation where some tangent points are + //horizontal or vertical(and hence the value is zero), thus + //reducing the number of arguments needed. + + int i = 0; +#if DEBUG + if ((_currentIndex % 6) != 0) + { + throw new OpenFontNotSupportedException(); + } + +#endif + double curX = _currentX; + double curY = _currentY; + for (; i < _currentIndex;) + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY += _argStack[i + 1]), //dxa,dya + (float)(curX += _argStack[i + 2]), (float)(curY += _argStack[i + 3]), //dxb,dyb + (float)(curX += _argStack[i + 4]), (float)(curY += _argStack[i + 5]) //dxc,dyc + ); + // + i += 6; + } + _currentX = curX; + _currentY = curY; + _currentIndex = 0; //clear stack + } + public void HH_CurveTo() + { + + //|- dy1? {dxa dxb dyb dxc}+ hhcurveto (27) |- + + //appends one or more Bézier curves, as described by the + //dxa...dxc set of arguments, to the current point. + //For each curve, if there are 4 arguments, + //the curve starts and ends horizontal. + + + //The first curve need not start horizontal (the odd argument + //case). Note the argument order for the odd argument case + + int i = 0; + int count = _currentIndex; + double curX = _currentX; + double curY = _currentY; + + if ((count % 2) != 0) + { + //odd number + _glyphTranslator.Curve4( + (float)(curX += _argStack[1]), (float)(curY += _argStack[0]), //dxa,+dy1? + (float)(curX += _argStack[2]), (float)(curY += _argStack[3]), //dxb,dyb + (float)(curX += _argStack[4]), (float)(curY) //dxc,+0 + ); + i += 5; + count -= 5; + } + + //next + while (count > 0) + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY), //dxa,+0 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX += _argStack[i + 3]), (float)(curY) //dxc,+0 + ); + // + i += 4; + count -= 4; + } + _currentX = curX; + _currentY = curY; + _currentIndex = 0; //clear stack + } + public void HV_CurveTo() + { + //|- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto (31) |- + + //|- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto (31) |- + + //appends one or more Bézier curves to the current point. + + //The tangent for the first Bézier must be horizontal, and the second + //must be vertical (except as noted below). + + //If there is a multiple of four arguments, the curve starts + //horizontal and ends vertical.Note that the curves alternate + //between start horizontal, end vertical, and start vertical, and + //end horizontal.The last curve(the odd argument case) need not + //end horizontal/ vertical. + +#if DEBUG + +#endif + int i = 0; + int remainder = 0; + + switch (remainder = (_currentIndex % 8)) + { + default: throw new OpenFontNotSupportedException(); + case 0: + case 1: + { + //|- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto (31) |- + + double curX = _currentX; + double curY = _currentY; + + int count = _currentIndex; + while (count > 0) + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY), //+dxa,0 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX), (float)(curY += _argStack[i + 3]) //+0,dyc + ); + + + if (count == 9) + { + //last cycle + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 4]), //+0,dyd + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 7]), (float)(curY += _argStack[i + 8]) //dxf,+dyf + ); + // + count -= 9; + i += 9; + } + else + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 4]), //+0,dyd + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 7]), (float)(curY) //dxf,+0 + ); + // + count -= 8; + i += 8; + } + + } + _currentX = curX; + _currentY = curY; + } + break; + + case 4: + case 5: + { + + //|- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto (31) |- + + //If there is a multiple of four arguments, the curve starts + //horizontal and ends vertical. + //Note that the curves alternate between start horizontal, end vertical, and start vertical, and + //end horizontal.The last curve(the odd argument case) need not + //end horizontal/ vertical. + + double curX = _currentX; + double curY = _currentY; + + int count = _currentIndex; + + if (count == 5) + { + //last one + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY), //dx1 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dx2,dy2 + (float)(curX += _argStack[i + 4]), (float)(curY += _argStack[i + 3]) //dx3,dy3 + ); + + count -= 5; + i += 5; + } + else + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY), //dx1 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dx2,dy2 + (float)(curX), (float)(curY += _argStack[i + 3]) //dy3 + ); + + count -= 4; + i += 4; + } + + + while (count > 0) + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 0]), //0,dya + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX += _argStack[i + 3]), (float)(curY) //dxc, +0 + ); + + if (count == 9) + { + //last cycle + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 4]), (float)(curY), //dxd,0 + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 8]), (float)(curY += _argStack[i + 7]) //dxf,dyf + ); + i += 9; + count -= 9; + } + else + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 4]), (float)(curY), //dxd,0 + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX), (float)(curY += _argStack[i + 7]) //0,dyf + ); + // + i += 8; + count -= 8; + } + + } + + _currentX = curX; + _currentY = curY; + } + break; + } + + + _currentIndex = 0; //clear stack + } + public void R_CurveLine() + { + +#if DEBUG + +#endif + //|- { dxa dya dxb dyb dxc dyc} +dxd dyd rcurveline(24) |- + //is equivalent to one rrcurveto for each set of six arguments + //dxa...dyc, followed by exactly one rlineto using + //the dxd, dyd arguments. + + //The number of curves is determined from the count + //on the argument stack. + + double curX = _currentX; + double curY = _currentY; + + int i = 0; + int count = _currentIndex; + while (count > 0) + { + + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY += _argStack[i + 1]), //dxa,dya + (float)(curX += _argStack[i + 2]), (float)(curY += _argStack[i + 3]), //dxb,dyb + (float)(curX += _argStack[i + 4]), (float)(curY += _argStack[i + 5]) //dxc,dyc + ); + // + i += 6; + count -= 6; + + if (count == 2) + { + //last one + _glyphTranslator.LineTo((float)(curX += _argStack[i]), (float)(curY += _argStack[i + 1])); + break;//exit while + } + } + _currentX = curX; + _currentY = curY; + _currentIndex = 0; //clear stack + + } + public void R_LineCurve() + { +#if DEBUG + +#endif + //|- { dxa dya} +dxb dyb dxc dyc dxd dyd rlinecurve(25) |- + + //is equivalent to one rlineto for each pair of arguments beyond + //the six arguments dxb...dyd needed for the one + //rrcurveto command.The number of lines is determined from the count of + //items on the argument stack. + + double curX = _currentX; + double curY = _currentY; + + int i = 0; + int count = _currentIndex; + while (count > 0) + { + _glyphTranslator.LineTo( + (float)(curX += _argStack[i + 0]), (float)(curY += _argStack[i + 1])); + // + i += 2; + count -= 2; + + if (count == 6) + { + //last one + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY += _argStack[i + 1]), //dxa,dya + (float)(curX += _argStack[i + 2]), (float)(curY += _argStack[i + 3]), //dxb,dyb + (float)(curX += _argStack[i + 4]), (float)(curY += _argStack[i + 5]) //dxc,dyc + ); + break;//exit while + } + } + _currentX = curX; + _currentY = curY; + _currentIndex = 0; //clear stack + + } + public void VH_CurveTo() + { + +#if DEBUG + + +#endif + + //|- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30) |- + + + //|- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto (30) |- + + //appends one or more Bézier curves to the current point, where + //the first tangent is vertical and the second tangent is horizontal. + + //This command is the complement of + //hvcurveto; + + //see the description of hvcurveto for more information. + int i = 0; + int remainder = 0; + + switch (remainder = (_currentIndex % 8)) + { + default: throw new OpenFontNotSupportedException(); + case 0: + case 1: + { + //|- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto (30) |- + double curX = _currentX; + double curY = _currentY; + int ncount = _currentIndex; + while (ncount > 0) + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 0]), //+0,dya + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX += _argStack[i + 3]), (float)(curY) //dxc,+0 + ); + + if (ncount == 9) + { + //last cycle + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 4]), (float)(curY), //dxd,+0 + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 8]), (float)(curY += _argStack[i + 7]) //+dxf,dyf + ); + // + i += 9; + ncount -= 9; + } + else + { + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 4]), (float)(curY), //dxd,+0 + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX), (float)(curY += _argStack[i + 7]) //+0,dyf + ); + // + i += 8; + ncount -= 8; + } + } + _currentX = curX; + _currentY = curY; + } + break; + + case 4: + case 5: + { + + //|- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30) |- + double curX = _currentX; + double curY = _currentY; + + int ncount = _currentIndex; + if (ncount == 5) + { + //only 1 + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 0]), //dy1 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dx2,dy2 + (float)(curX += _argStack[i + 3]), (float)(curY += _argStack[i + 4]) //dx3,dyf + ); + i += 5; + ncount -= 5; + } + else + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 0]), //dy1 + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dx2,dy2 + (float)(curX += _argStack[i + 3]), (float)(curY) //dx3 + ); + i += 4; + ncount -= 4; + } + + while (ncount > 0) + { + //line to + + _glyphTranslator.Curve4( + (float)(curX += _argStack[i + 0]), (float)(curY), //dxa, + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX), (float)(curY += _argStack[i + 3]) //+0, dyc + ); + if (ncount == 9) + { + //last cycle + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 4]), //+0,dyd + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 7]), (float)(curY += _argStack[i + 8]) //dxf,dyf + ); + i += 9; + ncount -= 9; + } + else + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 4]), //+0,dyd + (float)(curX += _argStack[i + 5]), (float)(curY += _argStack[i + 6]), //dxe,dye + (float)(curX += _argStack[i + 7]), (float)(curY) //dxf,0 + ); + i += 8; + ncount -= 8; + } + } + _currentX = curX; + _currentY = curY; + } + break; + } + + _currentIndex = 0; //clear stack + + + } + public void VV_CurveTo() + { + // |- dx1? {dya dxb dyb dyc}+ vvcurveto (26) |- + //appends one or more curves to the current point. + //If the argument count is a multiple of four, the curve starts and ends vertical. + //If the argument count is odd, the first curve does not begin with a vertical tangent. + + int i = 0; + int count = _currentIndex; + + double curX = _currentX; + double curY = _currentY; + + if ((count % 2) != 0) + { + //odd number + _glyphTranslator.Curve4( + (float)(curX += _argStack[0]), (float)(curY += _argStack[1]), //dx1?,+dya + (float)(curX += _argStack[2]), (float)(curY += _argStack[3]), //dxb,dyb + (float)(curX), (float)(curY += _argStack[4]) //+0,+dyc + ); + i += 5; + count -= 5; + } + + while (count > 0) + { + _glyphTranslator.Curve4( + (float)(curX), (float)(curY += _argStack[i + 0]), //+0,dya + (float)(curX += _argStack[i + 1]), (float)(curY += _argStack[i + 2]), //dxb,dyb + (float)(curX), (float)(curY += _argStack[i + 3]) //+0,dyc + ); + // + i += 4; + count -= 4; + } + _currentX = curX; + _currentY = curY; + _currentIndex = 0; //clear stack + } + public void EndChar() + { + _currentIndex = 0; + } + public void Flex() + { + //|- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + //causes two Bézier curves, as described by the arguments(as + //shown in Figure 2 below), to be rendered as a straight line when + //the flex depth is less than fd / 100 device pixels, and as curved lines + // when the flex depth is greater than or equal to fd/ 100 device pixels + + + _currentIndex = 0; //clear stack + } + public void H_Flex() + { + //|- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + //causes the two curves described by the arguments + //dx1...dx6 to be rendered as a straight line when + //the flex depth is less than 0.5(that is, fd is 50) device pixels, + //and as curved lines when the flex depth is greater than or equal to 0.5 device pixels. + + //hflex is used when the following are all true: + //a) the starting and ending points, first and last control points + //have the same y value. + //b) the joining point and the neighbor control points have + //the same y value. + //c) the flex depth is 50. + + _currentIndex = 0; //clear stack + } + public void H_Flex1() + { + //|- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + + //causes the two curves described by the arguments to be + //rendered as a straight line when the flex depth is less than 0.5 + //device pixels, and as curved lines when the flex depth is greater + //than or equal to 0.5 device pixels. + + //hflex1 is used if the conditions for hflex + //are not met but all of the following are true: + + //a) the starting and ending points have the same y value, + //b) the joining point and the neighbor control points have + //the same y value. + //c) the flex depth is 50. + _currentIndex = 0; //clear stack + } + public void Flex1() + { + //|- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) | + + //causes the two curves described by the arguments to be + //rendered as a straight line when the flex depth is less than 0.5 + //device pixels, and as curved lines when the flex depth is greater + //than or equal to 0.5 device pixels. + + //The d6 argument will be either a dx or dy value, depending on + //the curve(see Figure 3). To determine the correct value, + //compute the distance from the starting point(x, y), the first + //point of the first curve, to the last flex control point(dx5, dy5) + //by summing all the arguments except d6; call this(dx, dy).If + //abs(dx) > abs(dy), then the last point’s x-value is given by d6, and + //its y - value is equal to y. + // Otherwise, the last point’s x-value is equal to x and its y-value is given by d6. + + + _currentIndex = 0; //clear stack + } + + + //------------------------------------------------------------------- + //4.3 Hint Operators + public void H_Stem() + { + //|- y dy {dya dyb}* hstem (1) |- + + +#if DEBUG + if ((_currentIndex % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + //hintCount += _currentIndex / 2; + _currentIndex = 0; //clear stack + } + public void V_Stem() + { + //|- x dx {dxa dxb}* vstem (3) |- +#if DEBUG + if ((_currentIndex % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + //hintCount += _currentIndex / 2; + _currentIndex = 0; //clear stack + } + public void V_StemHM() + { + + //|- x dx {dxa dxb}* vstemhm (23) |- +#if DEBUG + if ((_currentIndex % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + //hintCount += _currentIndex / 2; + _currentIndex = 0; //clear stack + } + public void H_StemHM() + { + //|- y dy {dya dyb}* hstemhm (18) |- +#if DEBUG + if ((_currentIndex % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + //hintCount += _currentIndex / 2; + //has the same meaning as + //hstem (1), + //except that it must be used + //in place of hstem if the charstring contains one or more + //hintmask operators. + _currentIndex = 0; //clear stack + } + + //---------------------------------------- + //hintmask | -hintmask(19 + mask) | - + //The mask data bytes are defined as follows: + //• The number of data bytes is exactly the number needed, one + //bit per hint, to reference the number of stem hints declared + //at the beginning of the charstring program. + //• Each bit of the mask, starting with the most-significant bit of + //the first byte, represents the corresponding hint zone in the + //order in which the hints were declared at the beginning of + //the charstring. + //• For each bit in the mask, a value of ‘1’ specifies that the + //corresponding hint shall be active. A bit value of ‘0’ specifies + //that the hint shall be inactive. + //• Unused bits in the mask, if any, must be zero. + + public void HintMask1(int hintMaskValue) + { + //specifies which hints are active and which are not active. If any + //hints overlap, hintmask must be used to establish a nonoverlapping + //subset of hints. + //hintmask may occur any number of + //times in a charstring. Path operators occurring after a hintmask + //are influenced by the new hint set, but the current point is not + //moved. If stem hint zones overlap and are not properly + //managed by use of the hintmask operator, the results are + //undefined. + + //|- hintmask (19 + mask) |- + _currentIndex = 0; //clear stack + } + public void HintMask2(int hintMaskValue) + { + _currentIndex = 0; //clear stack + } + public void HintMask3(int hintMaskValue) + { + _currentIndex = 0; //clear stack + } + public void HintMask4(int hintMaskValue) + { + _currentIndex = 0; //clear stack + } + public void HintMaskBits(int bitCount) + { + + //calculate bytes need by + //bytes need = (bitCount+7)/8 +#if DEBUG + if (_currentIndex != (bitCount + 7) / 8) + { + + } +#endif + _currentIndex = 0; //clear stack + } + //---------------------------------------- + //|- cntrmask(20 + mask) |- + + //specifies the counter spaces to be controlled, and their relative + //priority.The mask bits in the bytes, following the operator, + //reference the stem hint declarations; the most significant bit of + //the first byte refers to the first stem hint declared, through to + //the last hint declaration.The counters to be controlled are + //those that are delimited by the referenced stem hints.Bits set to + //1 in the first cntrmask command have top priority; subsequent + //cntrmask commands specify lower priority counters(see Figure + //1 and the accompanying example). + public void CounterSpaceMask1(int cntMaskValue) + { + _currentIndex = 0;//clear stack + } + public void CounterSpaceMask2(int cntMaskValue) + { + _currentIndex = 0;//clear stack + } + public void CounterSpaceMask3(int cntMaskValue) + { + _currentIndex = 0;//clear stack + } + public void CounterSpaceMask4(int cntMaskValue) + { + _currentIndex = 0;//clear stack + } + public void CounterSpaceMaskBits(int bitCount) + { + //calculate bytes need by + //bytes need = (bitCount+7)/8 +#if DEBUG + if (_currentIndex != (bitCount + 7) / 8) + { + + } +#endif + + _currentIndex = 0;//clear stack + } + //---------------------------------------- + + + + //4.4: Arithmetic Operators + + //case Type2Operator2.abs: + // case Type2Operator2.add: + // case Type2Operator2.sub: + // case Type2Operator2.div: + // case Type2Operator2.neg: + // case Type2Operator2.random: + // case Type2Operator2.mul: + // case Type2Operator2.sqrt: + // case Type2Operator2.drop: + // case Type2Operator2.exch: + // case Type2Operator2.index: + // case Type2Operator2.roll: + // case Type2Operator2.dup: + + public void Op_Abs() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Abs)); + } + public void Op_Add() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Add)); + } + public void Op_Sub() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Sub)); + } + public void Op_Div() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Div)); + } + public void Op_Neg() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Neg)); + } + public void Op_Random() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Random)); + } + public void Op_Mul() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Mul)); + } + public void Op_Sqrt() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Sqrt)); + } + public void Op_Drop() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Drop)); + } + public void Op_Exch() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Exch)); + } + public void Op_Index() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Index)); + } + public void Op_Roll() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Roll)); + } + public void Op_Dup() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Dup)); + } + + + //------------------------- + //4.5: Storage Operators + + //The storage operators utilize a transient array and provide + //facilities for storing and retrieving transient array data. + + //The transient array provides non-persistent storage for + //intermediate values. + //There is no provision to initialize this array, + //except explicitly using the put operator, + //and values stored in the + //array do not persist beyond the scope of rendering an individual + //character. + + public void Put() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Put)); + } + public void Get() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Get)); + } + + //------------------------- + //4.6: Conditional + public void Op_And() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_And)); + } + public void Op_Or() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Or)); + } + public void Op_Not() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Not)); + } + public void Op_Eq() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_Eq)); + } + public void Op_IfElse() + { + Debug.WriteLine("NOT_IMPLEMENT:" + nameof(Op_IfElse)); + } + public double Pop() + { +#if DEBUG + if (_currentIndex < 1) + { + + } +#endif + return (double)_argStack[--_currentIndex];//*** use prefix + } + + public void Ret() + { +#if DEBUG + if (_currentIndex > 0) + { + + } +#endif + _currentIndex = 0; + } +#if DEBUG + public void dbugClearEvalStack() + { + _currentIndex = 0; + } +#endif + } + + + + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2CharStringParser.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2CharStringParser.cs new file mode 100644 index 00000000..00aa179d --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2CharStringParser.cs @@ -0,0 +1,1301 @@ +//Apache2, 2018, Villu Ruusmann , Apache/PdfBox Authors ( https://github.com/apache/pdfbox) +//Apache2, 2018-present, WinterDev + +//ref http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Typography.OpenFont.CFF +{ + + + //The Type 2 Charstring Format + //... + //must be used in a CFF (Compact Font Format) or OpenType font + //file to create a complete font program + + readonly struct Type2Instruction + { + public readonly int Value; + public readonly byte Op; + public Type2Instruction(OperatorName op, int value) + { + this.Op = (byte)op; + this.Value = value; +#if DEBUG + _dbug_OnlyOp = false; +#endif + } + public Type2Instruction(byte op, int value) + { + this.Op = op; + this.Value = value; +#if DEBUG + _dbug_OnlyOp = false; +#endif + } + public Type2Instruction(OperatorName op) + { + this.Op = (byte)op; + this.Value = 0; +#if DEBUG + _dbug_OnlyOp = true; +#endif + } + + + public float ReadValueAsFixed1616() + { + byte b0 = (byte)((0xff) & Value >> 24); + byte b1 = (byte)((0xff) & Value >> 16); + byte b2 = (byte)((0xff) & Value >> 8); + byte b3 = (byte)((0xff) & Value >> 0); + + + ///This number is interpreted as a Fixed; that is, a signed number with 16 bits of fraction + float int_part = (short)((b0 << 8) | b1); + float fraction_part = (short)((b2 << 8) | b3) / (float)(1 << 16); + return int_part + fraction_part; + } + + internal bool IsLoadInt => (OperatorName)Op == OperatorName.LoadInt; + +#if DEBUG + readonly bool _dbug_OnlyOp; + + [System.ThreadStatic] + static System.Text.StringBuilder s_dbugSb; + + public override string ToString() + { + + int merge_flags = Op >> 6; //upper most 2 bits we use as our extension + //so operator name is lower 6 bits + + int only_operator = Op & 0b111111; + OperatorName op_name = (OperatorName)only_operator; + + if (_dbug_OnlyOp) + { + return op_name.ToString(); + } + else + { + if (s_dbugSb == null) + { + s_dbugSb = new System.Text.StringBuilder(); + } + s_dbugSb.Length = 0;//reset + + bool has_ExtenedForm = true; + + + //this is my extension + switch (merge_flags) + { +#if DEBUG + default: throw new OpenFontNotSupportedException(); +#endif + case 0: + //nothing + has_ExtenedForm = false; + break; + case 1: + //contains merge data for LoadInt + s_dbugSb.Append(Value.ToString() + " "); + break; + case 2: + //contains merge data for LoadShort2 + s_dbugSb.Append((short)(Value >> 16) + " " + (short)(Value >> 0) + " "); + break; + case 3: + //contains merge data for LoadSbyte4 + s_dbugSb.Append((sbyte)(Value >> 24) + " " + (sbyte)(Value >> 16) + " " + (sbyte)(Value >> 8) + " " + (sbyte)(Value) + " "); + break; + } + + switch (op_name) + { + case OperatorName.LoadInt: + s_dbugSb.Append(Value); + break; + case OperatorName.LoadFloat: + s_dbugSb.Append(ReadValueAsFixed1616().ToString()); + break; + //----------- + case OperatorName.LoadShort2: + s_dbugSb.Append((short)(Value >> 16) + " " + (short)(Value >> 0)); + break; + case OperatorName.LoadSbyte4: + s_dbugSb.Append((sbyte)(Value >> 24) + " " + (sbyte)(Value >> 16) + " " + (sbyte)(Value >> 8) + " " + (sbyte)(Value)); + break; + case OperatorName.LoadSbyte3: + s_dbugSb.Append((sbyte)(Value >> 24) + " " + (sbyte)(Value >> 16) + " " + (sbyte)(Value >> 8)); + break; + //----------- + case OperatorName.hintmask1: + case OperatorName.hintmask2: + case OperatorName.hintmask3: + case OperatorName.hintmask4: + case OperatorName.hintmask_bits: + s_dbugSb.Append((op_name).ToString() + " " + Convert.ToString(Value, 2)); + break; + default: + if (has_ExtenedForm) + { + s_dbugSb.Append((op_name).ToString()); + } + else + { + s_dbugSb.Append((op_name).ToString() + " " + Value.ToString()); + } + + break; + } + + return s_dbugSb.ToString(); + + } + + } +#endif + } + + + class OriginalType2OperatorAttribute : Attribute + { + public OriginalType2OperatorAttribute(Type2Operator1 type2Operator1) + { + } + public OriginalType2OperatorAttribute(Type2Operator2 type2Operator1) + { + } + } + enum Type2Operator1 : byte + { + //Appendix A Type 2 Charstring Command Codes + _Reserved0_ = 0, + hstem, //1 + _Reserved2_,//2 + vstem, //3 + vmoveto,//4 + rlineto, //5 + hlineto, //6 + vlineto,//7, + rrcurveto,//8 + _Reserved9_, //9 + callsubr, //10 + //--------------------- + _return, //11 + escape,//12 + _Reserved13_, + endchar,//14 + _Reserved15_, + _Reserved16_, + _Reserved17_, + hstemhm,//18 + hintmask,//19 + cntrmask,//20 + //--------------------- + rmoveto,//21 + hmoveto,//22 + vstemhm,//23 + rcurveline, //24 + rlinecurve,//25 + vvcurveto,//26 + hhcurveto, //27 + shortint, //28 + callgsubr, //29 + vhcurveto, //30 + //----------------------- + hvcurveto, //31 + } + enum Type2Operator2 : byte + { + //Two-byte Type 2 Operators + _Reserved0_ = 0, + _Reserved1_, + _Reserved2_, + and, //3 + or, //4 + not, //5 + _Reserved6_, + _Reserved7_, + _Reserved8_, + // + abs,//9 + add,//10 + //------------------ + sub,//11 + div,//12 + _Reserved13_, + neg,//14 + eq, //15 + _Reserved16_, + _Reserved17_, + drop,//18 + _Reserved19_, + put,//20 + //------------------ + get, //21 + ifelse,//22 + random,//23 + mul, //24, + _Reserved25_, + sqrt,//26 + dup,//27 + exch,//28 , exchanges the top two elements on the argument stack + index,//29 + roll,//30 + //-------------- + _Reserved31_, + _Reserved32_, + _Reserved33_, + //-------------- + hflex,//34 + flex, //35 + hflex1,//36 + flex1//37 + } + + /// + /// Merged ccf operators,(op1 and op2, note on attribute of each field) + /// + enum OperatorName : byte + { + Unknown, + // + LoadInt, + LoadFloat, + GlyphWidth, + + LoadSbyte4, //my extension, 4 sbyte in an int32 + LoadSbyte3, //my extension, 3 sbytes in an int32 + LoadShort2, //my extension, 2 short in an int32 + + //--------------------- + //type2Operator1 + //--------------------- + [OriginalType2Operator(Type2Operator1.hstem)] hstem, + [OriginalType2Operator(Type2Operator1.vstem)] vstem, + [OriginalType2Operator(Type2Operator1.vmoveto)] vmoveto, + [OriginalType2Operator(Type2Operator1.rlineto)] rlineto, + [OriginalType2Operator(Type2Operator1.hlineto)] hlineto, + [OriginalType2Operator(Type2Operator1.vlineto)] vlineto, + [OriginalType2Operator(Type2Operator1.rrcurveto)] rrcurveto, + [OriginalType2Operator(Type2Operator1.callsubr)] callsubr, + //--------------------- + [OriginalType2Operator(Type2Operator1._return)] _return, + //[OriginalType2Operator(Type2Operator1.escape)] escape, //not used! + [OriginalType2Operator(Type2Operator1.endchar)] endchar, + [OriginalType2Operator(Type2Operator1.hstemhm)] hstemhm, + + //--------- + [OriginalType2Operator(Type2Operator1.hintmask)] hintmask1, //my hint-mask extension, contains 1 byte hint + [OriginalType2Operator(Type2Operator1.hintmask)] hintmask2, //my hint-mask extension, contains 2 bytes hint + [OriginalType2Operator(Type2Operator1.hintmask)] hintmask3, //my hint-mask extension, contains 3 bytes hint + [OriginalType2Operator(Type2Operator1.hintmask)] hintmask4, //my hint-mask extension, contains 4 bytes hint + [OriginalType2Operator(Type2Operator1.hintmask)] hintmask_bits,//my hint-mask extension, contains n bits of hint + + //--------- + + [OriginalType2Operator(Type2Operator1.cntrmask)] cntrmask1, //my counter-mask extension, contains 1 byte hint + [OriginalType2Operator(Type2Operator1.cntrmask)] cntrmask2, //my counter-mask extension, contains 2 bytes hint + [OriginalType2Operator(Type2Operator1.cntrmask)] cntrmask3, //my counter-mask extension, contains 3 bytes hint + [OriginalType2Operator(Type2Operator1.cntrmask)] cntrmask4, //my counter-mask extension, contains 4 bytes hint + [OriginalType2Operator(Type2Operator1.cntrmask)] cntrmask_bits, //my counter-mask extension, contains n bits of hint + + //--------------------- + [OriginalType2Operator(Type2Operator1.rmoveto)] rmoveto, + [OriginalType2Operator(Type2Operator1.hmoveto)] hmoveto, + [OriginalType2Operator(Type2Operator1.vstemhm)] vstemhm, + [OriginalType2Operator(Type2Operator1.rcurveline)] rcurveline, + [OriginalType2Operator(Type2Operator1.rlinecurve)] rlinecurve, + [OriginalType2Operator(Type2Operator1.vvcurveto)] vvcurveto, + [OriginalType2Operator(Type2Operator1.hhcurveto)] hhcurveto, + [OriginalType2Operator(Type2Operator1.shortint)] shortint, + [OriginalType2Operator(Type2Operator1.callgsubr)] callgsubr, + [OriginalType2Operator(Type2Operator1.vhcurveto)] vhcurveto, + //----------------------- + [OriginalType2Operator(Type2Operator1.hvcurveto)] hvcurveto, + //--------------------- + //Two-byte Type 2 Operators + [OriginalType2Operator(Type2Operator2.and)] and, + [OriginalType2Operator(Type2Operator2.or)] or, + [OriginalType2Operator(Type2Operator2.not)] not, + [OriginalType2Operator(Type2Operator2.abs)] abs, + [OriginalType2Operator(Type2Operator2.add)] add, + //------------------ + [OriginalType2Operator(Type2Operator2.sub)] sub, + [OriginalType2Operator(Type2Operator2.div)] div, + [OriginalType2Operator(Type2Operator2.neg)] neg, + [OriginalType2Operator(Type2Operator2.eq)] eq, + [OriginalType2Operator(Type2Operator2.drop)] drop, + [OriginalType2Operator(Type2Operator2.put)] put, + //------------------ + [OriginalType2Operator(Type2Operator2.get)] get, + [OriginalType2Operator(Type2Operator2.ifelse)] ifelse, + [OriginalType2Operator(Type2Operator2.random)] random, + [OriginalType2Operator(Type2Operator2.mul)] mul, + [OriginalType2Operator(Type2Operator2.sqrt)] sqrt, + [OriginalType2Operator(Type2Operator2.dup)] dup, + [OriginalType2Operator(Type2Operator2.exch)] exch, + [OriginalType2Operator(Type2Operator2.index)] index, + [OriginalType2Operator(Type2Operator2.roll)] roll, + [OriginalType2Operator(Type2Operator2.hflex)] hflex, + [OriginalType2Operator(Type2Operator2.flex)] flex, + [OriginalType2Operator(Type2Operator2.hflex1)] hflex1, + [OriginalType2Operator(Type2Operator2.flex1)] flex1 + } + + + + class Type2GlyphInstructionList + { + List _insts; + + public Type2GlyphInstructionList() + { + _insts = new List(); + } + + public Type2Instruction RemoveLast() + { + int last = _insts.Count - 1; + Type2Instruction _lastInst = _insts[last]; + _insts.RemoveAt(last); + return _lastInst; + } + // + public void AddInt(int intValue) + { +#if DEBUG + debugCheck(); +#endif + _insts.Add(new Type2Instruction(OperatorName.LoadInt, intValue)); + } + public void AddFloat(int float1616Fmt) + { +#if DEBUG + debugCheck(); + //var test = new Type2Instruction(OperatorName.LoadFloat, float1616Fmt); + //string str = test.ToString(); +#endif + _insts.Add(new Type2Instruction(OperatorName.LoadFloat, float1616Fmt)); + } + public void AddOp(OperatorName opName) + { +#if DEBUG + debugCheck(); +#endif + _insts.Add(new Type2Instruction(opName)); + } + + public void AddOp(OperatorName opName, int value) + { +#if DEBUG + debugCheck(); +#endif + _insts.Add(new Type2Instruction(opName, value)); + } + public int Count => _insts.Count; + internal void ChangeFirstInstToGlyphWidthValue() + { + //check the first element must be loadint + if (_insts.Count == 0) return; + + Type2Instruction firstInst = _insts[0]; + if (!firstInst.IsLoadInt) { throw new OpenFontNotSupportedException(); } + //the replace + _insts[0] = new Type2Instruction(OperatorName.GlyphWidth, firstInst.Value); + } + + + + + internal List InnerInsts => _insts; + +#if DEBUG + void debugCheck() + { + if (_dbugMark == 5 && _insts.Count > 50) + { + + } + } + public int dbugInstCount => _insts.Count; + int _dbugMark; + + public ushort dbugGlyphIndex; + + public int dbugMark + { + get => _dbugMark; + set + { + _dbugMark = value; + } + } + + public void dbugDumpInstructionListToFile(string filename) + { + dbugCffInstHelper.dbugDumpInstructionListToFile(_insts, filename); + } +#endif + } + +#if DEBUG + public static class dbugCffInstHelper + { + internal static void dbugDumpInstructionListToFile(this IEnumerable insts, string filename) + { + using (FileStream fs = new FileStream(filename, FileMode.Create)) + using (StreamWriter w = new StreamWriter(fs)) + { + int i = 0; + foreach (Type2Instruction inst in insts) + { + + w.Write("[" + i + "] "); + if (inst.IsLoadInt) + { + w.Write(inst.Value.ToString()); + w.Write(' '); + } + else + { + w.Write(inst.ToString()); + w.WriteLine(); + } + i++; + } + } + } + } + +#endif + + + class Type2CharStringParser + { + //from https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + //or https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf + + //Type 2 Charstring Organization: + //... + //The sequence and form of a Type 2 charstring program may be represented as: + + //w? {hs* vs* cm* hm* mt subpath}? {mt subpath}* endchar + + //where, + //w= width, + //hs = hstem or hstemhm command + //vs = vstem or vstemhm command + //cm = cntrmask operator + //hm = hintmask operator + //mt = moveto (i.e.any of the moveto) operators + + //subpath = refers to the construction of a subpath(one complete closed contour), + // which may include hintmaskoperators where appropriate. + + //------------- + // + //width: If the charstring has a width other than that of defaultWidthX(see Technical Note #5176, “The Compact Font Format Specification”), + // it must be specified as the first number in the charstring, + //and encoded as the difference from nominalWidthX + + + public Type2CharStringParser() + { + } + +#if DEBUG + int _dbugCount = 0; + int _dbugInstructionListMark = 0; +#endif + int _hintStemCount = 0; + bool _foundSomeStem = false; + bool _enterPathConstructionSeq = false; + + Type2GlyphInstructionList _insts; + int _current_integer_count = 0; + bool _doStemCount = true; + Cff1Font _currentCff1Font; + int _globalSubrBias; + int _localSubrBias; + + public void SetCurrentCff1Font(Cff1Font currentCff1Font) + { + //this will provide subr buffer for callsubr callgsubr + _currentFontDict = null;//reset + _currentCff1Font = currentCff1Font; + + if (_currentCff1Font._globalSubrRawBufferList != null) + { + _globalSubrBias = CalculateBias(currentCff1Font._globalSubrRawBufferList.Count); + } + if (_currentCff1Font._localSubrRawBufferList != null) + { + _localSubrBias = CalculateBias(currentCff1Font._localSubrRawBufferList.Count); + } + } + + + static int CalculateBias(int nsubr) + { + //------------- + //from Technical Note #5176 (CFF spec) + //resolve with bias + //Card16 bias; + //Card16 nSubrs = subrINDEX.count; + //if (CharstringType == 1) + // bias = 0; + //else if (nSubrs < 1240) + // bias = 107; + //else if (nSubrs < 33900) + // bias = 1131; + //else + // bias = 32768; + //find local subroutine + return (nsubr < 1240) ? 107 : (nsubr < 33900) ? 1131 : 32768; + } + + struct SimpleBinaryReader + { + byte[] _buffer; + int _pos; + public SimpleBinaryReader(byte[] buffer) + { + _buffer = buffer; + _pos = 0; + } + public bool IsEnd() => _pos >= _buffer.Length; + public byte ReadByte() + { + //read current byte to stack and advance pos after read + return _buffer[_pos++]; + } + public int BufferLength => _buffer.Length; + public int Position => _pos; + + public int ReadFloatFixed1616() + { + byte b0 = _buffer[_pos]; + byte b1 = _buffer[_pos + 1]; + byte b2 = _buffer[_pos + 2]; + byte b3 = _buffer[_pos + 3]; + + _pos += 4; + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + } + + void ParseType2CharStringBuffer(byte[] buffer) + { + byte b0 = 0; + + bool cont = true; + + var reader = new SimpleBinaryReader(buffer); + while (cont && !reader.IsEnd()) + { + b0 = reader.ReadByte(); +#if DEBUG + //easy for debugging here + _dbugCount++; + if (b0 < 32) + { + + } +#endif + switch (b0) + { + default: //else 32 -255 + { + if (b0 < 32) + { + Debug.WriteLine("err!:" + b0); + return; + } + // + _insts.AddInt(ReadIntegerNumber(ref reader, b0)); + if (_doStemCount) + { + _current_integer_count++; + } + } + break; + case 255: + { + + //from https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + //If the charstring byte contains the value 255, + //the next four bytes indicate a two’s complement signed number. + + //The first of these four bytes contains the highest order bits, + //he second byte contains the next higher order bits and + //the fourth byte contains the lowest order bits. + + //eg. found in font Asana Math regular, glyph_index: 114 , 292, 1070 etc. + + _insts.AddFloat(reader.ReadFloatFixed1616()); + + if (_doStemCount) + { + _current_integer_count++; + } + } + break; + case (byte)Type2Operator1.shortint: // 28 + + //shortint + //First byte of a 3-byte sequence specifying a number. + //a ShortInt value is specified by using the operator (28) followed by two bytes + //which represent numbers between –32768 and + 32767.The + //most significant byte follows the(28) + byte s_b0 = reader.ReadByte(); + byte s_b1 = reader.ReadByte(); + _insts.AddInt((short)((s_b0 << 8) | (s_b1))); + // + if (_doStemCount) + { + _current_integer_count++; + } + break; + //--------------------------------------------------- + case (byte)Type2Operator1._Reserved0_://??? + case (byte)Type2Operator1._Reserved2_://??? + case (byte)Type2Operator1._Reserved9_://??? + case (byte)Type2Operator1._Reserved13_://??? + case (byte)Type2Operator1._Reserved15_://??? + case (byte)Type2Operator1._Reserved16_: //??? + case (byte)Type2Operator1._Reserved17_: //??? + //reserved, do nothing ? + break; + + case (byte)Type2Operator1.escape: //12 + { + + b0 = reader.ReadByte(); + switch ((Type2Operator2)b0) + { + default: + if (b0 <= 38) + { + Debug.WriteLine("err!:" + b0); + return; + } + break; + //------------------------- + //4.1: Path Construction Operators + case Type2Operator2.flex: _insts.AddOp(OperatorName.flex); break; + case Type2Operator2.hflex: _insts.AddOp(OperatorName.hflex); break; + case Type2Operator2.hflex1: _insts.AddOp(OperatorName.hflex1); break; + case Type2Operator2.flex1: _insts.AddOp(OperatorName.flex1); ; break; + //------------------------- + //4.4: Arithmetic Operators + case Type2Operator2.abs: _insts.AddOp(OperatorName.abs); break; + case Type2Operator2.add: _insts.AddOp(OperatorName.add); break; + case Type2Operator2.sub: _insts.AddOp(OperatorName.sub); break; + case Type2Operator2.div: _insts.AddOp(OperatorName.div); break; + case Type2Operator2.neg: _insts.AddOp(OperatorName.neg); break; + case Type2Operator2.random: _insts.AddOp(OperatorName.random); break; + case Type2Operator2.mul: _insts.AddOp(OperatorName.mul); break; + case Type2Operator2.sqrt: _insts.AddOp(OperatorName.sqrt); break; + case Type2Operator2.drop: _insts.AddOp(OperatorName.drop); break; + case Type2Operator2.exch: _insts.AddOp(OperatorName.exch); break; + case Type2Operator2.index: _insts.AddOp(OperatorName.index); break; + case Type2Operator2.roll: _insts.AddOp(OperatorName.roll); break; + case Type2Operator2.dup: _insts.AddOp(OperatorName.dup); break; + + //------------------------- + //4.5: Storage Operators + case Type2Operator2.put: _insts.AddOp(OperatorName.put); break; + case Type2Operator2.get: _insts.AddOp(OperatorName.get); break; + //------------------------- + //4.6: Conditional + case Type2Operator2.and: _insts.AddOp(OperatorName.and); break; + case Type2Operator2.or: _insts.AddOp(OperatorName.or); break; + case Type2Operator2.not: _insts.AddOp(OperatorName.not); break; + case Type2Operator2.eq: _insts.AddOp(OperatorName.eq); break; + case Type2Operator2.ifelse: _insts.AddOp(OperatorName.ifelse); break; + } + + StopStemCount(); + } + break; + + //--------------------------------------------------------------------------- + case (byte)Type2Operator1.endchar: + AddEndCharOp(); + cont = false; + //when we found end char + //stop reading this... + break; + case (byte)Type2Operator1.rmoveto: AddMoveToOp(OperatorName.rmoveto); StopStemCount(); break; + case (byte)Type2Operator1.hmoveto: AddMoveToOp(OperatorName.hmoveto); StopStemCount(); break; + case (byte)Type2Operator1.vmoveto: AddMoveToOp(OperatorName.vmoveto); StopStemCount(); break; + //--------------------------------------------------------------------------- + case (byte)Type2Operator1.rlineto: _insts.AddOp(OperatorName.rlineto); StopStemCount(); break; + case (byte)Type2Operator1.hlineto: _insts.AddOp(OperatorName.hlineto); StopStemCount(); break; + case (byte)Type2Operator1.vlineto: _insts.AddOp(OperatorName.vlineto); StopStemCount(); break; + case (byte)Type2Operator1.rrcurveto: _insts.AddOp(OperatorName.rrcurveto); StopStemCount(); break; + case (byte)Type2Operator1.hhcurveto: _insts.AddOp(OperatorName.hhcurveto); StopStemCount(); break; + case (byte)Type2Operator1.hvcurveto: _insts.AddOp(OperatorName.hvcurveto); StopStemCount(); break; + case (byte)Type2Operator1.rcurveline: _insts.AddOp(OperatorName.rcurveline); StopStemCount(); break; + case (byte)Type2Operator1.rlinecurve: _insts.AddOp(OperatorName.rlinecurve); StopStemCount(); break; + case (byte)Type2Operator1.vhcurveto: _insts.AddOp(OperatorName.vhcurveto); StopStemCount(); break; + case (byte)Type2Operator1.vvcurveto: _insts.AddOp(OperatorName.vvcurveto); StopStemCount(); break; + //------------------------------------------------------------------- + //4.3 Hint Operators + case (byte)Type2Operator1.hstem: AddStemToList(OperatorName.hstem); break; + case (byte)Type2Operator1.vstem: AddStemToList(OperatorName.vstem); break; + case (byte)Type2Operator1.vstemhm: AddStemToList(OperatorName.vstemhm); break; + case (byte)Type2Operator1.hstemhm: AddStemToList(OperatorName.hstemhm); break; + //------------------------------------------------------------------- + case (byte)Type2Operator1.hintmask: AddHintMaskToList(ref reader); StopStemCount(); break; + case (byte)Type2Operator1.cntrmask: AddCounterMaskToList(ref reader); StopStemCount(); break; + //------------------------------------------------------------------- + //4.7: Subroutine Operators + case (byte)Type2Operator1._return: + { +#if DEBUG + if (!reader.IsEnd()) + { + throw new OpenFontNotSupportedException(); + } + +#endif + } + return; + //------------------------------------------------------------------- + case (byte)Type2Operator1.callsubr: + { + //get local subr proc + if (_currentCff1Font != null) + { + Type2Instruction inst = _insts.RemoveLast(); + if (!inst.IsLoadInt) + { + throw new OpenFontNotSupportedException(); + } + if (_doStemCount) + { + _current_integer_count--; + } + //subr_no must be adjusted with proper bias value + if (_currentCff1Font._localSubrRawBufferList != null) + { + ParseType2CharStringBuffer(_currentCff1Font._localSubrRawBufferList[inst.Value + _localSubrBias]); + } + else if (_currentFontDict != null) + { + //use private dict + ParseType2CharStringBuffer(_currentFontDict.LocalSubr[inst.Value + _localSubrBias]); + } + else + { + throw new OpenFontNotSupportedException(); + } + } + } + break; + case (byte)Type2Operator1.callgsubr: + { + if (_currentCff1Font != null) + { + Type2Instruction inst = _insts.RemoveLast(); + if (!inst.IsLoadInt) + { + throw new OpenFontNotSupportedException(); + } + if (_doStemCount) + { + _current_integer_count--; + } + //subr_no must be adjusted with proper bias value + //load global subr + ParseType2CharStringBuffer(_currentCff1Font._globalSubrRawBufferList[inst.Value + _globalSubrBias]); + } + } + break; + } + } + } +#if DEBUG + public ushort dbugCurrentGlyphIndex; +#endif + FontDict _currentFontDict; + public void SetCidFontDict(FontDict fontdic) + { +#if DEBUG + if (fontdic == null) + { + throw new OpenFontNotSupportedException(); + } +#endif + + _currentFontDict = fontdic; + if (fontdic.LocalSubr != null) + { + _localSubrBias = CalculateBias(_currentFontDict.LocalSubr.Count); + } + else + { + _localSubrBias = 0; + } + } + + public Type2GlyphInstructionList ParseType2CharString(byte[] buffer) + { + //reset + _hintStemCount = 0; + _current_integer_count = 0; + _foundSomeStem = false; + _enterPathConstructionSeq = false; + _doStemCount = true; + + _insts = new Type2GlyphInstructionList(); + //-------------------- +#if DEBUG + _dbugInstructionListMark++; + if (_currentCff1Font == null) + { + throw new OpenFontNotSupportedException(); + } + // + _insts.dbugGlyphIndex = dbugCurrentGlyphIndex; + + if (dbugCurrentGlyphIndex == 496) + { + + } +#endif + ParseType2CharStringBuffer(buffer); + +#if DEBUG + if (dbugCurrentGlyphIndex == 496) + { + //_insts.dbugDumpInstructionListToFile("glyph_496.txt"); + } +#endif + return _insts; + } + + void StopStemCount() + { + _current_integer_count = 0; + _doStemCount = false; + } + OperatorName _latestOpName = OperatorName.Unknown; + + void AddEndCharOp() + { + //from https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + //Note 4 The first stack - clearing operator, which must be one of + //hstem, hstemhm, vstem, vstemhm, + //cntrmask, hintmask, + //hmoveto, vmoveto, rmoveto, + //or endchar, + //takes an additional argument — the width(as described earlier), which may be expressed as zero or one numeric argument + + if (!_foundSomeStem && !_enterPathConstructionSeq) + { + if (_insts.Count > 0) + { + _insts.ChangeFirstInstToGlyphWidthValue(); + } + } + //takes an additional argument — the width(as described earlier), which may be expressed as zero or one numeric argument + _insts.AddOp(OperatorName.endchar); + } + + + + /// + /// for hmoveto, vmoveto, rmoveto + /// + /// + void AddMoveToOp(OperatorName op) + { + //from https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + //Note 4 The first stack - clearing operator, which must be one of + //hstem, hstemhm, vstem, vstemhm, + //cntrmask, hintmask, + //hmoveto, vmoveto, rmoveto, + //or endchar, + //takes an additional argument — the width(as described earlier), which may be expressed as zero or one numeric argument + //just add + + if (!_foundSomeStem && !_enterPathConstructionSeq) + { + if (op == OperatorName.rmoveto) + { + if ((_insts.Count % 2) != 0) + { + _insts.ChangeFirstInstToGlyphWidthValue(); + } + } + else + { + //vmoveto, hmoveto + if (_insts.Count > 1) + { + //... + _insts.ChangeFirstInstToGlyphWidthValue(); + } + } + } + _enterPathConstructionSeq = true; + _insts.AddOp(op); + } + /// + /// for hstem, hstemhm, vstem, vstemhm + /// + /// + void AddStemToList(OperatorName stemName) + { + + //from https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5177.Type2.pdf + //Note 4 The first stack - clearing operator, which must be one of + //hstem, hstemhm, vstem, vstemhm, + //cntrmask, hintmask, + //hmoveto, vmoveto, rmoveto, + //or endchar, + //takes an additional argument — the width(as described earlier), which may be expressed as zero or one numeric argument + + //support 4 kinds + + //1. + //|- y dy {dya dyb}* hstemhm (18) |- + //2. + //|- x dx {dxa dxb}* vstemhm (23) |- + //3. + //|- y dy {dya dyb}* hstem (1) |- + //4. + //|- x dx {dxa dxb}* vstem (3) |- + //----------------------- + + //notes + //The sequence and form of a Type 2 charstring program may be + //represented as: + //w? { hs* vs*cm * hm * mt subpath}? { mt subpath} *endchar + + if ((_current_integer_count % 2) != 0) + { + //all kind has even number of stem + if (_foundSomeStem) + { +#if DEBUG + _insts.dbugDumpInstructionListToFile("test_type2_" + (_dbugInstructionListMark - 1) + ".txt"); +#endif + throw new OpenFontNotSupportedException(); + } + else + { + //the first one is 'width' + _insts.ChangeFirstInstToGlyphWidthValue(); + _current_integer_count--; + } + } + _hintStemCount += (_current_integer_count / 2); //save a snapshot of stem count + _insts.AddOp(stemName); + _current_integer_count = 0;//clear + _foundSomeStem = true; + _latestOpName = stemName; + } + /// + /// add hintmask + /// + /// + void AddHintMaskToList(ref SimpleBinaryReader reader) + { + if (_foundSomeStem && _current_integer_count > 0) + { + + //type2 5177.pdf + //... + //If hstem and vstem hints are both declared at the beginning of + //a charstring, and this sequence is followed directly by the + //hintmask or cntrmask operators, ... + //the vstem hint operator need not be included *** + +#if DEBUG + if ((_current_integer_count % 2) != 0) + { + throw new OpenFontNotSupportedException(); + } + else + { + + } +#endif + if (_doStemCount) + { + switch (_latestOpName) + { + case OperatorName.hstem: + //add vstem ***( from reason above) + + _hintStemCount += (_current_integer_count / 2); //save a snapshot of stem count + _insts.AddOp(OperatorName.vstem); + _latestOpName = OperatorName.vstem; + _current_integer_count = 0; //clear + break; + case OperatorName.hstemhm: + //add vstem ***( from reason above) ?? + _hintStemCount += (_current_integer_count / 2); //save a snapshot of stem count + _insts.AddOp(OperatorName.vstem); + _latestOpName = OperatorName.vstem; + _current_integer_count = 0;//clear + break; + case OperatorName.vstemhm: + //------- + //TODO: review here? + //found this in xits.otf + _hintStemCount += (_current_integer_count / 2); //save a snapshot of stem count + _insts.AddOp(OperatorName.vstem); + _latestOpName = OperatorName.vstem; + _current_integer_count = 0;//clear + break; + default: + throw new OpenFontNotSupportedException(); + } + } + else + { + + } + } + + if (_hintStemCount == 0) + { + if (!_foundSomeStem) + { + _hintStemCount = (_current_integer_count / 2); + if (_hintStemCount == 0) + { + return; + } + _foundSomeStem = true;//? + } + else + { + throw new OpenFontNotSupportedException(); + } + } + + //---------------------- + //this is my hintmask extension, => to fit with our Evaluation stack + int properNumberOfMaskBytes = (_hintStemCount + 7) / 8; + + if (reader.Position + properNumberOfMaskBytes >= reader.BufferLength) + { + throw new OpenFontNotSupportedException(); + } + if (properNumberOfMaskBytes > 4) + { + int remaining = properNumberOfMaskBytes; + + for (; remaining > 3;) + { + _insts.AddInt(( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) | + (reader.ReadByte()) + )); + remaining -= 4; //*** + } + switch (remaining) + { + case 0: + //do nothing + break; + case 1: + _insts.AddInt(reader.ReadByte() << 24); + break; + case 2: + _insts.AddInt( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16)); + + break; + case 3: + _insts.AddInt( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8)); + break; + default: throw new OpenFontNotSupportedException();//should not occur ! + } + + _insts.AddOp(OperatorName.hintmask_bits, properNumberOfMaskBytes); + } + else + { + //last remaining <4 bytes + switch (properNumberOfMaskBytes) + { + case 0: + default: throw new OpenFontNotSupportedException();//should not occur ! + case 1: + _insts.AddOp(OperatorName.hintmask1, (reader.ReadByte() << 24)); + break; + case 2: + _insts.AddOp(OperatorName.hintmask2, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) + ); + break; + case 3: + _insts.AddOp(OperatorName.hintmask3, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) + ); + break; + case 4: + _insts.AddOp(OperatorName.hintmask4, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) | + (reader.ReadByte()) + ); + break; + } + } + } + /// + /// cntrmask + /// + /// + void AddCounterMaskToList(ref SimpleBinaryReader reader) + { + if (_hintStemCount == 0) + { + if (!_foundSomeStem) + { + //???? + _hintStemCount = (_current_integer_count / 2); + _foundSomeStem = true;//? + } + else + { + throw new OpenFontNotSupportedException(); + } + } + else + { + _hintStemCount += (_current_integer_count / 2); + } + //---------------------- + //this is my hintmask extension, => to fit with our Evaluation stack + int properNumberOfMaskBytes = (_hintStemCount + 7) / 8; + if (reader.Position + properNumberOfMaskBytes >= reader.BufferLength) + { + throw new OpenFontNotSupportedException(); + } + + if (properNumberOfMaskBytes > 4) + { + int remaining = properNumberOfMaskBytes; + + for (; remaining > 3;) + { + _insts.AddInt(( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) | + (reader.ReadByte()) + )); + remaining -= 4; //*** + } + switch (remaining) + { + case 0: + //do nothing + break; + case 1: + _insts.AddInt(reader.ReadByte() << 24); + break; + case 2: + _insts.AddInt( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16)); + + break; + case 3: + _insts.AddInt( + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8)); + break; + default: throw new OpenFontNotSupportedException();//should not occur ! + } + + _insts.AddOp(OperatorName.cntrmask_bits, properNumberOfMaskBytes); + } + else + { + //last remaining <4 bytes + switch (properNumberOfMaskBytes) + { + case 0: + default: throw new OpenFontNotSupportedException();//should not occur ! + case 1: + _insts.AddOp(OperatorName.cntrmask1, (reader.ReadByte() << 24)); + break; + case 2: + _insts.AddOp(OperatorName.cntrmask2, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) + ); + break; + case 3: + _insts.AddOp(OperatorName.cntrmask3, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) + ); + break; + case 4: + _insts.AddOp(OperatorName.cntrmask4, + (reader.ReadByte() << 24) | + (reader.ReadByte() << 16) | + (reader.ReadByte() << 8) | + (reader.ReadByte()) + ); + break; + } + } + } + + static int ReadIntegerNumber(ref SimpleBinaryReader _reader, byte b0) + { + if (b0 >= 32 && b0 <= 246) + { + return b0 - 139; + } + else if (b0 <= 250) // && b0 >= 247 , *** if-else sequence is important! *** + { + byte b1 = _reader.ReadByte(); + return (b0 - 247) * 256 + b1 + 108; + } + else if (b0 <= 254) //&& b0 >= 251 ,*** if-else sequence is important! *** + { + byte b1 = _reader.ReadByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + else + { + throw new OpenFontNotSupportedException(); + } + } + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2InstructionCompacter.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2InstructionCompacter.cs new file mode 100644 index 00000000..cbc98840 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.CFF/Type2InstructionCompacter.cs @@ -0,0 +1,483 @@ +//MIT, 2020-present, WinterDev + +using System; +using System.Collections.Generic; + + +namespace Typography.OpenFont.CFF +{ + + class Type2InstructionCompacter + { + //This is our extension + //----------------------- +#if DEBUG + public static bool s_dbugBreakMe; +#endif + List _step1List; + List _step2List; + + void CompactStep1OnlyLoadInt(List insts) + { + int j = insts.Count; + CompactRange _latestCompactRange = CompactRange.None; + int startCollectAt = -1; + int collecting_count = 0; + void FlushWaitingNumbers() + { + //Nested method + //flush waiting integer + if (_latestCompactRange == CompactRange.Short) + { + switch (collecting_count) + { + default: throw new OpenFontNotSupportedException(); + case 0: break; //nothing + case 2: + _step1List.Add(new Type2Instruction(OperatorName.LoadShort2, + (((ushort)insts[startCollectAt].Value) << 16) | + (((ushort)insts[startCollectAt + 1].Value)) + )); + startCollectAt += 2; + collecting_count -= 2; + break; + case 1: + _step1List.Add(insts[startCollectAt]); + startCollectAt += 1; + collecting_count -= 1; + break; + + } + } + else + { + switch (collecting_count) + { + default: throw new OpenFontNotSupportedException(); + case 0: break;//nothing + case 4: + { + _step1List.Add(new Type2Instruction(OperatorName.LoadSbyte4, + (((byte)insts[startCollectAt].Value) << 24) | + (((byte)insts[startCollectAt + 1].Value) << 16) | + (((byte)insts[startCollectAt + 2].Value) << 8) | + (((byte)insts[startCollectAt + 3].Value) << 0) + )); + startCollectAt += 4; + collecting_count -= 4; + } + break; + case 3: + _step1List.Add(new Type2Instruction(OperatorName.LoadSbyte3, + (((byte)insts[startCollectAt].Value) << 24) | + (((byte)insts[startCollectAt + 1].Value) << 16) | + (((byte)insts[startCollectAt + 2].Value) << 8) + )); + startCollectAt += 3; + collecting_count -= 3; + break; + case 2: + _step1List.Add(new Type2Instruction(OperatorName.LoadShort2, + (((ushort)insts[startCollectAt].Value) << 16) | + ((ushort)insts[startCollectAt + 1].Value) + )); + startCollectAt += 2; + collecting_count -= 2; + break; + case 1: + _step1List.Add(insts[startCollectAt]); + startCollectAt += 1; + collecting_count -= 1; + break; + + } + } + + startCollectAt = -1; + collecting_count = 0; + } + + for (int i = 0; i < j; ++i) + { + Type2Instruction inst = insts[i]; + if (inst.IsLoadInt) + { + //check waiting data in queue + //get compact range + CompactRange c1 = GetCompactRange(inst.Value); + switch (c1) + { + default: throw new OpenFontNotSupportedException(); + case CompactRange.None: + { + if (collecting_count > 0) + { + FlushWaitingNumbers(); + } + _step1List.Add(inst); + _latestCompactRange = CompactRange.None; + } + break; + case CompactRange.SByte: + { + if (_latestCompactRange == CompactRange.Short) + { + FlushWaitingNumbers(); + _latestCompactRange = CompactRange.SByte; + } + + switch (collecting_count) + { + default: throw new OpenFontNotSupportedException(); + case 0: + startCollectAt = i; + _latestCompactRange = CompactRange.SByte; + break; + case 1: + break; + case 2: + break; + case 3: + //we already have 3 bytes + //so this is 4th byte + collecting_count++; + FlushWaitingNumbers(); + continue; + } + collecting_count++; + } + break; + case CompactRange.Short: + { + if (_latestCompactRange == CompactRange.SByte) + { + FlushWaitingNumbers(); + _latestCompactRange = CompactRange.Short; + } + + switch (collecting_count) + { + default: throw new OpenFontNotSupportedException(); + case 0: + startCollectAt = i; + _latestCompactRange = CompactRange.Short; + break; + case 1: + //we already have 1 so this is 2nd + collecting_count++; + FlushWaitingNumbers(); + continue; + } + + collecting_count++; + } + break; + } + } + else + { + //other cmds + //flush waiting cmd + if (collecting_count > 0) + { + FlushWaitingNumbers(); + } + + _step1List.Add(inst); + _latestCompactRange = CompactRange.None; + } + } + } + + + static byte IsLoadIntOrMergeableLoadIntExtension(OperatorName opName) + { + switch (opName) + { + //case OperatorName.LoadSbyte3://except LoadSbyte3 *** + case OperatorName.LoadInt: //merge-able + return 1; + case OperatorName.LoadShort2://merge-able + return 2; + case OperatorName.LoadSbyte4://merge-able + return 3; + } + return 0; + } + void CompactStep2MergeLoadIntWithNextCommand() + { + //a second pass + //check if we can merge some load int( LoadInt, LoadSByte4, LoadShort2) except LoadSByte3 + //to next instruction command or not + int j = _step1List.Count; + for (int i = 0; i < j; ++i) + { + Type2Instruction i0 = _step1List[i]; + + if (i + 1 < j) + { + //has next cmd + byte merge_flags = IsLoadIntOrMergeableLoadIntExtension((OperatorName)i0.Op); + if (merge_flags > 0) + { + Type2Instruction i1 = _step1List[i + 1]; + //check i1 has empty space for i0 or not + bool canbe_merged = false; + switch ((OperatorName)i1.Op) + { + case OperatorName.LoadInt: + case OperatorName.LoadShort2: + case OperatorName.LoadSbyte4: + case OperatorName.LoadSbyte3: + case OperatorName.LoadFloat: + + case OperatorName.hintmask1: + case OperatorName.hintmask2: + case OperatorName.hintmask3: + case OperatorName.hintmask4: + case OperatorName.hintmask_bits: + case OperatorName.cntrmask1: + case OperatorName.cntrmask2: + case OperatorName.cntrmask3: + case OperatorName.cntrmask4: + case OperatorName.cntrmask_bits: + break; + default: + canbe_merged = true; + break; + } + if (canbe_merged) + { + +#if DEBUG + if (merge_flags > 3) { throw new OpenFontNotSupportedException(); } +#endif + + _step2List.Add(new Type2Instruction((byte)((merge_flags << 6) | i1.Op), i0.Value)); + i += 1; + } + else + { + _step2List.Add(i0); + } + } + else + { + //this is the last one + _step2List.Add(i0); + } + + } + else + { + //this is the last one + _step2List.Add(i0); + } + } + } + public Type2Instruction[] Compact(List insts) + { + //for simpicity + //we have 2 passes + //1. compact consecutive numbers + //2. compact other cmd + + //reset + if (_step1List == null) + { + _step1List = new List(); + } + if (_step2List == null) + { + _step2List = new List(); + } + _step1List.Clear(); + _step2List.Clear(); + // + CompactStep1OnlyLoadInt(insts); + CompactStep2MergeLoadIntWithNextCommand(); +#if DEBUG + + //you can check/compare the compact form and the original form + dbugReExpandAndCompare_ForStep1(_step1List, insts); + dbugReExpandAndCompare_ForStep2(_step2List, insts); +#endif + return _step2List.ToArray(); + //return _step1List.ToArray(); + + } + +#if DEBUG + void dbugReExpandAndCompare_ForStep1(List step1, List org) + { + List expand1 = new List(org.Count); + { + int j = step1.Count; + for (int i = 0; i < j; ++i) + { + Type2Instruction inst = step1[i]; + switch ((OperatorName)inst.Op) + { + case OperatorName.LoadSbyte4: + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 24))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 16))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 8))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value))); + break; + case OperatorName.LoadSbyte3: + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 24))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 16))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 8))); + break; + case OperatorName.LoadShort2: + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value >> 16))); + expand1.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value))); + break; + default: + expand1.Add(inst); + break; + } + } + } + //-------------------------------------------- + if (expand1.Count != org.Count) + { + //ERR=> then find first diff + int min = Math.Min(expand1.Count, org.Count); + for (int i = 0; i < min; ++i) + { + Type2Instruction inst_exp = expand1[i]; + Type2Instruction inst_org = org[i]; + if (inst_exp.Op != inst_org.Op || + inst_exp.Value != inst_org.Value) + { + throw new OpenFontNotSupportedException(); + } + } + } + else + { + //compare command-by-command + int j = step1.Count; + for (int i = 0; i < j; ++i) + { + Type2Instruction inst_exp = expand1[i]; + Type2Instruction inst_org = org[i]; + if (inst_exp.Op != inst_org.Op || + inst_exp.Value != inst_org.Value) + { + throw new OpenFontNotSupportedException(); + } + } + } + + } + void dbugReExpandAndCompare_ForStep2(List step2, List org) + { + List expand2 = new List(org.Count); + { + int j = step2.Count; + for (int i = 0; i < j; ++i) + { + + Type2Instruction inst = step2[i]; + + //we use upper 2 bits to indicate that this is merged cmd or not + byte merge_flags = (byte)(inst.Op >> 6); + //lower 6 bits is actual cmd + OperatorName onlyOpName = (OperatorName)(inst.Op & 0b111111); + switch (onlyOpName) + { + case OperatorName.LoadSbyte4: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 24))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 16))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 8))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value))); + break; + case OperatorName.LoadSbyte3: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 24))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 16))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 8))); + break; + case OperatorName.LoadShort2: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value >> 16))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value))); + break; + default: + { + switch (merge_flags) + { + case 0: + expand2.Add(inst); + break; + case 1: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, inst.Value)); + expand2.Add(new Type2Instruction(onlyOpName, 0)); + break; + case 2: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value >> 16))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (short)(inst.Value))); + expand2.Add(new Type2Instruction(onlyOpName, 0)); + break; + case 3: + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 24))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 16))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value >> 8))); + expand2.Add(new Type2Instruction(OperatorName.LoadInt, (sbyte)(inst.Value))); + expand2.Add(new Type2Instruction(onlyOpName, 0)); + break; + } + } + break; + } + } + } + //-------------------------------------------- + if (expand2.Count != org.Count) + { + throw new OpenFontNotSupportedException(); + } + else + { + //compare command-by-command + int j = step2.Count; + for (int i = 0; i < j; ++i) + { + Type2Instruction inst_exp = expand2[i]; + Type2Instruction inst_org = org[i]; + if (inst_exp.Op != inst_org.Op || + inst_exp.Value != inst_org.Value) + { + throw new OpenFontNotSupportedException(); + } + } + } + + } +#endif + enum CompactRange + { + None, + // + SByte, + Short, + } + + static CompactRange GetCompactRange(int value) + { + if (value > sbyte.MinValue && value < sbyte.MaxValue) + { + return CompactRange.SByte; + } + else if (value > short.MinValue && value < short.MaxValue) + { + return CompactRange.Short; + } + else + { + return CompactRange.None; + } + } + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/HorizontalDeviceMetrics.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/HorizontalDeviceMetrics.cs new file mode 100644 index 00000000..c92c302f --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/HorizontalDeviceMetrics.cs @@ -0,0 +1,59 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + class HorizontalDeviceMetrics : TableEntry + { + public const string _N = "hdmx"; + public override string Name => _N; + // + //https://www.microsoft.com/typography/otspec/hdmx.htm + //The hdmx table relates to OpenType™ fonts with TrueType outlines. + //The Horizontal Device Metrics table stores integer advance widths scaled to particular pixel sizes. + //This allows the font manager to build integer width tables without calling the scaler for each glyph. + //Typically this table contains only selected screen sizes. + //This table is sorted by pixel size. + //The checksum for this table applies to both subtables listed. + + //Note that for non-square pixel grids, + //the character width (in pixels) will be used to determine which device record to use. + //For example, a 12 point character on a device with a resolution of 72x96 would be 12 pixels high and 16 pixels wide. + //The hdmx device record for 16 pixel characters would be used. + + //If bit 4 of the flag field in the 'head' table is not set, + //then it is assumed that the font scales linearly; in this case an 'hdmx' table is not necessary and should not be built. + //If bit 4 of the flag field is set, then one or more glyphs in the font are assumed to scale nonlinearly. + //In this case, performance can be improved by including the 'hdmx' table with one or more important DeviceRecord's for important sizes. + //Please see the chapter “Recommendations for OpenType Fonts” for more detail. + + //The table begins as follows: + //hdmx Header + //Type Name Description + //USHORT version Table version number (0) + //SHORT numRecords Number of device records. + //LONG sizeDeviceRecord Size of a device record, long aligned. + //DeviceRecord records[numRecords] Array of device records. + + //Each DeviceRecord for format 0 looks like this. + //Device Record + //Type Name Description + //BYTE pixelSize Pixel size for following widths (as ppem). + //BYTE maxWidth Maximum width. + //BYTE widths[numGlyphs] Array of widths (numGlyphs is from the 'maxp' table). + + //Each DeviceRecord is padded with 0's to make it long word aligned. + + //Each Width value is the width of the particular glyph, in pixels, + //at the pixels per em (ppem) size listed at the start of the DeviceRecord. + + //The ppem sizes are measured along the y axis. + + protected override void ReadContentFrom(BinaryReader reader) + { + + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Kern.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Kern.cs new file mode 100644 index 00000000..8cd3ddd2 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Kern.cs @@ -0,0 +1,180 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System.Collections.Generic; +using System.IO; +namespace Typography.OpenFont.Tables +{ + class Kern : TableEntry + { + public const string _N = "kern"; + public override string Name => _N; + // + //https://docs.microsoft.com/en-us/typography/opentype/spec/kern + + //Note: Apple has extended the definition of the 'kern' table to provide additional functionality. + //The Apple extensions are not supported on Windows.Fonts intended for cross-platform use or + //for the Windows platform in general should conform to the 'kern' table format specified here. + + List _kernSubTables = new List(); + public short GetKerningDistance(ushort left, ushort right) + { + //use kern sub table 0 + //TODO: review if have more than 1 table + return _kernSubTables[0].GetKernDistance(left, right); + } + protected override void ReadContentFrom(BinaryReader reader) + { + ushort verion = reader.ReadUInt16(); + ushort nTables = reader.ReadUInt16();//subtable count + //TODO: review here + if (nTables > 1) + { + Utils.WarnUnimplemented("Support for {0} kerning tables", nTables); + } + + for (int i = 0; i < nTables; ++i) + { + ushort subTableVersion = reader.ReadUInt16(); + ushort len = reader.ReadUInt16(); //Length of the subtable, in bytes (including this header). + KernCoverage kerCoverage = new KernCoverage(reader.ReadUInt16());//What type of information is contained in this table. + + //The coverage field is divided into the following sub-fields, with sizes given in bits: + //---------------------------------------------- + //Format of the subtable. + //Only formats 0 and 2 have been defined. + //Formats 1 and 3 through 255 are reserved for future use. + + switch (kerCoverage.format) + { + case 0: + ReadSubTableFormat0(reader, len - (3 * 2));//3 header field * 2 byte each + break; + case 2: + //TODO: implement + default: + Utils.WarnUnimplemented("Kerning Coverage Format {0}", kerCoverage.format); + break; + } + } + } + + void ReadSubTableFormat0(BinaryReader reader, int remainingBytes) + { + ushort npairs = reader.ReadUInt16(); + ushort searchRange = reader.ReadUInt16(); + ushort entrySelector = reader.ReadUInt16(); + ushort rangeShift = reader.ReadUInt16(); + //---------------------------------------------- + var ksubTable = new KerningSubTable(npairs); + _kernSubTables.Add(ksubTable); + while (npairs > 0) + { + ksubTable.AddKernPair( + reader.ReadUInt16(), //left// + reader.ReadUInt16(),//right + reader.ReadInt16());//value + npairs--; + } + } + readonly struct KerningPair + { + /// + /// left glyph index + /// + public readonly ushort left; + /// + /// right glyph index + /// + public readonly ushort right; + /// + /// n FUnits. If this value is greater than zero, the characters will be moved apart. If this value is less than zero, the character will be moved closer together. + /// + public readonly short value; + public KerningPair(ushort left, ushort right, short value) + { + this.left = left; + this.right = right; + this.value = value; + } +#if DEBUG + public override string ToString() + { + return left + " " + right; + } +#endif + } + readonly struct KernCoverage + { + //horizontal 0 1 1 if table has horizontal data, 0 if vertical. + //minimum 1 1 If this bit is set to 1, the table has minimum values. If set to 0, the table has kerning values. + //cross-stream 2 1 If set to 1, kerning is perpendicular to the flow of the text. + + //horizontal ... + //If the text is normally written horizontally, + //kerning will be done in the up and down directions. + //If kerning values are positive, the text will be kerned upwards; + //if they are negative, the text will be kerned downwards. + + //vertical ... + //If the text is normally written vertically, + //kerning will be done in the left and right directions. + //If kerning values are positive, the text will be kerned to the right; + //if they are negative, the text will be kerned to the left. + + //The value 0x8000 in the kerning data resets the cross-stream kerning back to 0. + //override 3 1 If this bit is set to 1 the value in this table should replace the value currently being accumulated. + //reserved1 4-7 4 Reserved. This should be set to zero. + //format 8-15 8 Format of the subtable. Only formats 0 and 2 have been defined. Formats 1 and 3 through 255 are reserved for future use. + // + public readonly ushort coverage; + public readonly bool horizontal; + public readonly bool hasMinimum; + public readonly bool crossStream; + public readonly bool _override; + public readonly byte format; + public KernCoverage(ushort coverage) + { + this.coverage = coverage; + //bit 0,len 1, 1 if table has horizontal data, 0 if vertical. + horizontal = (coverage & 0x1) == 1; + //bit 1,len 1, If this bit is set to 1, the table has minimum values. If set to 0, the table has kerning values. + hasMinimum = ((coverage >> 1) & 0x1) == 1; + //bit 2,len 1, If set to 1, kerning is perpendicular to the flow of the text. + crossStream = ((coverage >> 2) & 0x1) == 1; + //bit 3,len 1, If this bit is set to 1 the value in this table should replace the value currently being accumulated. + _override = ((coverage >> 3) & 0x1) == 1; + //bit 4-7 => Reserved. This should be set to zero. + format = (byte)((coverage >> 8) & 0xff); + } + } + + class KerningSubTable + { + List _kernPairs; + Dictionary _kernDic; + public KerningSubTable(int capcity) + { + _kernPairs = new List(capcity); + _kernDic = new Dictionary(capcity); + } + public void AddKernPair(ushort left, ushort right, short value) + { + _kernPairs.Add(new KerningPair(left, right, value)); + //may has duplicate key ? + //TODO: review here + uint key = (uint)((left << 16) | right); + _kernDic[key] = value; //just replace? + } + public short GetKernDistance(ushort left, ushort right) + { + //find if we have this left & right ? + uint key = (uint)((left << 16) | right); + + _kernDic.TryGetValue(key, out short found); + return found; + } + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/LinearThreashold.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/LinearThreashold.cs new file mode 100644 index 00000000..d0e01507 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/LinearThreashold.cs @@ -0,0 +1 @@ +//TODO: implement this \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Merge.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Merge.cs new file mode 100644 index 00000000..d0e01507 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Merge.cs @@ -0,0 +1 @@ +//TODO: implement this \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Meta.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Meta.cs new file mode 100644 index 00000000..95134061 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/Meta.cs @@ -0,0 +1,237 @@ +//MIT, 2020-present, WinterDev + +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/meta + //meta — Metadata Table + + + //NOTE: readmore about language tag, https://tools.ietf.org/html/bcp47 + //...The language of an information item or a user's language preferences + //often need to be identified so that appropriate processing can be + //applied. + //... + //... + //One means of indicating the language used is by labeling the + //information content with an identifier or "tag". These tags can also + //be used to specify the user's preferences when selecting information + //content or to label additional attributes of content and associated + //resources. + + //.. + //..The Language Tag + //.. + + //Language tags are used to help identify languages, whether spoken, + //written, signed, or otherwise signaled, for the purpose of + //communication.This includes constructed and artificial languages + //but excludes languages not intended primarily for human + //communication, such as programming languages. + + + + + + class Meta : TableEntry + { + //The metadata table contains various metadata values for the font. + //Different categories of metadata are identified by four-character tags. + //Values for different categories can be either binary or text. + + public const string _N = "meta"; + public override string Name => _N; + public Meta() { } + /// + /// dlng tags + /// + public string[] DesignLanguageTags { get; private set; } + /// + /// slng tags + /// + public string[] SupportedLanguageTags { get; private set; } + protected override void ReadContentFrom(BinaryReader reader) + { + //found in some fonts (eg tahoma) + + //Table Formats + //The metadata table begins with a header, structured as follows. + + //Metadata header: + //Type Name Description + //uint32 version Version number of the metadata table — set to 1. + //uint32 flags Flags — currently unused; set to 0. + //uint32 (reserved) Not used; should be set to 0. + //uint32 dataMapsCount The number of data maps in the table. + //DataMap dataMaps[dataMapsCount] Array of data map records. + + + long tableStartsAt = reader.BaseStream.Position;//*** + + uint version = reader.ReadUInt32(); + uint flags = reader.ReadUInt32(); + uint reserved = reader.ReadUInt32(); + uint dataMapsCount = reader.ReadUInt32(); +#if DEBUG + if (version != 1 || flags != 0) + { + throw new OpenFontNotSupportedException(); + } +#endif + + DataMapRecord[] dataMaps = new DataMapRecord[dataMapsCount]; + for (int i = 0; i < dataMaps.Length; ++i) + { + dataMaps[i] = new DataMapRecord(reader.ReadUInt32(), + reader.ReadUInt32(), + reader.ReadUInt32()); + } + + //Metadata tags identify the category of information provided and representation format used for a given metadata value. + //A registry of commonly - used tags is maintained, + //but private, vendor-determined tags can also be used. + + //Like other OpenType tags, + //metadata tags are four unsigned bytes that can equivalently be interpreted as a string of four ASCII characters. + //Metadata tags must begin with a letter (0x41 to 0x5A, 0x61 to 0x7A) and + //must use only letters, digits (0x30 to 0x39) or space (0x20). + //Space characters must only occur as trailing characters in tags that have fewer than four letters or digits. + + //Privately - defined axis tags must begin with an uppercase letter(0x41 to 0x5A), + //and must use only uppercase letters or digits. + //Registered axis tags must not use that pattern, but can be any other valid pattern. + + //The data for a given record may be either textual or binary. + //The representation format is specified for each tag. + //Depending on the tag, multiple records for a given tag or multiple, + //delimited values in a record may or may not be permitted, as specified for each tag. + //If only one record or value is permitted for a tag, + //then any instances after the first may be ignored. + + //translate data for each tags + //The following registered tags are defined or reserved at this time: + + + + //------------------------------------------------------------------------------------ + //IMPORTANT: + //Note: OpenType Layout script and language system tags are not the same as + //those used in BCP 47 and should not be referenced when creating or processing ScriptLangTags. + //------------------------------------------------------------------------------------ + //... In most cases, however, generic tags should be used, + //and it is anticipated that most tags used in 'dlng' and 'slng' metadata declarations will consist only of a script subtag. + //Language or other subtags can be included, however, and may be appropriate in some cases. + //Implementations must allow for ScriptLangTags that include additional subtags, + //but they may also choose to interpret only the script subtag and ignore other subtags. + + + for (int i = 0; i < dataMaps.Length; ++i) + { + DataMapRecord record = dataMaps[i]; + + switch (record.GetTagString()) + { +#if DEBUG + default: + System.Diagnostics.Debug.WriteLine("openfont-meta: unknown tag:" + record.GetTagString()); + break; +#endif + case "apple": //Reserved — used by Apple. + case "bild"://Reserved — used by Apple. + break; + case "dlng": + { + //The values for 'dlng' and 'slng' are comprised of a series of comma-separated ScriptLangTags, + //which are described in detail below. + //Spaces may follow the comma delimiters and are ignored. + //Each ScriptLangTag identifies a language or script. + + //A list of tags is interpreted to imply that all of the languages or scripts are included. + + //dlng Design languages Text, + //using only Basic Latin (ASCII) characters. + //Indicates languages and/or scripts for the user audiences that the font was primarily designed for. + + //Only one instance is used. + + //dlng Design languages Text, + //The 'dlng' value is used to indicate the languages or scripts of the primary user audiences for which the font was designed. + + //This value may be useful for selecting default font formatting based on content language, + //for presenting filtered font options based on user language preferences, + //or similar applications involving the language or script of content or user settings. + + if (DesignLanguageTags == null) + { + //Only one instance is used. + reader.BaseStream.Position = tableStartsAt + record.dataOffset; + DesignLanguageTags = ReadCommaSepData(reader.ReadBytes((int)record.dataLength)); + } + } + break; + case "slng": + { + //slng Supported languages Text, using only Basic Latin(ASCII) characters. + //Indicates languages and / or scripts that the font is declared to be capable of supporting. + + //Only one instance is used. + + //slng Supported languages Text + //The 'slng' value is used to declare languages or scripts that the font is capable of supported. + + //This value may be useful for font fallback mechanisms or other applications involving the language or + //script of content or user settings. + //Note: Implementations that use 'slng' values in a font may choose to ignore Unicode - range bits set in the OS/ 2 table. + + if (SupportedLanguageTags == null) + { //Only one instance is used. + reader.BaseStream.Position = tableStartsAt + record.dataOffset; + SupportedLanguageTags = ReadCommaSepData(reader.ReadBytes((int)record.dataLength)); + } + } + break; + } + } + } + static string[] ReadCommaSepData(byte[] data) + { + string[] tags = System.Text.Encoding.UTF8.GetString(data).Split(','); + for (int i = 0; i < tags.Length; ++i) + { + tags[i] = tags[i].Trim(); + +#if DEBUG + if (tags[i].Contains("-")) + { + + } +#endif + + } + return tags; + } + readonly struct DataMapRecord + { + public readonly uint tag; + public readonly uint dataOffset; + public readonly uint dataLength; + public DataMapRecord(uint tag, uint dataOffset, uint dataLength) + { + this.tag = tag; + this.dataOffset = dataOffset; + this.dataLength = dataLength; + } + public string GetTagString() => Utils.TagToString(tag); +#if DEBUG + public override string ToString() + { + return GetTagString() + ":" + dataOffset + "," + dataLength; + } +#endif + + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/STAT.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/STAT.cs new file mode 100644 index 00000000..da5e85cf --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/STAT.cs @@ -0,0 +1,409 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/stat + + //The style attributes table describes design attributes that + //distinguish font-style variants within a font family. + //It also provides associations between those attributes and + //name elements that may be used to present font options within application user interfaces. + + //**A style attributes table is required in all variable fonts. + + //The style attributes table is also recommended for all new, non-variable fonts, // + //especially if fonts have style attributes in axes other than weight, width, or slope. + + + /// + /// STAT — Style Attributes Table + /// + class STAT : TableEntry + { + + public const string _N = "STAT"; + public override string Name => _N; + // + protected override void ReadContentFrom(BinaryReader reader) + { + //Style Attributes Header + //The style attributes table, version 1.2, is organized as follows: + //Style attributes header: + //Type Name Description + //uint16 majorVersion Major version number of the style attributes table — set to 1. + //uint16 minorVersion Minor version number of the style attributes table — set to 2. + //uint16 designAxisSize The size in bytes of each axis record. + //uint16 designAxisCount The number of design axis records. + // In a font with an 'fvar' table, this value must be greater than or equal to the axisCount value in the 'fvar' table. + // In all fonts, must be greater than zero if axisValueCount is greater than zero. + //Offset32 designAxesOffset Offset in bytes from the beginning of the STAT table to the start of the design axes array. + // If designAxisCount is zero, set to zero; + // if designAxisCount is greater than zero, must be greater than zero. + //uint16 axisValueCount The number of axis value tables. + //Offset32 offsetToAxisValueOffsets Offset in bytes from the beginning of the STAT table to the start of the design axes value offsets array. + // If axisValueCount is zero, set to zero; + // if axisValueCount is greater than zero, must be greater than zero. + //uint16 elidedFallbackNameID Name ID used as fallback when projection of names into a particular font model produces a subfamily name containing only elidable elements. + + long beginPos = reader.BaseStream.Position; + // + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort designAxisSize = reader.ReadUInt16(); + ushort designAxisCount = reader.ReadUInt16(); + // + uint designAxesOffset = reader.ReadUInt32(); + ushort axisValueCount = reader.ReadUInt16(); + uint offsetToAxisValueOffsets = reader.ReadUInt32(); + // + ushort elidedFallbackNameID = (minorVersion != 0) ? reader.ReadUInt16() : (ushort)0; + //(elidedFallbackNameIDk, In version 1.0 of the style attributes table, the elidedFallbackNameId field was not included. Use of version 1.0 is deprecated) + + + + //The header is followed by the design axes and axis value offsets arrays, the location of which are provided by offset fields. + //Type Name Description + //AxisRecord designAxes[designAxisCount] The design-axes array. + //Offset16 axisValueOffsets[axisValueCount] Array of offsets to axis value tables, + // in bytes from the start of the axis value offsets array. + + //The designAxisSize field indicates the size of each axis record. + //Future minor-version updates of the STAT table may define compatible extensions + //to the axis record format with additional fields. + //**Implementations must use the designAxisSize designAxisSize field to determine the start of each record.** + + + //Axis Records + //The axis record provides information about a single design axis. + //AxisRecord: + //Type Name Description + //Tag axisTag A tag identifying the axis of design variation. + //uint16 axisNameID The name ID for entries in the 'name' table that provide a display string for this axis. + //uint16 axisOrdering A value that applications can use to determine primary sorting of face names, or for ordering of descriptors when composing family or face names. + + AxisRecord[] axisRecords = new AxisRecord[designAxisCount]; + for (int i = 0; i < designAxisCount; ++i) + { + var axisRecord = new AxisRecord(); + axisRecords[i] = axisRecord; + axisRecord.axisTagName = Utils.TagToString(reader.ReadUInt32()); //4 + axisRecord.axisNameId = reader.ReadUInt16(); //2 + axisRecord.axisOrdering = reader.ReadUInt16(); //2 + + + //*** + if (designAxisSize > 8) + { + //**Implementations must use the designAxisSize designAxisSize field to determine the start of each record.** + //Future minor-version updates of the STAT table may define compatible extensions + //to the axis record format with additional fields. + + + // so skip more ... + // + //at least there are 8 bytes + reader.BaseStream.Position += (designAxisSize - 8); + } + } + + + long axisValueOffsets_beginPos = reader.BaseStream.Position = beginPos + offsetToAxisValueOffsets; + ushort[] axisValueOffsets = Utils.ReadUInt16Array(reader, axisValueCount); // Array of offsets to axis value tables,in bytes from the start of the axis value offsets array. + + + //move to axis value record + + AxisValueTableBase[] axisValueTables = new AxisValueTableBase[axisValueCount]; + for (int i = 0; i < axisValueCount; ++i) + { + + //Axis Value Tables + //Axis value tables provide details regarding a specific style - attribute value on some specific axis of design variation, + //or a combination of design-variation axis values, and the relationship of those values to name elements. + //This information can be useful for presenting fonts in application user interfaces. + + // + //read each axis table + ushort offset = axisValueOffsets[i]; + reader.BaseStream.Position = axisValueOffsets_beginPos + offset; + + ushort format = reader.ReadUInt16();//common field of all axis value table + AxisValueTableBase axisValueTbl = null; + switch (format) + { + default: throw new OpenFontNotSupportedException(); + case 1: axisValueTbl = new AxisValueTableFmt1(); break; + case 2: axisValueTbl = new AxisValueTableFmt2(); break; + case 3: axisValueTbl = new AxisValueTableFmt3(); break; + case 4: axisValueTbl = new AxisValueTableFmt4(); break; + } + axisValueTbl.ReadContent(reader); + axisValueTables[i] = axisValueTbl; + } + + + //Each AxisValue record must have a different axisIndex value. + //The records can be in any order. + + //Flags + //The following axis value table flags are defined: + //Mask Name Description + //0x0001 OLDER_SIBLING_FONT_ATTRIBUTE If set, this axis value table provides axis value information that is applicable to other fonts within the same font family.This is used if the other fonts were released earlier and did not include information about values for some axis. If newer versions of the other fonts include the information themselves and are present, then this record is ignored. + //0x0002 ELIDABLE_AXIS_VALUE_NAME If set, it indicates that the axis value represents the “normal” value for the axis and may be omitted when composing name strings. + //0xFFFC Reserved Reserved for future use — set to zero. + + + //When the OlderSiblingFontAttribute flag is used, implementations may use the information provided to determine behaviour associated with a different font in the same family. + //If a previously - released family is extended with fonts for style variations from a new axis of design variation, + //then all of them should include a OlderSiblingFontAttribute table for the “normal” value of earlier fonts. + + //The values in the different fonts should match; if they do not, application behavior may be unpredictable. + + // Note: When the OlderSiblingFontAttribute flag is set, that axis value table is intended to provide default information about other fonts in the same family, + //but not about the font in which that axis value table is contained. + //The font should contain different axis value tables that do not use this flag to make declarations about itself. + + //The ElidableAxisValueName flag can be used to designate a “normal” value for an axis that should not normally appear in a face name. + //For example, the designer may prefer that face names not include “Normal” width or “Regular” weight. + //If this flag is set, applications are permitted to omit these descriptors from face names, though they may also include them in certain scenarios. + + //Note: Fonts should provide axis value tables for “normal” axis values even if they should not normally be reflected in face names. + + //Note: If a font or a variable-font instance is selected for which all axis values have the ElidableAxisValueName flag set, + //then applications may keep the name for the weight axis, if present, to use as a constructed subfamily name, with names for all other axis values omitted. + + //When the OlderSiblingFontAttribute flag is set, this will typically be providing information regarding the “normal” value on some newly-introduced axis. + //In this case, the ElidableAxisValueName flag may also be set, as desired.When applied to the earlier fonts, + //those likely would not have included any descriptors for the new axis, and so the effects of the ElidableAxisValueName flag are implicitly assumed. + + //If multiple axis value tables have the same axis index, then one of the following should be true: + + // The font is a variable font, and the axis is defined in the font variations table as a variation. + // The OlderSiblingFontAttribute flag is set in one of the records. + + //Two different fonts within a family may share certain style attributes in common. + //For example, Bold Condensed and Bold Semi Condensed fonts both have the same weight attribute, Bold. + //Axis value tables for particular values should be implemented consistently across a family. + //If they are not consistent, applications may exhibit unpredictable behaviors. + } + + + public class AxisRecord + { + public string axisTagName; + public ushort axisNameId; + public ushort axisOrdering; +#if DEBUG + public override string ToString() + { + return axisTagName; + } +#endif + } + + public abstract class AxisValueTableBase + { + public abstract int Format { get; } + + + /// + /// assume we have read format + /// + /// + public abstract void ReadContent(BinaryReader reader); + } + + + + public class AxisValueTableFmt1 : AxisValueTableBase + { + public override int Format => 1; + //Axis value table, format 1 + //Axis value table format 1 has the following structure. + + //AxisValueFormat1: + //Type Name Description + //uint16 format Format identifier — set to 1. + //uint16 axisIndex Zero - base index into the axis record array identifying the axis of design variation to which the axis value record applies. + // Must be less than designAxisCount. + //uint16 flags Flags — see below for details. + //uint16 valueNameID The name ID for entries in the 'name' table that provide a display string for this attribute value. + //Fixed value A numeric value for this attribute value. + + //A format 1 table is used simply to associate a specific axis value with a name. + + public ushort axisIndex; + public ushort flags; + public ushort valueNameId; + public float value; + public override void ReadContent(BinaryReader reader) + { + //at here, assume we have read format, + //Fixed => 32-bit signed fixed-point number (16.16) + axisIndex = reader.ReadUInt16(); + flags = reader.ReadUInt16(); + valueNameId = reader.ReadUInt16(); + value = reader.ReadFixed(); + } + } + public class AxisValueTableFmt2 : AxisValueTableBase + { + public override int Format => 2; + //Axis value table, format 2 + //Axis value table format 2 has the following structure. + + //AxisValueFormat2 + //Type Name Description + //uint16 format Format identifier — set to 2. + //uint16 axisIndex Zero - base index into the axis record array identifying the axis of design variation to which the axis value record applies. + // Must be less than designAxisCount. + //uint16 flags Flags — see below for details. + //uint16 valueNameID The name ID for entries in the 'name' table that provide a display string for this attribute value. + //Fixed nominalValue A nominal numeric value for this attribute value. + //Fixed rangeMinValue The minimum value for a range associated with the specified name ID. + //Fixed rangeMaxValue The maximum value for a range associated with the specified name ID. + + //A format 2 table can be used if a given name is associated with a particular axis value, but is also associated with a range of values.For example, + //in a family that supports optical size variations, “Subhead” may be used in relation to a range of sizes. + //The rangeMinValue and rangeMaxValue fields are used to define that range. + //In a variable font, a named instance has specific coordinates for each axis. + + //The nominalValue field allows some specific, nominal value to be associated with a name, + //to align with the named instances defined in the font variations table, + //while the rangeMinValue and rangeMaxValue fields allow the same name + //also to be associated with a range of axis values. + + //Some design axes may be open ended, having an effective minimum value of negative infinity, + //or an effective maximum value of positive infinity. + //To represent an effective minimum of negative infinity, set rangeMinValue to 0x80000000. + //To represent an effective maximum of positive infinity, set rangeMaxValue to 0x7FFFFFFF. + + //Two format 2 tables for a given axis should not have ranges with overlap greater than zero. + //If a font has two format 2 tables for a given axis, + //T1 and T2, with overlapping ranges, the following rules will apply: + + + //If the range of T1 overlaps the higher end of the range of T2 with a greater max value than T2(T1.rangeMaxValue > T2.rangeMaxValue and T1.rangeMinValue <= T2.rangeMaxValue), + //then T1 is used for all values within its range, including the portion that overlaps the range of T2. + + //If the range of T2 is contained entirely within the range of T1(T2.rangeMinValue >= T1.rangeMinValue and T2.rangeMaxValue <= T1.rangeMaValue), then T2 is ignored. + + //In the case of two tables with identical ranges for the same axis, it will be up to the implementation which is used and which is ignored. + + public ushort axisIndex; + public ushort flags; + public ushort valueNameId; + public float nominalValue; + public float rangeMinValue; + public float rangeMaxValue; + public override void ReadContent(BinaryReader reader) + { + axisIndex = reader.ReadUInt16(); + flags = reader.ReadUInt16(); + valueNameId = reader.ReadUInt16(); + nominalValue = reader.ReadFixed(); + rangeMinValue = reader.ReadFixed(); + rangeMaxValue = reader.ReadFixed(); + } + } + public class AxisValueTableFmt3 : AxisValueTableBase + { + public override int Format => 3; + // + //Axis value table, format 3 + //Axis value table format 3 has the following structure: + //AxisValueFormat3: + //Type Name Description + //uint16 format Format identifier — set to 3. + //uint16 axisIndex Zero-base index into the axis record array identifying the axis of design variation to which the axis value record applies. + // Must be less than designAxisCount. + //uint16 flags Flags — see below for details. + //uint16 valueNameID The name ID for entries in the 'name' table that provide a display string for this attribute value. + //Fixed value A numeric value for this attribute value. + //Fixed linkedValue The numeric value for a style-linked mapping from this value. + + + //A format 3 table can be used to indicate another value on the same axis that is to be treated as a style - linked counterpart to the current value. + //This is primarily intended for “bold” style linking on a weight axis. + //These mappings may be used in applications to determine which style within a family should be selected when a user selects a “bold” formatting option. + //A mapping is defined from a “non - bold” value to its “bold” counterpart. + //It is not necessary to provide a “bold” mapping for every weight value; + //mappings should be provided for lighter weights, + //but heavier weights(typically, semibold or above) would already be considered “bold” and would not require a “bold” mapping. + + //Note: Applications are not required to use these style - linked mappings when implementing text formatting user interfaces. + //This data can be provided in a font for the benefit of applications that choose to do so. + //If a given application does not apply such style mappings for the given axis, then the linkedValue field is ignored. + + public ushort axisIndex; + public ushort flags; + public ushort valueNameId; + public float value; + public float linkedValue; + + public override void ReadContent(BinaryReader reader) + { + axisIndex = reader.ReadUInt16(); + flags = reader.ReadUInt16(); + valueNameId = reader.ReadUInt16(); + value = reader.ReadFixed(); + linkedValue = reader.ReadFixed(); + } + } + + + public class AxisValueTableFmt4 : AxisValueTableBase + { + public override int Format => 4; + //Axis value table, format 4 + //Axis value table format 4 has the following structure: + + //AxisValueFormat4: + //Type Name Description + //uint16 format Format identifier — set to 4. + //uint16 axisCount The total number of axes contributing to this axis-values combination. + //uint16 flags Flags — see below for details. + //uint16 valueNameID The name ID for entries in the 'name' table that provide a display string for this combination of axis values. + //AxisValue axisValues[axisCount] Array of AxisValue records that provide the combination of axis values, one for each contributing axis. + + + public AxisValueRecord[] _axisValueRecords; + public ushort flags; + public ushort valueNameId; + public override void ReadContent(BinaryReader reader) + { + ushort axisCount = reader.ReadUInt16(); + flags = reader.ReadUInt16(); + valueNameId = reader.ReadUInt16(); + _axisValueRecords = new AxisValueRecord[axisCount]; + for (int i = 0; i < axisCount; ++i) + { + _axisValueRecords[i] = new AxisValueRecord( + reader.ReadUInt16(), + reader.ReadFixed()); + } + } + } + + public readonly struct AxisValueRecord + { + //The axisValues array uses AxisValue records, which have the following format. + //AxisValue record: + //Type Name Description + //uint16 axisIndex Zero - base index into the axis record array identifying the axis to which this value applies.Must be less than designAxisCount. + //Fixed value A numeric value for this attribute value. + public readonly ushort axisIndex; + public readonly float value; + public AxisValueRecord(ushort axisIndex, float value) + { + this.axisIndex = axisIndex; + this.value = value; + } + } + + } + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalDeviceMetrics.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalDeviceMetrics.cs new file mode 100644 index 00000000..3630fe8d --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalDeviceMetrics.cs @@ -0,0 +1,71 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; + +namespace Typography.OpenFont.Tables +{ + class VerticalDeviceMetrics : TableEntry + { + public const string _N = "VDMX"; + public override string Name => _N; + // + //https://docs.microsoft.com/en-us/typography/opentype/spec/vdmx + //VDMX - Vertical Device Metrics + //The VDMX table relates to OpenType™ fonts with TrueType outlines. + //Under Windows, the usWinAscent and usWinDescent values from the 'OS/2' table + //will be used to determine the maximum black height for a font at any given size. + //Windows calls this distance the Font Height. + //Because TrueType instructions can lead to Font Heights that differ from the actual scaled and rounded values, + //basing the Font Height strictly on the yMax and yMin can result in “lost pixels.” + //Windows will clip any pixels that extend above the yMax or below the yMin. + //In order to avoid grid fitting the entire font to determine the correct height, the VDMX table has been defined. + + //The VDMX table consists of a header followed by groupings of VDMX records: + Ratio[] _ratios; + protected override void ReadContentFrom(BinaryReader reader) + { + //uint16 version Version number (0 or 1). + //uint16 numRecs Number of VDMX groups present + //uint16 numRatios Number of aspect ratio groupings + //RatioRange ratRange[numRatios] Ratio ranges (see below for more info) + //Offset16 offset[numRatios] Offset from start of this table to the VDMX group for this ratio range. + //--- + //RatioRange Record: + //Type Name Description + //uint8 bCharSet Character set (see below). + //uint8 xRatio Value to use for x-Ratio + //uint8 yStartRatio Starting y-Ratio value. + //uint8 yEndRatio Ending y-Ratio value. + ushort version = reader.ReadUInt16(); + ushort numRecs = reader.ReadUInt16(); + ushort numRatios = reader.ReadUInt16(); + _ratios = new Ratio[numRatios]; + for (int i = 0; i < numRatios; ++i) + { + _ratios[i] = new Ratio( + reader.ReadByte(), + reader.ReadByte(), + reader.ReadByte(), + reader.ReadByte()); + } + ushort[] offsets = Utils.ReadUInt16Array(reader, numRatios); + //------ + //actual vdmx group + //TODO: implement this + } + readonly struct Ratio + { + public readonly byte charset; + public readonly byte xRatio; + public readonly byte yStartRatio; + public readonly byte yEndRatio; + public Ratio(byte charset, byte xRatio, byte yStartRatio, byte yEndRatio) + { + this.charset = charset; + this.xRatio = xRatio; + this.yStartRatio = yStartRatio; + this.yEndRatio = yEndRatio; + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetrics.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetrics.cs new file mode 100644 index 00000000..6371d681 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetrics.cs @@ -0,0 +1,98 @@ +//Apache2, 2017-present, WinterDev + +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + /// + /// vertical metrics table + /// + class VerticalMetrics : TableEntry + { + public const string _N = "vmtx"; + public override string Name => _N; + + // https://docs.microsoft.com/en-us/typography/opentype/spec/vmtx + // vmtx - Vertical Metrics Table + + //The vertical metrics table allows you to specify the vertical spacing for each glyph in a vertical font. + //This table consists of either one or two arrays that contain metric information(the advance heights and top sidebearings) + //for the vertical layout of each of the glyphs in the font. + //The vertical metrics coordinate system is shown below. + + + //Vertical Metrics Table Format + + //The overall structure of the vertical metrics table consists of two arrays shown below: + //the vMetrics array followed by an array of top side bearings. + // + //The top side bearing is measured relative to the top of the origin of glyphs, + //for vertical composition of ideographic glyphs. + // + //This table does not have a header, + //but does require that the number of glyphs included in the two arrays equals the total number of glyphs in the font. + // + //The number of entries in the vMetrics array is determined by the value of the numOfLongVerMetrics field of the vertical header table. + // + //The vMetrics array contains two values for each entry. + //These are the advance height and the top sidebearing for each glyph included in the array. + // + //In monospaced fonts, such as Courier or Kanji, all glyphs have the same advance height. + //If the font is monospaced, only one entry need be in the first array, but that one entry is required. + //The format of an entry in the vertical metrics array is given below. + + // + //Type Name Description + //uint16 advanceHeight The advance height of the glyph. Unsigned integer in FUnits + //int16 topSideBearing The top sidebearing of the glyph. Signed integer in FUnits. + + //The second array is optional and generally is used for a run of monospaced glyphs in the font. + //Only one such run is allowed per font, and it must be located at the end of the font. + //This array contains the top sidebearings of glyphs not represented in the first array, + //and all the glyphs in this array must have the same advance height as the last entry in the vMetrics array. + //All entries in this array are therefore monospaced. + // + //The number of entries in this array is calculated by subtracting the value of numOfLongVerMetrics from the number of glyphs in the font. + //The sum of glyphs represented in the first array plus the glyphs represented in the second array therefore equals the number of glyphs in the font. + //The format of the top sidebearing array is given below. + //Type Name Description + // int16 topSideBearing[] The top sidebearing of the glyph. Signed integer in FUnits. + + ushort _numOfLongVerMetrics; + AdvanceHeightAndTopSideBearing[] _advHeightAndTopSideBearings; + public VerticalMetrics(ushort numOfLongVerMetrics) + { + _numOfLongVerMetrics = numOfLongVerMetrics; + } + protected override void ReadContentFrom(BinaryReader reader) + { + _advHeightAndTopSideBearings = new AdvanceHeightAndTopSideBearing[_numOfLongVerMetrics]; + int m = 0; + for (int i = _numOfLongVerMetrics - 1; i >= 0; --i) + { + _advHeightAndTopSideBearings[m] = new AdvanceHeightAndTopSideBearing( + reader.ReadUInt16(), + reader.ReadInt16() + ); + } + } + + public readonly struct AdvanceHeightAndTopSideBearing + { + public readonly ushort advanceHeight; + public readonly short topSideBearing; + public AdvanceHeightAndTopSideBearing(ushort advanceHeight, short topSideBearing) + { + this.advanceHeight = advanceHeight; + this.topSideBearing = topSideBearing; + } +#if DEBUG + public override string ToString() + { + return advanceHeight + "," + topSideBearing; + } +#endif + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetricsHeader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetricsHeader.cs new file mode 100644 index 00000000..d3b1204a --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Others/VerticalMetricsHeader.cs @@ -0,0 +1,116 @@ +//Apache2, 2017-present, WinterDev +//https://docs.microsoft.com/en-us/typography/opentype/spec/vhea + +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + class VerticalHeader : TableEntry + { + public const string _N = "vhea"; + public override string Name => _N; + + //vhea — Vertical Header Tables + //The vertical header table(tag name: 'vhea') contains information needed for vertical fonts.The glyphs of vertical fonts are written either top to bottom or bottom to top. This table contains information that is general to the font as a whole. Information that pertains to specific glyphs is given in the vertical metrics table (tag name: 'vmtx') described separately.The formats of these tables are similar to those for horizontal metrics (hhea and hmtx). + //Data in the vertical header table must be consistent with data that appears in the vertical metrics table.The advance height and top sidebearing values in the vertical metrics table must correspond with the maximum advance height and minimum bottom sidebearing values in the vertical header table. + //See the section “OpenType CJK Font Guidelines“ for more information about constructing CJK (Chinese, Japanese, and Korean) fonts. + + // Table Format + + //The difference between version 1.0 and version 1.1 is the name and definition of the following fields: + //ascender becomes vertTypoAscender + //descender becomes vertTypoDescender + //lineGap becomes vertTypoLineGap + // + //Version 1.0 of the vertical header table format is as follows: + //Version 1.0 + //Type Name Description + //Fixed version Version number of the vertical header table; 0x00010000 for version 1.0 + //int16 ascent Distance in FUnits from the centerline to the previous line’s descent. + //int16 descent Distance in FUnits from the centerline to the next line’s ascent. + //int16 lineGap Reserved; set to 0 + //int16 advanceHeightMax The maximum advance height measurement -in FUnits found in the font.This value must be consistent with the entries in the vertical metrics table. + //int16 minTop_SideBearing The minimum top sidebearing measurement found in the font, in FUnits.This value must be consistent with the entries in the vertical metrics table. + //int16 minBottom_SideBearing The minimum bottom sidebearing measurement found in the font,in FUnits. + // This value must be consistent with the entries in the vertical metrics table. + //int16 yMaxExtent Defined as yMaxExtent=minTopSideBearing + (yMax - yMin) + //int16 caretSlopeRise The value of the caretSlopeRise field divided by the value of the caretSlopeRun Field determines the slope of the caret. A value of 0 for the rise and a value of 1 for the run specifies a horizontal caret. A value of 1 for the rise and a value of 0 for the run specifies a vertical caret. Intermediate values are desirable for fonts whose glyphs are oblique or italic.For a vertical font, a horizontal caret is best. + //int16 caretSlopeRun See the caretSlopeRise field. Value= 1 for nonslanted vertical fonts. + //int16 caretOffset The amount by which the highlight on a slanted glyph needs to be shifted away from the glyph in order to produce the best appearance. Set value equal to 0 for nonslanted fonts. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 metricDataFormat Set to 0. + //uint16 numOfLongVerMetrics Number of advance heights in the vertical metrics table. + //------------- + + // Version 1.1 of the vertical header table format is as follows: + //Version 1.1 + //Type Name Description + //Fixed version Version number of the vertical header table; 0x00011000 for version 1.1 + // Note the representation of a non-zero fractional part, in Fixed numbers. + //int16 vertTypoAscender The vertical typographic ascender for this font.It is the distance in FUnits from the ideographic em-box center baseline for the vertical axis to the right of the ideographic em-box and is usually set to (head.unitsPerEm)/2. For example, a font with an em of 1000 fUnits will set this field to 500. See the baseline section of the OpenType Tag Registry for a description of the ideographic em-box. + //int16 vertTypoDescender The vertical typographic descender for this font.It is the distance in FUnits from the ideographic em-box center baseline for the horizontal axis to the left of the ideographic em-box and is usually set to (head.unitsPerEm)/2. For example, a font with an em of 1000 fUnits will set this field to 500. + //int16 vertTypoLineGap The vertical typographic gap for this font.An application can determine the recommended line spacing for single spaced vertical text for an OpenType font by the following expression: ideo embox width + vhea.vertTypoLineGap + // + //int16 advanceHeightMax The maximum advance height measurement -in FUnits found in the font.This value must be consistent with the entries in the vertical metrics table. + //int16 minTop_SideBearing The minimum top sidebearing measurement found in the font, in FUnits.This value must be consistent with the entries in the vertical metrics table. + //int16 minBottom_SideBearing The minimum bottom sidebearing measurement found in the font,in FUnits. + // This value must be consistent with the entries in the vertical metrics table. + //int16 yMaxExtent Defined as yMaxExtent =minTopSideBearing + (yMax - yMin) + //int16 caretSlopeRise The value of the caretSlopeRise field divided by the value of the caretSlopeRun Field determines the slope of the caret.A value of 0 for the rise and a value of 1 for the run specifies a horizontal caret.A value of 1 for the rise and a value of 0 for the run specifies a vertical caret.Intermediate values are desirable for fonts whose glyphs are oblique or italic.For a vertical font, a horizontal caret is best. + //int16 caretSlopeRun See the caretSlopeRise field.Value = 1 for nonslanted vertical fonts. + //int16 caretOffset The amount by which the highlight on a slanted glyph needs to be shifted away from the glyph in order to produce the best appearance.Set value equal to 0 for nonslanted fonts. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 reserved Set to 0. + //int16 metricDataFormat Set to 0. + //uint16 numOfLongVerMetrics Number of advance heights in the vertical metrics table. + + + + + public byte VersionMajor { get; set; } + public byte VersionMinor { get; set; } + public short VertTypoAscender { get; set; } + public short VertTypoDescender { get; set; } + public short VertTypoLineGap { get; set; } + // + public short AdvanceHeightMax { get; set; } + public short MinTopSideBearing { get; set; } + public short MinBottomSideBearing { get; set; } + // + public short YMaxExtend { get; set; } + public short CaretSlopeRise { get; set; } + public short CaretSlopeRun { get; set; } + public short CaretOffset { get; set; } + public ushort NumOfLongVerMetrics { get; set; } + protected override void ReadContentFrom(BinaryReader reader) + { + uint version = reader.ReadUInt32(); + VersionMajor = (byte)(version >> 16); + VersionMinor = (byte)(version >> 8); + + VertTypoAscender = reader.ReadInt16(); + VertTypoDescender = reader.ReadInt16(); + VertTypoLineGap = reader.ReadInt16(); + // + AdvanceHeightMax = reader.ReadInt16(); + MinTopSideBearing = reader.ReadInt16(); + MinBottomSideBearing = reader.ReadInt16(); + // + YMaxExtend = reader.ReadInt16(); + CaretSlopeRise = reader.ReadInt16(); + CaretSlopeRun = reader.ReadInt16(); + CaretOffset = reader.ReadInt16(); + // + //skip 5 int16 => 4 reserve field + 1 metricDataFormat + reader.BaseStream.Position += (2 * (4 + 1)); //short = 2 byte, + // + NumOfLongVerMetrics = reader.ReadUInt16(); + } + + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Cvt_Programs.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Cvt_Programs.cs new file mode 100644 index 00000000..7087ceab --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Cvt_Programs.cs @@ -0,0 +1,54 @@ +//MIT, 2015-2016, Michael Popoloski, WinterDev + +using System.IO; +namespace Typography.OpenFont.Tables +{ + + class CvtTable : TableEntry + { + public const string _N = "cvt ";//need 4 chars//*** + public override string Name => _N; + + // + + /// + /// control value in font unit + /// + internal int[] _controlValues; + protected override void ReadContentFrom(BinaryReader reader) + { + int nelems = (int)(this.TableLength / sizeof(short)); + var results = new int[nelems]; + for (int i = 0; i < nelems; i++) + { + results[i] = reader.ReadInt16(); + } + _controlValues = results; + } + } + class PrepTable : TableEntry + { + public const string _N = "prep"; + public override string Name => _N; + // + + internal byte[] _programBuffer; + // + protected override void ReadContentFrom(BinaryReader reader) + { + _programBuffer = reader.ReadBytes((int)this.TableLength); + } + } + class FpgmTable : TableEntry + { + public const string _N = "fpgm"; + public override string Name => _N; + // + + internal byte[] _programBuffer; + protected override void ReadContentFrom(BinaryReader reader) + { + _programBuffer = reader.ReadBytes((int)this.TableLength); + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Gasp.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Gasp.cs new file mode 100644 index 00000000..2d972ad3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Gasp.cs @@ -0,0 +1,106 @@ +//Apache2, 2016-present, WinterDev +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + /// + /// Grid-fitting And Scan-conversion Procedure Table + /// + class Gasp : TableEntry + { + public const string _N = "gasp"; + public override string Name => _N; + // + //https://docs.microsoft.com/en-us/typography/opentype/spec/gasp + + + // This table contains information which describes the preferred rasterization techniques + //for the typeface when it is rendered on grayscale-capable devices. + //This table also has some use for monochrome devices, + //which may use the table to turn off hinting at very large or small sizes, to improve performance. + + //At very small sizes, + //the best appearance on grayscale devices can usually be achieved by rendering the glyphs + //in grayscale without using hints. + // + //At intermediate sizes, hinting and monochrome rendering will usually produce the best appearance. + // + //At large sizes, the combination of hinting and grayscale rendering will + //typically produce the best appearance. + + //If the 'gasp' table is not present in a typeface, + //the rasterizer may apply default rules to decide how to render the glyphs on grayscale devices. + + //The 'gasp' table consists of a header followed by groupings of 'gasp' records: + GaspRangeRecord[] _rangeRecords; + protected override void ReadContentFrom(BinaryReader reader) + { + + //Type Name Description + //USHORT version Version number (set to 1) + //USHORT numRanges Number of records to follow + //GASPRANGE gaspRange[numRanges] Sorted by ppem + + //Each GASPRANGE record looks like this: + //Type Name Description + //USHORT rangeMaxPPEM Upper limit of range, in PPEM + //USHORT rangeGaspBehavior Flags describing desired rasterizer behavior. + ushort version = reader.ReadUInt16(); + ushort numRanges = reader.ReadUInt16(); + _rangeRecords = new GaspRangeRecord[numRanges]; + for (int i = 0; i < numRanges; ++i) + { + _rangeRecords[i] = new GaspRangeRecord( + reader.ReadUInt16(), + (GaspRangeBehavior)reader.ReadUInt16()); + } + } + + [Flags] + enum GaspRangeBehavior : ushort + { + Neither = 0, + GASP_DOGRAY = 0x0002, + GASP_GRIDFIT = 0x0001, + GASP_DOGRAY_GASP_GRIDFIT = 0x0003, + GASP_SYMMETRIC_GRIDFIT = 0x0004, + GASP_SYMMETRIC_SMOOTHING = 0x0008, + GASP_SYMMETRIC_SMOOTHING_GASP_SYMMETRIC_GRIDFIT = 0x000C + } + readonly struct GaspRangeRecord + { + public readonly ushort rangeMaxPPEM; + public readonly GaspRangeBehavior rangeGaspBehavior; + public GaspRangeRecord(ushort rangeMaxPPEM, GaspRangeBehavior rangeGaspBehavior) + { + this.rangeMaxPPEM = rangeMaxPPEM; + this.rangeGaspBehavior = rangeGaspBehavior; + } + + // There are four flags for the rangeGaspBehavior flags: + //Flag Meaning + //GASP_DOGRAY Use grayscale rendering + //GASP_GRIDFIT Use gridfitting + //GASP_SYMMETRIC_SMOOTHING Use smoothing along multiple axes with ClearType® + //Only supported in version 1 gasp + //GASP_SYMMETRIC_GRIDFIT Use gridfitting with ClearType symmetric smoothing + //Only supported in version 1 gasp + + //The set of bit flags may be extended in the future. + //The first two bit flags operate independently of the following two bit flags. + //If font smoothing is enabled, then the first two bit flags are used. + //If ClearType is enabled, then the following two bit flags are used. The seven currently defined values of rangeGaspBehavior would have the following uses: + //Flag Value Meaning + + //GASP_DOGRAY 0x0002 small sizes, typically ppem<9 + //GASP_GRIDFIT 0x0001 medium sizes, typically 9<=ppem<=16 + //GASP_DOGRAY|GASP_GRIDFIT 0x0003 large sizes, typically ppem>16 + //(neither) 0x0000 optional for very large sizes, typically ppem>2048 + //GASP_SYMMETRIC_GRIDFIT 0x0004 typically always enabled + //GASP_SYMMETRIC_SMOOTHING 0x0008 larger screen sizes, typically ppem>15, most commonly used with the gridfit flag. + //GASP_SYMMETRIC_SMOOTHING| GASP_SYMMETRIC_GRIDFIT 0x000C larger screen sizes, typically ppem>15 + //neither 0x0000 optional for very large sizes, typically ppem>2048 + } + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Glyf.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Glyf.cs new file mode 100644 index 00000000..6dcb7d71 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/Glyf.cs @@ -0,0 +1,533 @@ +//Apache2, 2014-2016, Samuel Carlsson, WinterDev +using System; +using System.Collections.Generic; +using System.IO; +namespace Typography.OpenFont.Tables +{ + class Glyf : TableEntry + { + public const string _N = "glyf"; + public override string Name => _N; + // + Glyph[] _glyphs; + readonly GlyphLocations _glyphLocations; + + //-------------------- + //both ttf and cff + //we don't share EmptyGlyph between typefaces + internal readonly Glyph _emptyGlyph = new Glyph(new GlyphPointF[0], new ushort[0], Bounds.Zero, null, 0); + + public Glyf(GlyphLocations glyphLocations) + { + _glyphLocations = glyphLocations; + } + public Glyph[] Glyphs + { + get => _glyphs; + internal set => _glyphs = value; + } + protected override void ReadContentFrom(BinaryReader reader) + { + uint tableOffset = this.Header.Offset; + GlyphLocations locations = _glyphLocations; + int glyphCount = locations.GlyphCount; + _glyphs = new Glyph[glyphCount]; + + List compositeGlyphs = new List(); + + for (int i = 0; i < glyphCount; i++) + { + reader.BaseStream.Seek(tableOffset + locations.Offsets[i], SeekOrigin.Begin);//reset + uint length = locations.Offsets[i + 1] - locations.Offsets[i]; + if (length > 0) + { + //https://www.microsoft.com/typography/OTSPEC/glyf.htm + //header, + //Type Name Description + //SHORT numberOfContours If the number of contours is greater than or equal to zero, this is a single glyph; if negative, this is a composite glyph. + //SHORT xMin Minimum x for coordinate data. + //SHORT yMin Minimum y for coordinate data. + //SHORT xMax Maximum x for coordinate data. + //SHORT yMax Maximum y for coordinate data. + short contoursCount = reader.ReadInt16(); + if (contoursCount >= 0) + { + Bounds bounds = Utils.ReadBounds(reader); + _glyphs[i] = ReadSimpleGlyph(reader, contoursCount, bounds, (ushort)i); + } + else + { + //skip composite glyph, + //resolve later + compositeGlyphs.Add((ushort)i); + } + } + else + { + _glyphs[i] = _emptyGlyph; + } + } + + //-------------------------------- + //resolve composte glyphs + //-------------------------------- + + foreach (ushort glyphIndex in compositeGlyphs) + { + +#if DEBUG + //if (glyphIndex == 7) + //{ + //} +#endif + _glyphs[glyphIndex] = ReadCompositeGlyph(_glyphs, reader, tableOffset, glyphIndex); + + + } + } + + static bool HasFlag(SimpleGlyphFlag target, SimpleGlyphFlag test) + { + return (target & test) == test; + } + internal static bool HasFlag(CompositeGlyphFlags target, CompositeGlyphFlags test) + { + return (target & test) == test; + } + static SimpleGlyphFlag[] ReadFlags(BinaryReader input, int flagCount) + { + var result = new SimpleGlyphFlag[flagCount]; + int i = 0; + int repeatCount = 0; + var flag = (SimpleGlyphFlag)0; + while (i < flagCount) + { + if (repeatCount > 0) + { + repeatCount--; + } + else + { + flag = (SimpleGlyphFlag)input.ReadByte(); + if (HasFlag(flag, SimpleGlyphFlag.Repeat)) + { + repeatCount = input.ReadByte(); + } + } + result[i++] = flag; + } + return result; + } + + static short[] ReadCoordinates(BinaryReader input, int pointCount, SimpleGlyphFlag[] flags, SimpleGlyphFlag isByte, SimpleGlyphFlag signOrSame) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/glyf + //Note: In the glyf table, the position of a point is not stored in absolute terms but as a vector relative to the previous point. + //The delta-x and delta-y vectors represent these (often small) changes in position. + + //Each flag is a single bit. Their meanings are shown below. + //Bit Flags Description + //0 ON_CURVE_POINT If set, the point is on the curve; otherwise, it is off the curve. + //1 X_SHORT_VECTOR If set, the corresponding x-coordinate is 1 byte long. If not set, 2 bytes. + //2 Y_SHORT_VECTOR If set, the corresponding y-coordinate is 1 byte long. If not set, 2 bytes. + //3 REPEAT_FLAG If set, the next byte specifies the number of additional times this set of flags is to be repeated. + // In this way, the number of flags listed can be smaller than the number of points in a character. + + //4 X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR + // This flag has two meanings, depending on how the x-Short Vector flag is set. + // If x-Short Vector is set, this bit describes the sign of the value, + // with 1 equalling positive and 0 negative. + // If the x-Short Vector bit is not set and this bit is set, then the current x-coordinate is the same as the previous x-coordinate. + // If the x-Short Vector bit is not set and this bit is also not set, the current x-coordinate is a signed 16-bit delta vector. + + //5 Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR + // This flag has two meanings, + // depending on how the y-Short Vector flag is set. + // If y-Short Vector is set, this bit describes the sign of the value, + // with 1 equalling positive and 0 negative. + // If the y-Short Vector bit is not set and this bit is set, then the current y-coordinate is the same as the previous y-coordinate. + // If the y-Short Vector bit is not set and this bit is also not set, + // the current y-coordinate is a signed 16-bit delta vector. + //6 OVERLAP_SIMPLE This bit is reserved. Set it to zero. (not used in OpenType) + //7 Reserved This bit is reserved. Set it to zero. + + var xs = new short[pointCount]; + int x = 0; + for (int i = 0; i < pointCount; i++) + { + int dx; + if (HasFlag(flags[i], isByte)) + { + byte b = input.ReadByte(); + dx = HasFlag(flags[i], signOrSame) ? b : -b; + } + else + { + if (HasFlag(flags[i], signOrSame)) + { + dx = 0; + } + else + { + dx = input.ReadInt16(); + } + } + x += dx; + xs[i] = (short)x; // TODO: overflow? + } + return xs; + } + [Flags] + enum SimpleGlyphFlag : byte + { + OnCurve = 1, + XByte = 1 << 1, + YByte = 1 << 2, + Repeat = 1 << 3, + XSignOrSame = 1 << 4, + YSignOrSame = 1 << 5 + } + + static Glyph ReadSimpleGlyph(BinaryReader reader, int contourCount, Bounds bounds, ushort index) + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/glyf + //Simple Glyph Description + //This is the table information needed if numberOfContours is greater than zero, + //that is, a glyph is not a composite. + + //Type Name Description + //uint16 endPtsOfContours[numberOfContours] Array of last points of each contour; + //uint16 instructionLength Total number of bytes for instructions. + //uint8 instructions[instructionLength] Array of instructions for each glyph; + //uint8 flags[variable] Array of flags for each coordinate in outline; variable is the number of flags. + //uint8 or int16 xCoordinates[variable] First coordinates relative to (0,0); others are relative to previous point. + //uint8 or int16 yCoordinates[variable] First coordinates relative to (0,0); others are relative to previous point. + + ushort[] endPoints = Utils.ReadUInt16Array(reader, contourCount); + //------------------------------------------------------- + ushort instructionLen = reader.ReadUInt16(); + byte[] instructions = reader.ReadBytes(instructionLen); + //------------------------------------------------------- + // TODO: should this take the max points rather? + int pointCount = endPoints[contourCount - 1] + 1; // TODO: count can be zero? + SimpleGlyphFlag[] flags = ReadFlags(reader, pointCount); + short[] xs = ReadCoordinates(reader, pointCount, flags, SimpleGlyphFlag.XByte, SimpleGlyphFlag.XSignOrSame); + short[] ys = ReadCoordinates(reader, pointCount, flags, SimpleGlyphFlag.YByte, SimpleGlyphFlag.YSignOrSame); + + int n = xs.Length; + GlyphPointF[] glyphPoints = new GlyphPointF[n]; + for (int i = n - 1; i >= 0; --i) + { + glyphPoints[i] = new GlyphPointF(xs[i], ys[i], HasFlag(flags[i], SimpleGlyphFlag.OnCurve)); + } + //----------- + //lets build GlyphPoint set + //----------- + return new Glyph(glyphPoints, endPoints, bounds, instructions, index); + } + + + [Flags] + internal enum CompositeGlyphFlags : ushort + { + //These are the constants for the flags field: + //Bit Flags Description + //0 ARG_1_AND_2_ARE_WORDS If this is set, the arguments are words; otherwise, they are bytes. + //1 ARGS_ARE_XY_VALUES If this is set, the arguments are xy values; otherwise, they are points. + //2 ROUND_XY_TO_GRID For the xy values if the preceding is true. + //3 WE_HAVE_A_SCALE This indicates that there is a simple scale for the component. Otherwise, scale = 1.0. + //4 RESERVED This bit is reserved. Set it to 0. + //5 MORE_COMPONENTS Indicates at least one more glyph after this one. + //6 WE_HAVE_AN_X_AND_Y_SCALE The x direction will use a different scale from the y direction. + //7 WE_HAVE_A_TWO_BY_TWO There is a 2 by 2 transformation that will be used to scale the component. + //8 WE_HAVE_INSTRUCTIONS Following the last component are instructions for the composite character. + //9 USE_MY_METRICS If set, this forces the aw and lsb (and rsb) for the composite to be equal to those from this original glyph. This works for hinted and unhinted characters. + //10 OVERLAP_COMPOUND If set, the components of the compound glyph overlap. Use of this flag is not required in OpenType — that is, it is valid to have components overlap without having this flag set. It may affect behaviors in some platforms, however. (See Apple’s specification for details regarding behavior in Apple platforms.) + //11 SCALED_COMPONENT_OFFSET The composite is designed to have the component offset scaled. + //12 UNSCALED_COMPONENT_OFFSET The composite is designed not to have the component offset scaled. + + ARG_1_AND_2_ARE_WORDS = 1, + ARGS_ARE_XY_VALUES = 1 << 1, + ROUND_XY_TO_GRID = 1 << 2, + WE_HAVE_A_SCALE = 1 << 3, + RESERVED = 1 << 4, + MORE_COMPONENTS = 1 << 5, + WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6, + WE_HAVE_A_TWO_BY_TWO = 1 << 7, + WE_HAVE_INSTRUCTIONS = 1 << 8, + USE_MY_METRICS = 1 << 9, + OVERLAP_COMPOUND = 1 << 10, + SCALED_COMPONENT_OFFSET = 1 << 11, + UNSCALED_COMPONENT_OFFSET = 1 << 12 + } + + Glyph ReadCompositeGlyph(Glyph[] createdGlyphs, BinaryReader reader, uint tableOffset, ushort compositeGlyphIndex) + { + //------------------------------------------------------ + //https://www.microsoft.com/typography/OTSPEC/glyf.htm + //Composite Glyph Description + + //This is the table information needed for composite glyphs (numberOfContours is -1). + //A composite glyph starts with two USHORT values (“flags” and “glyphIndex,” i.e. the index of the first contour in this composite glyph); + //the data then varies according to “flags”). + //Type Name Description + //uint16 flags component flag + //uint16 glyphIndex glyph index of component + //VARIABLE argument1 x-offset for component or point number; type depends on bits 0 and 1 in component flags + //VARIABLE argument2 y-offset for component or point number; type depends on bits 0 and 1 in component flags + //--------- + //note: VARIABLE => may be uint8,int8,uint16 or int16 + //see more at https://fontforge.github.io/assets/old/Composites/index.html + //--------- + + //move to composite glyph position + reader.BaseStream.Seek(tableOffset + _glyphLocations.Offsets[compositeGlyphIndex], SeekOrigin.Begin);//reset + //------------------------ + short contoursCount = reader.ReadInt16(); // ignored + Bounds bounds = Utils.ReadBounds(reader); + + Glyph finalGlyph = null; + CompositeGlyphFlags flags; + +#if DEBUG + int ncount = 0; +#endif + do + { + flags = (CompositeGlyphFlags)reader.ReadUInt16(); + ushort glyphIndex = reader.ReadUInt16(); + if (createdGlyphs[glyphIndex] == null) + { + // This glyph is not read yet, resolve it first! + long storedOffset = reader.BaseStream.Position; + Glyph missingGlyph = ReadCompositeGlyph(createdGlyphs, reader, tableOffset, glyphIndex); + createdGlyphs[glyphIndex] = missingGlyph; + reader.BaseStream.Position = storedOffset; + } + Glyph newGlyph = Glyph.TtfOutlineGlyphClone(createdGlyphs[glyphIndex], compositeGlyphIndex); + + int arg1 = 0;//arg1, arg2 may be int8,uint8,int16,uint 16 + int arg2 = 0;//arg1, arg2 may be int8,uint8,int16,uint 16 + + if (HasFlag(flags, CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS)) + { + + //0x0002 ARGS_ARE_XY_VALUES Bit 1: If this is set, + //the arguments are **signed xy values** + //otherwise, they are unsigned point numbers. + if (HasFlag(flags, CompositeGlyphFlags.ARGS_ARE_XY_VALUES)) + { + //singed + arg1 = reader.ReadInt16(); + arg2 = reader.ReadInt16(); + } + else + { + //unsigned + arg1 = reader.ReadUInt16(); + arg2 = reader.ReadUInt16(); + } + } + else + { + //0x0002 ARGS_ARE_XY_VALUES Bit 1: If this is set, + //the arguments are **signed xy values** + //otherwise, they are unsigned point numbers. + if (HasFlag(flags, CompositeGlyphFlags.ARGS_ARE_XY_VALUES)) + { + //singed + arg1 = (sbyte)reader.ReadByte(); + arg2 = (sbyte)reader.ReadByte(); + } + else + { + //unsigned + arg1 = reader.ReadByte(); + arg2 = reader.ReadByte(); + } + } + + //----------------------------------------- + float xscale = 1; + float scale01 = 0; + float scale10 = 0; + float yscale = 1; + + bool useMatrix = false; + //----------------------------------------- + bool hasScale = false; + if (HasFlag(flags, CompositeGlyphFlags.WE_HAVE_A_SCALE)) + { + //If the bit WE_HAVE_A_SCALE is set, + //the scale value is read in 2.14 format-the value can be between -2 to almost +2. + //The glyph will be scaled by this value before grid-fitting. + + xscale = yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (HasFlag(flags, CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE)) + { + + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (HasFlag(flags, CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO)) + { + + //The bit WE_HAVE_A_TWO_BY_TWO allows for linear transformation of the X and Y coordinates by specifying a 2 × 2 matrix. + //This could be used for scaling and 90-degree*** rotations of the glyph components, for example. + + //2x2 matrix + + //The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value. + //For example, an i-circumflex (U+00EF) is often composed of the circumflex and a dotless-i. + //In order to force the composite to have the same metrics as the dotless-i, + //set USE_MY_METRICS for the dotless-i component of the composite. + //Without this bit, the rsb and lsb would be calculated from the hmtx entry for the composite + //(or would need to be explicitly set with TrueType instructions). + + //Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components. + useMatrix = true; + hasScale = true; + + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + scale01 = reader.ReadF2Dot14(); /* Format 2.14 */ + scale10 = reader.ReadF2Dot14();/* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + +#if DEBUG + //TODO: review here + if (HasFlag(flags, CompositeGlyphFlags.UNSCALED_COMPONENT_OFFSET)) + { + + + } + else + { + + + } + if (HasFlag(flags, CompositeGlyphFlags.USE_MY_METRICS)) + { + + } +#endif + } + + //Argument1 and argument2 can be either... + // x and y offsets to be added to the glyph(the ARGS_ARE_XY_VALUES flag is set), + //or + // two point numbers(the ARGS_ARE_XY_VALUES flag is **not** set) + + //When arguments 1 and 2 are an x and a y offset instead of points and the bit ROUND_XY_TO_GRID is set to 1, + //the values are rounded to those of the closest grid lines before they are added to the glyph. + //X and Y offsets are described in FUnits. + + + //-------------------------------------------------------------------- + if (HasFlag(flags, CompositeGlyphFlags.ARGS_ARE_XY_VALUES)) + { + + if (useMatrix) + { + //use this matrix + Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, scale01, scale10, yscale); + Glyph.TtfOffsetXY(newGlyph, (short)arg1, (short)arg2); + } + else + { + if (hasScale) + { + if (xscale == 1.0 && yscale == 1.0) + { + + } + else + { + Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, 0, 0, yscale); + } + Glyph.TtfOffsetXY(newGlyph, (short)arg1, (short)arg2); + } + else + { + if (HasFlag(flags, CompositeGlyphFlags.ROUND_XY_TO_GRID)) + { + //TODO: implement round xy to grid*** + //---------------------------- + } + //just offset*** + Glyph.TtfOffsetXY(newGlyph, (short)arg1, (short)arg2); + } + } + } + else + { + //two point numbers. + //the first point number indicates the point that is to be matched to the new glyph. + //The second number indicates the new glyph's “matched” point. + //Once a glyph is added,its point numbers begin directly after the last glyphs (endpoint of first glyph + 1) + + //TODO: implement this... + + } + + // + if (finalGlyph == null) + { + finalGlyph = newGlyph; + } + else + { + //merge + Glyph.TtfAppendGlyph(finalGlyph, newGlyph); + } + + +#if DEBUG + ncount++; +#endif + + } while (HasFlag(flags, CompositeGlyphFlags.MORE_COMPONENTS)); + // + if (HasFlag(flags, CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS)) + { + ushort numInstr = reader.ReadUInt16(); + byte[] insts = reader.ReadBytes(numInstr); + finalGlyph.GlyphInstructions = insts; + } + //F2DOT14 16-bit signed fixed number with the low 14 bits of fraction (2.14). + //Transformation Option + // + //The C pseudo-code fragment below shows how the composite glyph information is stored and parsed; definitions for “flags” bits follow this fragment: + // do { + // USHORT flags; + // USHORT glyphIndex; + // if ( flags & ARG_1_AND_2_ARE_WORDS) { + // (SHORT or FWord) argument1; + // (SHORT or FWord) argument2; + // } else { + // USHORT arg1and2; /* (arg1 << 8) | arg2 */ + // } + // if ( flags & WE_HAVE_A_SCALE ) { + // F2Dot14 scale; /* Format 2.14 */ + // } else if ( flags & WE_HAVE_AN_X_AND_Y_SCALE ) { + // F2Dot14 xscale; /* Format 2.14 */ + // F2Dot14 yscale; /* Format 2.14 */ + // } else if ( flags & WE_HAVE_A_TWO_BY_TWO ) { + // F2Dot14 xscale; /* Format 2.14 */ + // F2Dot14 scale01; /* Format 2.14 */ + // F2Dot14 scale10; /* Format 2.14 */ + // F2Dot14 yscale; /* Format 2.14 */ + // } + //} while ( flags & MORE_COMPONENTS ) + //if (flags & WE_HAVE_INSTR){ + // USHORT numInstr + // BYTE instr[numInstr] + //------------------------------------------------------------ + + + return finalGlyph ?? _emptyGlyph; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/GlyphLocations.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/GlyphLocations.cs new file mode 100644 index 00000000..3ad0b3be --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.TrueType/GlyphLocations.cs @@ -0,0 +1,82 @@ +//MIT, 2018-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev +//https://www.microsoft.com/typography/otspec/loca.htm + +using System.IO; +namespace Typography.OpenFont.Tables +{ + class GlyphLocations : TableEntry + { + public const string _N = "loca"; + public override string Name => _N; + + + // loca - Index to Location + + //The indexToLoc table stores the offsets to the locations of the glyphs in the font, + //relative to the beginning of the glyphData table. + //In order to compute the length of the last glyph element, + //there is an extra entry after the last valid index. + + //By definition, + //index zero points to the “missing character,” + //which is the character that appears if a character is not found in the font. + //The missing character is commonly represented by a blank box or a space. + //If the font does not contain an outline for the missing character, + //then the first and second offsets should have the same value. + //This also applies to any other characters without an outline, such as the space character. + //If a glyph has no outline, then loca[n] = loca [n+1]. + //In the particular case of the last glyph(s), loca[n] will be equal the length of the glyph data ('glyf') table. + //The offsets must be in ascending order with loca[n] <= loca[n+1]. + + //Most routines will look at the 'maxp' table to determine the number of glyphs in the font, but the value in the 'loca' table must agree. + + //There are two versions of this table, the short and the long. The version is specified in the indexToLocFormat entry in the 'head' table. + + uint[] _offsets; + public GlyphLocations(int glyphCount, bool isLongVersion) + { + _offsets = new uint[glyphCount + 1]; + this.IsLongVersion = isLongVersion; + } + public bool IsLongVersion { get; private set; } + public uint[] Offsets => _offsets; + public int GlyphCount => _offsets.Length - 1; + + protected override void ReadContentFrom(BinaryReader reader) + { + //Short version + //Type Name Description + //USHORT offsets[n] The actual local offset divided by 2 is stored. + //The value of n is numGlyphs + 1. + //The value for numGlyphs is found in the 'maxp' table. + //------------------------- + //Long version + //Type Name Description + //ULONG offsets[n] The actual local offset is stored. + //The value of n is numGlyphs + 1. The value for numGlyphs is found in the 'maxp' table. + + //Note that the local offsets should be long-aligned, i.e., multiples of 4. Offsets which are not long-aligned may seriously degrade performance of some processors. + + int glyphCount = GlyphCount; + int lim = glyphCount + 1; + _offsets = new uint[lim]; + if (IsLongVersion) + { + //long version + for (int i = 0; i < lim; i++) + { + _offsets[i] = reader.ReadUInt32(); + } + } + else + { + //short version + for (int i = 0; i < lim; i++) + { + _offsets[i] = (uint)(reader.ReadUInt16() << 1); // =*2 + } + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/AVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/AVar.cs new file mode 100644 index 00000000..6af3cff3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/AVar.cs @@ -0,0 +1,127 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/avar + + /// + /// avar — Axis Variations Table + /// + class AVar : TableEntry + { + public const string _N = "avar"; + public override string Name => _N; + + //The axis variations table('avar') is an optional table + //used in variable fonts that use OpenType Font Variations mechanisms. + //It can be used to modify aspects of how a design varies for different instances along a particular design-variation axis. + //Specifically, it allows modification of the coordinate normalization that is used when processing variation data for a particular variation instance. + + //... + + //The 'avar' table must be used in combination with a font variations('fvar') table and + //other required or optional tables used in variable fonts. + + SegmentMapRecord[] _axisSegmentMaps; + protected override void ReadContentFrom(BinaryReader reader) + { + + //The 'avar' table is comprised of a small header plus segment maps for each axis. + + //Axis variation table: + //Type Name Description + //uint16 majorVersion Major version number of the axis variations table — set to 1. + //uint16 minorVersion Minor version number of the axis variations table — set to 0. + //uint16 Permanently reserved; set to zero. + //uint16 axisCount The number of variation axes for this font. + // This must be the same number as axisCount in the 'fvar' table. + //SegmentMaps axisSegmentMaps[axisCount] The segment maps array—one segment map for each axis, + // in the order of axes specified in the 'fvar' table. + //-------------- + + //There must be one segment map for each axis defined in the 'fvar' table, + //and the segment maps for the different axes must be given in the order of axes specified in the 'fvar' table. + //The segment map for each axis is comprised of a list of axis - value mapping records. + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort reserved = reader.ReadUInt16(); + ushort axisCount = reader.ReadUInt16(); + + //Each axis value map record provides a single axis-value mapping correspondence. + _axisSegmentMaps = new SegmentMapRecord[axisCount]; + for (int i = 0; i < axisCount; ++i) + { + SegmentMapRecord segmentMap = new SegmentMapRecord(); + segmentMap.ReadContent(reader); + _axisSegmentMaps[i] = segmentMap; + } + + + + } + public class SegmentMapRecord + { + //SegmentMaps record: + //Type Name Description + //uint16 positionMapCount The number of correspondence pairs for this axis. + //AxisValueMap axisValueMaps[positionMapCount] The array of axis value map records for this axis. + public AxisValueMap[] axisValueMaps; + public void ReadContent(BinaryReader reader) + { + ushort positionMapCount = reader.ReadUInt16(); + axisValueMaps = new AxisValueMap[positionMapCount]; + for (int i = 0; i < positionMapCount; ++i) + { + axisValueMaps[i] = new AxisValueMap( + reader.ReadF2Dot14(), + reader.ReadF2Dot14() + ); + } + } + } + public readonly struct AxisValueMap + { + //AxisValueMap record: + //Type Name Description + //F2DOT14 fromCoordinate A normalized coordinate value obtained using default normalization. + //F2DOT14 toCoordinate The modified, normalized coordinate value. + + + //Axis value maps can be provided for any axis, + //but are required only if the normalization mapping for an axis is being modified. + //If the segment map for a given axis has any value maps, + //then it must include at least three value maps: -1 to - 1, 0 to 0, and 1 to 1. + //These value mappings are essential to the design of the variation mechanisms and + //are required even if no additional maps are specified for a given axis. + //If any of these is missing, then no modification to axis coordinate values will be made for that axis. + + + //All of the axis value map records for a given axis must have different fromCoordinate values, + //and axis value map records must be arranged in increasing order of the fromCoordinate value. + //If the fromCoordinate value of a record is less than or equal to the fromCoordinate value of a previous record in the array, + //then the given record may be ignored. + + //Also, for any given record except the first, + //the toCoordinate value must be greater than or equal to the toCoordinate value of the preceding record. + //This requirement ensures that there are no retrograde behaviors as the user-scale value range is traversed. + //If a toCoordinate value of a record is less than that of the previous record, then the given record may be ignored. + + public readonly float fromCoordinate; + public readonly float toCoordinate; + public AxisValueMap(float fromCoordinate, float toCoordinate) + { + this.fromCoordinate = fromCoordinate; + this.toCoordinate = toCoordinate; + } +#if DEBUG + public override string ToString() + { + return "from:" + fromCoordinate + " to:" + toCoordinate; + } +#endif + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/CVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/CVar.cs new file mode 100644 index 00000000..7aaf8b7a --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/CVar.cs @@ -0,0 +1,28 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/cvar + + /// + /// cvar — CVT Variations Table + /// + class CVar : TableEntry + { + public const string _N = "cvar"; + public override string Name => _N; + public CVar() + { + //The control value table (CVT) variations table is used in variable fonts to provide variation data for CVT values. + //For a general overview of OpenType Font Variations + + + } + protected override void ReadContentFrom(BinaryReader reader) + { + + } + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.ItemVariationStore.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.ItemVariationStore.cs new file mode 100644 index 00000000..545d87cb --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.ItemVariationStore.cs @@ -0,0 +1,119 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats + + //Item variation stores are used for most variation data other than that used for TrueType glyph outlines, + //including the variation data in MVAR, HVAR, VVAR, BASE and GDEF tables. + + //Note: For CFF2 glyph outlines, delta values are interleaved directly within the glyph outline description in the CFF2 table. + //The sets of regions which are associated with the delta sets are defined in an item variation store, + //contained as a subtable within the CFF2 table. + //See the CFF2 chapter for additional details. + + + //... + //The item variation store includes a variation region list and an array of item variation data subtables + + class ItemVariationStoreTable + { + + public VariationRegion[] variationRegions; + public void ReadContentFrom(BinaryReader reader) + { + + + //VariationRegionList: + //Type Name Description + //uint16 axisCount The number of variation axes for this font.This must be the same number as axisCount in the 'fvar' table. + //uint16 regionCount The number of variation region tables in the variation region list. + //VariationRegion variationRegions[regionCount] Array of variation regions. + + //The regions can be in any order. + //The regions are defined using an array of RegionAxisCoordinates records, one for each axis defined in the 'fvar' table: + + ushort axisCount = reader.ReadUInt16(); + ushort regionCount = reader.ReadUInt16(); + variationRegions = new VariationRegion[regionCount]; + for (int i = 0; i < regionCount; ++i) + { + var variationRegion = new VariationRegion(); + variationRegion.ReadContent(reader, axisCount); + variationRegions[i] = variationRegion; + } + } + } + class VariationRegion + { + //VariationRegion record: + //Type Name Description + //RegionAxisCoordinates regionAxes[axisCount] Array of region axis coordinates records, in the order of axes given in the 'fvar' table. + //Each RegionAxisCoordinates record provides coordinate values for a region along a single axis: + + public RegionAxisCoordinate[] regionAxes; + public void ReadContent(BinaryReader reader, int axisCount) + { + regionAxes = new RegionAxisCoordinate[axisCount]; + for (int i = 0; i < axisCount; ++i) + { + regionAxes[i] = new RegionAxisCoordinate( + reader.ReadF2Dot14(), //start + reader.ReadF2Dot14(), //peak + reader.ReadF2Dot14() //end + ); + } + } + } + readonly struct RegionAxisCoordinate + { + //RegionAxisCoordinates record: + //Type Name Description + //F2DOT14 startCoord The region start coordinate value for the current axis. + //F2DOT14 peakCoord The region peak coordinate value for the current axis. + //F2DOT14 endCoord The region end coordinate value for the current axis. + public readonly float startCoord; + public readonly float peakCoord; + public readonly float endCoord; + + public RegionAxisCoordinate(float startCoord, float peakCoord, float endCoord) + { + this.startCoord = startCoord; + this.peakCoord = peakCoord; + this.endCoord = endCoord; + + + //The three values must all be within the range - 1.0 to + 1.0. + //startCoord must be less than or equal to peakCoord, + //and peakCoord must be less than or equal to endCoord. + //The three values must be either all non-positive or all non-negative with one possible exception: + //if peakCoord is zero, then startCoord can be negative or 0 while endCoord can be positive or zero. + + //... + //Note: The following guidelines are used for setting the three values in different scenarios: + + //In the case of a non-intermediate region for which the given axis should factor into the scalar calculation for the region, + //either startCoord and peakCoord are set to a negative value(typically, -1.0) + //and endCoord is set to zero, or startCoord is set to zero and peakCoord and endCoord are set to a positive value(typically + 1.0). + + //In the case of an intermediate region for which the given axis should factor into the scalar calculation for the region, + //startCoord, peakCoord and endCoord are all set to non - positive values or are all set to non - negative values. + + //If the given axis should not factor into the scalar calculation for a region, + //then this is achieved by setting peakCoord to zero. + //In this case, startCoord can be any non - positive value, and endCoord can be any non - negative value. + //It is recommended either that all three be set to zero, or that startCoord be set to - 1.0 and endCoord be set to + 1.0. + + + } +#if DEBUG + public override string ToString() + { + return "start:" + startCoord + ",peak:" + peakCoord + ",end:" + endCoord; + } +#endif + } + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.TupleVariationStore.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.TupleVariationStore.cs new file mode 100644 index 00000000..c9e8f93a --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/Common.TupleVariationStore.cs @@ -0,0 +1,504 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats + //Tuple variation stores are used in the 'gvar' and 'cvar' tables, + //and organize sets of variation data into groupings, + //each of which is associated with a particular region of applicability within the variation space. + + //Within the 'gvar' table, there is a separate variation store for each glyph. + //Within the 'cvar' table, there is one variation store providing variations for all CVT values. + + + //There is a minor difference in the top-level structure of the store in these two contexts. + + //Within the 'cvar' table, + //it is the entire 'cvar' table that comprises the specific variation store format, + //with a header that begins with major/minor version fields. + + //The specific variation store format for glyph-specific data within the 'gvar' table is + //the **GlyphVariationData table(one per glyph ID)**,which does not include any version fields. + + //In other respects, the 'cvar' table and GlyphVariationData table formats are the same. + + //There is also a minor difference in certain data that can occur in a GlyphVariationData table versus a 'cvar' table. + //Differences between the 'gvar' and 'cvar' tables will be summarized later in this section + + + //In terms of logical information content, + //the GlyphVariationData and 'cvar' tables consist of a set of logical, tuple variation data tables, + //each for a particular region of the variation space. + //In physical layout, however, the logical tuple variation tables are divided + //into separate parts that get stored separately: a header portion, and a serialized-data portion. + + //In terms of overall structure, the GlyphVariationData table and the 'cvar' table each begin with a header, + //which is followed by serialized data. + //The header includes an array with all of the tuple variation headers. + //The serialized data include deltas and other data that will be explained below. + + //--------------------------------------------------- + // GlyphVariationData table / 'cvar' table + // ---- header ----------- + // (include tuple variation headers) + // + // ---- serialized data--- + // (adjustment deltas and other data) + // + //--------------------------------------------------- + //_fig: High-level organization of tuple variation stores_ + + //Tuple Records + + //The tuple variation store formats make reference to regions within the font’s variation space using tuple records. + //These references identify positions in terms of normalized coordinates, which use F2DOT14 values. + + //Tuple record(F2DOT14): + //Type Name Description + //F2DOT14 coordinates[axisCount] Coordinate array specifying a position within the font’s variation space. + // The number of elements must match the axisCount specified in the 'fvar' table. + + + + //---------------------------------------------------------------- + //Tuple Variation Store Header + + //The two variants of a tuple variation store header, + //the GlyphVariationData table header and the 'cvar' header, + //are only slightly different.The formats of each are as follows: + + + //GlyphVariationData header: + //Type Name Description + //uint16 tupleVariationCount A packed field. The high 4 bits are flags (see below), and the low 12 bits are the number of tuple variation tables for this glyph.The count can be any number between 1 and 4095. + //Offset16 dataOffset Offset from the start of the GlyphVariationData table to the serialized data. + //TupleVariationHeader tupleVariationHeaders[tupleVariationCount] Array of tuple variation headers. + + //'cvar' table header: + //Type Name Description + //uint16 majorVersion Major version number of the 'cvar' table — set to 1. + //uint16 minorVersion Minor version number of the 'cvar' table — set to 0. + //uint16 tupleVariationCount A packed field.The high 4 bits are flags (see below), and the low 12 bits are the number of tuple variation tables. The count can be any number between 1 and 4095. + //Offset16 dataOffset Offset from the start of the 'cvar' table to the serialized data. + //TupleVariationHeader tupleVariationHeaders[tupleVariationCount] Array of tuple variation headers. + + //The tupleVariationCount field contains a packed value that includes flags and the number of logical tuple variation tables — which is also the number of physical tuple variation headers.The format of the tupleVariationCount value is as follows: + //Mask Name Description + //0x8000 SHARED_POINT_NUMBERS Flag indicating that some or all tuple variation tables reference a shared set of “point” numbers. + // These shared numbers are represented as packed point number data at the start of the serialized data. + //0x7000 Reserved Reserved for future use — set to 0. + //0x0FFF COUNT_MASK Mask for the low bits to give the number of tuple variation tables. + + //If the sharedPointNumbers flag is set, + //then the serialized data following the header begins with packed “point” number data. + + //In the context of a GlyphVariationData table within the 'gvar' table, + //these identify outline point numbers for which deltas are explicitly provided. + + //In the context of the 'cvar' table, these are interpreted as CVT indices rather than point indices. + //The format of packed point number data is described below. + + //TupleVariationHeader + + //The GlyphVariationData and 'cvar' header formats + //include an array of tuple variation headers. + //The TupleVariationHeader format is as follows. + + class TupleVariationHeader + { + //TupleVariationHeader: + //Type Name Description + //uint16 variationDataSize The size in bytes of the serialized data for this tuple variation table. + //uint16 tupleIndex A packed field. + // The high 4 bits are flags(see below). + // The low 12 bits are an index into a shared tuple records array. + //Tuple peakTuple Peak tuple record for this tuple variation table — optional, determined by flags in the tupleIndex value. + // Note that this must always be included in the 'cvar' table. + //Tuple intermediateStartTuple Intermediate start tuple record for this tuple variation table — optional, determined by flags in the tupleIndex value. + //Tuple intermediateEndTuple Intermediate end tuple record for this tuple variation table — optional, determined by flags in the tupleIndex value. + + public ushort variableDataSize; + + public int flags; + public ushort indexToSharedTupleRecArray; + + public TupleRecord peakTuple; + public TupleRecord intermediateStartTuple; + public TupleRecord intermediateEndTuple; + + + public static TupleVariationHeader Read(BinaryReader reader, int axisCount) + { + TupleVariationHeader header = new TupleVariationHeader(); + + header.variableDataSize = reader.ReadUInt16(); + ushort tupleIndex = reader.ReadUInt16(); + int flags = (tupleIndex >> 12) & 0xF; //The high 4 bits are flags(see below). + header.flags = flags; //The high 4 bits are flags(see below). + header.indexToSharedTupleRecArray = (ushort)(tupleIndex & 0x0FFF); // The low 12 bits are an index into a shared tuple records array. + + + if ((flags & ((int)TupleIndexFormat.EMBEDDED_PEAK_TUPLE >> 12)) == ((int)TupleIndexFormat.EMBEDDED_PEAK_TUPLE >> 12)) + { + //TODO:... + header.peakTuple = TupleRecord.ReadTupleRecord(reader, axisCount); + } + if ((flags & ((int)TupleIndexFormat.INTERMEDIATE_REGION >> 12)) == ((int)TupleIndexFormat.INTERMEDIATE_REGION >> 12)) + { + //TODO:... + header.intermediateStartTuple = TupleRecord.ReadTupleRecord(reader, axisCount); + header.intermediateEndTuple = TupleRecord.ReadTupleRecord(reader, axisCount); + } + + return header; + } + + + + //--------- + public ushort[] PrivatePoints; + public short[] PackedDeltasXY; + + + //Note that the size of the TupleVariationHeader is variable, + //depending on whether peak or intermediate tuple records are included. (See below for more information.) + + //The variationDataSize value indicates the size of serialized data + //for the given tuple variation table that is contained in the serialized data. + //**It does not include the size of the TupleVariationHeader.** + + //Every tuple variation table has an associated peak tuple record. + //Most tuple variation tables use non-intermediate regions, + //and so require only the peak tuple record to define the region. + //- In the 'cvar' table, there is only one variation store, + // and so any given region will only need to be referenced once. + //- Within the 'gvar' table, however, there is a GlyphVariationData table for each glyph ID, + // and so any region may be referenced numerous times; + // in fact, most regions will be referenced within the GlyphVariationData tables for most glyphs. + //To provide a more efficient representation, + //the tuple variation store formats allow for an array of tuple records, + //stored outside the tuple variation store structures, + //that can be shared across many tuple variation stores. + //This is used only within the 'gvar' table; it is not needed or supported in the 'cvar' table. + //The formats alternately allow for a peak tuple record that is non-shared, + //specific to the given tuple variation table, to be embedded directly within a TupleVariationHeader. + //This is optional within the 'gvar' table, + //but required in the 'cvar' table, which does not use shared peak tuple records. + + //The tupleIndex field contains a packed value that includes flags and + //an index into a shared tuple records array(not used in the 'cvar' table). + //The format of the tupleIndex field is as follows. + } + + [Flags] + enum TupleIndexFormat + { + //tupleIndex format: + //Mask Name Description + //0x8000 EMBEDDED_PEAK_TUPLE Flag indicating that this tuple variation header includes an embedded peak tuple record, + // immediately after the tupleIndex field. + // If set, the low 12 bits of the tupleIndex value are ignored. + // Note that this must always be set within the 'cvar' table. + //0x4000 INTERMEDIATE_REGION Flag indicating that this tuple variation table applies to an intermediate region within the variation space. + // If set, the header includes the two intermediate - region, start and end tuple records, + // immediately after the peak tuple record(if present). + //0x2000 PRIVATE_POINT_NUMBERS Flag indicating that the serialized data for this tuple variation table includes packed “point” number data. + // If set, this tuple variation table uses that number data; + // if clear, this tuple variation table uses shared number data found at the start of the serialized data + // for this glyph variation data or 'cvar' table. + //0x1000 Reserved Reserved for future use — set to 0. + //0x0FFF TUPLE_INDEX_MASK Mask for the low 12 bits to give the shared tuple records index. + + EMBEDDED_PEAK_TUPLE = 0x8000, + INTERMEDIATE_REGION = 0x4000, + PRIVATE_POINT_NUMBERS = 0x2000, + Reserved = 0x1000, + TUPLE_INDEX_MASK = 0x0FFF + + + //Note that the intermediateRegion flag is independent of the embeddedPeakTuple flag or + //the shared tuple records index. + //Every tuple variation table has a peak n-tuple indicated either by an embedded tuple record (always true in the 'cvar' table) or + //by an index into a shared tuple records array (only in the 'gvar' table). + //An intermediate-region tuple variation table additionally has start and end n-tuples that also get used in the interpolation process; + //these are always represented using embedded tuple records. + + //Also note that the privatePointNumbers flag is independent of the sharedPointNumbers flag in the tupleVariationCount field of + //the GlyphVariationData or 'cvar' header. + //A GlyphVariationData or 'cvar' table may have shared point number data used by multiple tuple variation tables, + //but any given tuple variation table may have private point number data that it uses instead. + + //As noted, the size of tuple variation headers is variable. The next TupleVariationHeader can be calculated as follows: + + // const TupleVariationHeader* + // NextHeader( const TupleVariationHeader* currentHeader, int axisCount ) + // { + // int bump = 2 * sizeof( uint16 ); + // int tupleIndex = currentHeader->tupleIndex; + // if ( tupleIndex & embeddedPeakTuple ) + // bump += axisCount * sizeof( F2DOT14 ); + // if ( tupleIndex & intermediateRegion ) + // bump += 2 * axisCount * sizeof( F2DOT14 ); + // return (const TupleVariationHeader*)((char*)currentHeader + bump); + // } + } + + readonly struct TupleRecord + { + public readonly float[] coords; + public TupleRecord(float[] coords) => this.coords = coords; +#if DEBUG + public override string ToString() => coords?.Length.ToString() ?? "0"; +#endif + public static TupleRecord ReadTupleRecord(BinaryReader reader, int count) + { + float[] coords = new float[count]; + for (int n = 0; n < coords.Length; ++n) + { + coords[n] = reader.ReadF2Dot14(); + } + return new TupleRecord(coords); + } + } + + //---------------------------------------------------------------- + //Serialized Data + + //After the GlyphVariationData or 'cvar' header(including the TupleVariationHeader array) is + //a block of serialized data.The offset to this block of data is provided in the header. + + + //The serialized data block begins with shared “point” number data, + //followed by the variation data for the tuple variation tables. + //The shared point number data is optional: + //it is present if the corresponding flag is set in the tupleVariationCount field of the header. + //If present, the shared number data is represented as packed point numbers, described below. + + //--------------------------------------------------- + // Serialized data block + // ---- Shared "point" numbers ----------- + // (optional per flag in the header) + // + // ---- Per-tuple-variation data--- + // + //--------------------------------------------------- + //_fig: Organization of serialized data_ + + //The remaining data contains runs of data specific to individual tuple variation tables, + //in order of the tuple variation headers. + //Each TupleVariationHeader indicates the data size for the corresponding run of data for that tuple variation table. + + //The per-tuple-variation-table data optionally begins with private “point” numbers, + //present if the privatePointNumbers flag is set in the tupleIndex field of the TupleVariationHeader. + //Private point numbers are represented as packed point numbers, described below. + + //After the private point number data(if present), + //the tuple variation data will include packed delta data. + //The format for packed deltas is given below. + //- Within the 'gvar' table, + // there are packed deltas for X coordinates, followed by packed deltas for Y coordinates. + //--------------------------------------------------- + // Per-tuple-variation data (gvar) + // ---- Private point numbers ----------- + // (optional per flag in tupleVariationHeader) + // + // ---- X coordinate packed deltas--- + // + // ---- Y coordinate packed deltas--- + //--------------------------------------------------- + //_fig: Organization 'gvar' per-tuple variation data_ + + + //- Within the 'cvar' table, there is one set of packed deltas + //--------------------------------------------------- + // Per-tuple-variation data (gvar) + // ---- Private point numbers ----------- + // (optional per flag in tupleVariationHeader) + // + // ---- X coordinate packed deltas--- + // + // ---- Y coordinate packed deltas--- + //--------------------------------------------------- + //_fig: Organization 'cvar' per-tuple variation data_ + + + //The data size indicated in the TupleVariationHeader includes the size of the private point number data, + //if present, plus the size of the packed deltas. + + + + //--------------------------------------------------- + //Packed “Point” Numbers + + //Tuple variation data specify deltas to be applied to specific items: + //X and Y coordinates for glyph outline points within the 'gvar' table, and CVT values in the 'cvar' table. + + //For a given glyph, deltas may be provided for any or all of a glyph’s points, + //including “phantom” points generated within the rasterizer that represent glyph side bearing points. + //(See the chapter Instructing TrueType Glyphs for more background on phantom points.) + + //Similarly, within the 'cvar' table, deltas may be provided for any or all CVTs. + //The set of glyph points or CVTs for which deltas are provided is specified by packed point numbers. + + //**Note: If a glyph is a composite glyph, + //then “point” numbers are component indices for the components that make up the composite glyph. + //See the 'gvar' table chapter for complete details. + + //Likewise, in the context of the 'cvar' table, “point” numbers are indices for CVT entries. + + + //Note: Within the 'gvar' table, + //if deltas are not provided explicitly for some points, + // then inferred delta values may need to be calculated — see the 'gvar' table chapter for details. + //This does not apply to the 'cvar' table, however: + // if deltas are not provided for some CVT values, + // then no adjustments are made to those CVTs in connection with the particular tuple variation table. + + //Packed point numbers are stored as a count followed by one or more runs of point number data. + + + //The count may be stored in one or two bytes. + //After reading the first byte, the need for a second byte can be determined. + //The count bytes are processed as follows: + + // If the first byte is 0, then ... + // a second count byte is not used. + // This value has a special meaning: + // the tuple variation data provides deltas for all glyph points (including the “phantom” points), or for all CVTs. + + // If the first byte is non-zero and the high bit is clear (value is 1 to 127), then ... + // a second count byte is **not used**. + // The point count is equal to the value of the first byte. + + // If the high bit of the first byte is set, then ... + // a second byte is used. + // The count is read from interpreting the two bytes as a big-endian uint16 value with the high-order bit masked out. + + + //Thus, if the count fits in 7 bits, + //it is stored in a single byte, + //with the value 0 having a special interpretation. + + //If the count does not fit in 7 bits, + //then the count is stored in the first two bytes with the high bit of the first byte set as a flag that is not part of the count — the count uses 15 bits. + + //For example, a count of 0x00 indicates that deltas are provided for all point numbers / all CVTs, + //with no additional point number data required; + //a count of 0x32 indicates that there are a total of 50 point numbers specified; + //a count of 0x81 0x22 indicates that there are a total of 290 (= 0x0122) point numbers specified. + + + //Point number data runs follow after the count. + //Each data run begins with a control byte that specifies + //the number of point numbers defined in the run, + //and a flag bit indicating the format of the run data. + //The control byte’s high bit specifies whether the run is represented in 8-bit or 16-bit values. + //The low 7 bits specify the number of elements in the run minus 1. + //The format of the control byte is as follows: + + //Mask Name Description + //0x80 POINTS_ARE_WORDS Flag indicating the data type used for point numbers in this run. + // If set, the point numbers are stored as unsigned 16-bit values (uint16); + // if clear, the point numbers are stored as unsigned bytes (uint8). + //0x7F POINT_RUN_COUNT_MASK Mask for the low 7 bits of the control byte to give the number of point number elements, minus 1. + + + //For example, a control byte of 0x02 indicates that the run has three elements represented as uint8 values; + //a control byte of 0xD4 indicates that the run has 0x54 + 1 = 85 elements represented as uint16 values. + + + //In the first point run,.. + // the first point number is represented directly (that is, as a difference from zero). + // Each subsequent point number in that run is stored as the difference between it and the previous point number. + //In subsequent runs,... + // all elements, including the first, represent a difference from the last point number. + + //Since the values in the packed data are all unsigned, + //point numbers will be given in increasing order. + //Since the packed representation can include zero values, + //it is possible for a given point number to be repeated in the derived point number list. + //In that case, there will be multiple delta values in the deltas data associated with that point number. + //All of these deltas must be applied cumulatively to the given point. + + + //Packed Deltas + + //Tuple variation data specify deltas to be applied to glyph point coordinates or to CVT values. + //As in the case of point number data, deltas are stored in a packed format. + + //Packed delta data does not include the total number of delta values within the data. + //Logically, there are deltas for every point number or CVT index specified in the point-number data. + //Thus, the count of logical deltas is equal to the count of point numbers specified for that tuple variation table. + //But since the deltas are represented in a packed format, + //the actual count of stored values is typically less than the logical count. + //The data is read until the expected logic count of deltas is obtained. + + // Note: In the 'gvar' table, + // there will be two logical deltas for each point number: + // one that applies to the X coordinate, and one that applies to the Y coordinate. + // Therefore, the total logical delta count is two times the point number count. + // The packed deltas are arranged with all of the deltas for X coordinates first, followed by the deltas for Y coordinates. + + //Packed deltas are stored as a series of runs. + //Each delta run consists of a control byte followed by the actual delta values of that run. + //The control byte is a packed value with flags in the high two bits and a count in the low six bits. + + //The flags specify the data size of the delta values in the run. + //The format of the control byte is as follows: + + //Mask Name Description + //0x80 DELTAS_ARE_ZERO Flag indicating that this run contains no data (no explicit delta values are stored), and that all of the deltas for this run are zero. + //0x40 DELTAS_ARE_WORDS Flag indicating the data type for delta values in the run. If set, the run contains 16-bit signed deltas (int16); if clear, the run contains 8-bit signed deltas (int8). + //0x3F DELTA_RUN_COUNT_MASK Mask for the low 6 bits to provide the number of delta values in the run, minus one. + + //... + //... + + + + //------------------------------------------------- + //Differences Between 'gvar' and 'cvar' Tables + + //The following is a summary of key differences between tuple variation stores in the 'gvar' and 'cvar' tables. + + //- The 'gvar' table is a parent table for tuple variation stores, + // and contains one tuple variation store(the glyph variation data table) for each glyph ID. + // In contrast, the entire 'cvar' table is comprised of a single, + // slightly-extended(with version fields) tuple variation store. + + //- Because the 'gvar' table contains multiple tuple variation stores, + // sharing of data between tuple variation stores is possible, + // and is used for shared tuple records. + // Because the 'cvar' table has a single tuple variation store, no possibility of shared data arises. + + //- The tupleIndex field of TupleVariationHeader structures within a tuple variation store includes a flag + // that indicates whether the structure instance includes an embedded peak tuple record. + // In the 'gvar' table, this is optional.In the 'cvar' table, a peak tuple record is mandatory. + + //- The serialized data includes packed “point” numbers. + // In the 'gvar' table, these refer to glyph contour point numbers or, + // in the case of a composite glyph, to component indices. + // In the context of the 'cvar' table, these are indices for CVT entries. + + //- In the 'gvar' table, + // point numbers cover the points or components defined in a 'glyf' entry plus four additional “phantom” points that + // represent the glyph’s horizontal and vertical advance and side bearings. + // (See the chapter, Instructing TrueType Glyphs for more background on phantom points.) + // The last four point numbers for any glyph, including composite glyphs, are for the phantom points. + + //- In the 'gvar' table, + // if deltas are not provided for some points and the point indices are not represented in the point number data, + // then interpolated deltas for those points will in some cases be inferred. + // This is not done in the 'cvar' table, however. + + //- In the 'gvar' table, + // the serialized data for a given region has two logical deltas for each point number: + // one for the X coordinate, and one for the Y coordinate. + // Hence the total number of deltas is twice the count of control points. + // In the 'cvar' table, however, there is only one delta for each point number. + + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/FVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/FVar.cs new file mode 100644 index 00000000..18edce0d --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/FVar.cs @@ -0,0 +1,200 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/fvar + //'fvar' Header + //The format of the font variations table header is as follows. + + //Note: The 'fvar' table describes a font’s variation space, + //and other variation tables provide variation data to describe + //how different data items are varied across the font’s variation space + + /// + /// fvar font variations + /// + class FVar : TableEntry + { + public const string _N = "fvar"; + public override string Name => _N; + + + public VariableAxisRecord[] variableAxisRecords; + public InstanceRecord[] instanceRecords; + + // + protected override void ReadContentFrom(BinaryReader reader) + { + //Font variations header: + + //Type Name Description + //uint16 majorVersion Major version number of the font variations table — set to 1. + //uint16 minorVersion Minor version number of the font variations table — set to 0. + //Offset16 axesArrayOffset Offset in bytes from the beginning of the table to the start of the VariationAxisRecord array. + //uint16 (reserved) This field is permanently reserved.Set to 2. + //uint16 axisCount The number of variation axes in the font (the number of records in the axes array). + //uint16 axisSize The size in bytes of each VariationAxisRecord — set to 20 (0x0014) for this version. + //uint16 instanceCount The number of named instances defined in the font (the number of records in the instances array). + //uint16 instanceSize The size in bytes of each InstanceRecord — set to either axisCount * sizeof(Fixed) + 4, + // or to axisCount * sizeof(Fixed) + 6. + + + long beginAt = reader.BaseStream.Position; + //header: + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort axesArrayOffset = reader.ReadUInt16(); + ushort reserved = reader.ReadUInt16();//set to 2 + ushort axisCount = reader.ReadUInt16(); + ushort axisSize = reader.ReadUInt16(); + ushort instanceCount = reader.ReadUInt16(); + ushort instanceSize = reader.ReadUInt16(); + + + //The header is followed by axes and instances arrays. + //The location of the axes array is specified in the axesArrayOffset field; + //the instances array directly follows the axes array. + //Type Name Description + //VariationAxisRecord axes[axisCount] The variation axis array. + //InstanceRecord instances[instanceCount] The named instance array. + + //Note: The axisSize and instanceSize fields indicate + //the size of the VariationAxisRecord and InstanceRecord structures. + + //In this version of the 'fvar' table, the InstanceRecord structure has an optional field, + //and so two different size formulations are possible. + + //Future minor-version updates of the 'fvar' table may define compatible extensions to either formats. + + //***Implementations must use the axisSize and instanceSize fields to determine the start of each record.*** + + //The set of axes that make up the font’s variation space are defined by an array of variation axis records. + //The number of records, and the number of axes, is determined by the axisCount field. + //A functional variable font must have an axisCount value that is greater than zero. + + //If axisCount is zero, then the font is not functional as a variable font, *** + //and must be treated as a non-variable font; *** + //any variation-specific tables or data is ignored. + + variableAxisRecords = new VariableAxisRecord[axisCount]; + + for (int i = 0; i < axisCount; ++i) + { + long pos = reader.BaseStream.Position; + VariableAxisRecord varAxisRecord = new VariableAxisRecord(); + varAxisRecord.ReadContent(reader); + variableAxisRecords[i] = varAxisRecord; + if (reader.BaseStream.Position != pos + axisSize) + { + //***Implementations must use the axisSize and instanceSize fields to determine the start of each record.*** + reader.BaseStream.Position = pos + axisSize; + } + } + + instanceRecords = new InstanceRecord[instanceCount]; + + for (int i = 0; i < instanceCount; ++i) + { + long pos = reader.BaseStream.Position; + + InstanceRecord instanecRec = new InstanceRecord(); + instanecRec.ReadContent(reader, axisCount, instanceSize); + + if (reader.BaseStream.Position != pos + instanceSize) + { + //***Implementations must use the axisSize and instanceSize fields to determine the start of each record.*** + reader.BaseStream.Position = pos + instanceSize; + } + } + + } + + public class VariableAxisRecord + { + //VariationAxisRecord + + //The format of the variation axis record is as follows: + + //VariationAxisRecord + //Type Name Description + //Tag axisTag Tag identifying the design variation for the axis. + //Fixed minValue The minimum coordinate value for the axis. + //Fixed defaultValue The default coordinate value for the axis. + //Fixed maxValue The maximum coordinate value for the axis. + //uint16 flags Axis qualifiers — see details below. + //uint16 axisNameID The name ID for entries in the 'name' table that provide a display name for this axis. + + + public string axisTag; + public float minValue; + public float defaultValue; + public float maxValue; + public ushort flags; + public ushort axisNameID; + public void ReadContent(BinaryReader reader) + { + axisTag = Utils.TagToString(reader.ReadUInt32());//4 + minValue = reader.ReadFixed();//4 + defaultValue = reader.ReadFixed();//4 + maxValue = reader.ReadFixed();//4 + flags = reader.ReadUInt16();//2 + axisNameID = reader.ReadUInt16();//2 + // + + } + } + + public class InstanceRecord + { + //InstanceRecord + + //The instance record format includes an array of n-tuple coordinate arrays + //that define position within the font’s variation space. + //The n-tuple array has the following format: + + //Tuple Record(Fixed): + //Type Name Description + //Fixed coordinates[axisCount] Coordinate array specifying a position within the font’s variation space. + + //The format of the instance record is as follows. + + //InstanceRecord: + //Type Name Description + //uint16 subfamilyNameID The name ID for entries in the 'name' table that provide subfamily names for this instance. + //uint16 flags Reserved for future use — set to 0. + //Tuple coordinates The coordinates array for this instance. + //uint16 postScriptNameID Optional.The name ID for entries in the 'name' table that provide PostScript names for this instance. + + public ushort subfamilyNameID;//point to name table, will be resolved later + public ushort flags; + public TupleRecord coordinates; + public ushort postScriptNameID;//point to name table, will be resolved later + + public void ReadContent(BinaryReader reader, int axisCount, int instanceRecordSize) + { + long expectedEndPos = reader.BaseStream.Position + instanceRecordSize; + subfamilyNameID = reader.ReadUInt16(); + flags = reader.ReadUInt16(); + float[] coords = new float[axisCount]; + for (int i = 0; i < axisCount; ++i) + { + coords[i] = reader.ReadFixed(); + } + coordinates = new TupleRecord(coords); + + if (reader.BaseStream.Position < expectedEndPos) + { + //optional field + postScriptNameID = reader.ReadUInt16(); + } + + } + } + + } +} + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/GVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/GVar.cs new file mode 100644 index 00000000..263e6de8 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/GVar.cs @@ -0,0 +1,415 @@ +//MIT, 2019-present, WinterDev +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/gvar + + + class GlyphVariableData + { + public List _sharedPoints; + public TupleVariationHeader[] tupleHeaders; + + } + class GVar : TableEntry + { + public const string _N = "gvar"; + public override string Name => _N; + + public ushort axisCount; + internal TupleRecord[] _sharedTuples; + + internal GlyphVariableData[] _glyphVarDataArr; //TODO: lazy load ! + + public GVar() + { + + } + // + protected override void ReadContentFrom(BinaryReader reader) + { + //'gvar' header + + //The glyph variations table header format is as follows: + + //'gvar' header: + //Type Name Description + //uint16 majorVersion Major version number of the glyph variations table — set to 1. + //uint16 minorVersion Minor version number of the glyph variations table — set to 0. + //uint16 axisCount The number of variation axes for this font. + // This must be the same number as axisCount in the 'fvar' table. + //uint16 sharedTupleCount The number of shared tuple records. + // Shared tuple records can be referenced within glyph variation data tables for multiple glyphs, + // as opposed to other tuple records stored directly within a glyph variation data table. + //Offset32 sharedTuplesOffset Offset from the start of this table to the shared tuple records. + //uint16 glyphCount The number of glyphs in this font.This must match the number of glyphs stored elsewhere in the font. + //uint16 flags Bit-field that gives the format of the offset array that follows. + // If bit 0 is clear, the offsets are uint16; + // if bit 0 is set, the offsets are uint32. + //Offset32 glyphVariationDataArrayOffset Offset from the start of this table to the array of GlyphVariationData tables. + // + //Offset16- + //-or- Offset32 glyphVariationDataOffsets[glyphCount + 1] Offsets from the start of the GlyphVariationData array to each GlyphVariationData table. + // + //------------- + + + long beginAt = reader.BaseStream.Position; + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); +#if DEBUG + if (majorVersion != 1 && minorVersion != 0) + { + //WARN + } +#endif + + axisCount = reader.ReadUInt16(); //This must be the same number as axisCount in the 'fvar' table + + ushort sharedTupleCount = reader.ReadUInt16(); + uint sharedTuplesOffset = reader.ReadUInt32(); + ushort glyphCount = reader.ReadUInt16(); + ushort flags = reader.ReadUInt16(); + uint glyphVariationDataArrayOffset = reader.ReadUInt32(); + + uint[] glyphVariationDataOffsets = null; + if ((flags & 0x1) == 0) + { + //bit 0 is clear-> use Offset16 + glyphVariationDataOffsets = reader.ReadUInt16ArrayAsUInt32Array(glyphCount); + // + //***If the short format (Offset16) is used for offsets, + //the value stored is the offset divided by 2. + //Hence, the actual offset for the location of the GlyphVariationData table within the font + //will be the value stored in the offsets array multiplied by 2. + + for (int i = 0; i < glyphVariationDataOffsets.Length; ++i) + { + glyphVariationDataOffsets[i] *= 2; + } + } + else + { + //Offset32 + glyphVariationDataOffsets = reader.ReadUInt32Array(glyphCount); + } + + reader.BaseStream.Position = beginAt + sharedTuplesOffset; + ReadSharedTupleArray(reader, sharedTupleCount); + + + //GlyphVariationData array ... + long glyphVariableData_startAt = beginAt + glyphVariationDataArrayOffset; + reader.BaseStream.Position = glyphVariableData_startAt; + + _glyphVarDataArr = new GlyphVariableData[glyphVariationDataOffsets.Length]; + + for (int i = 0; i < glyphVariationDataOffsets.Length; ++i) + { + reader.BaseStream.Position = glyphVariableData_startAt + glyphVariationDataOffsets[i]; + _glyphVarDataArr[i] = ReadGlyphVariationData(reader); + } + + } + void ReadSharedTupleArray(BinaryReader reader, ushort sharedTupleCount) + { + //------------- + //Shared tuples array + //------------- + //The shared tuples array provides a set of variation-space positions + //that can be referenced by variation data for any glyph. + //The shared tuples array follows the GlyphVariationData offsets array + //at the end of the 'gvar' header. + //This data is simply an array of tuple records, each representing a position in the font’s variation space. + + //Shared tuples array: + //Type Name Description + //TupleRecord sharedTuples[sharedTupleCount] Array of tuple records shared across all glyph variation data tables. + + //Tuple records that are in the shared array or + //that are contained directly within a given glyph variation data table + //use 2.14 values to represent normalized coordinate values. + //See the Common Table Formats chapter for details. + + // + + //Tuple Records + //https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats + //The tuple variation store formats make reference to regions within the font’s variation space using tuple records. + //These references identify positions in terms of normalized coordinates, which use F2DOT14 values. + //Tuple record(F2DOT14): + //Type Name Description + //F2DOT14 coordinates[axisCount] Coordinate array specifying a position within the font’s variation space. + // The number of elements must match the axisCount specified in the 'fvar' table. + + + TupleRecord[] tupleRecords = new TupleRecord[sharedTupleCount]; + for (int t = 0; t < sharedTupleCount; ++t) + { + tupleRecords[t] = TupleRecord.ReadTupleRecord(reader, axisCount); + } + + _sharedTuples = tupleRecords; + } + + + static void ReadPackedPoints(BinaryReader reader, List packPoints) + { + byte b0 = reader.ReadByte(); + if (b0 == 0) + { + //If the first byte is 0, then a second count byte is not used. + //This value has a special meaning: the tuple variation data provides deltas for all glyph points (including the “phantom” points), or for all CVTs. + + } + else if (b0 > 0 && b0 <= 127) + { + //If the first byte is non-zero and the high bit is clear (value is 1 to 127), + //then a second count byte is not used. + //The point count is equal to the value of the first byte. + ReadPackedPoints(reader, b0, packPoints); + } + else + { + //If the high bit of the first byte is set, then a second byte is used. + //The count is read from interpreting the two bytes as a big-endian uint16 value with the high-order bit masked out. + + //Thus, if the count fits in 7 bits, it is stored in a single byte, with the value 0 having a special interpretation. + //If the count does not fit in 7 bits, then the count is stored in the first two bytes with the high bit of the first byte set as a flag + //that is not part of the count — the count uses 15 bits. + + byte b1 = reader.ReadByte(); + ReadPackedPoints(reader, ((b0 & 0x7F) << 8) | b1, packPoints); + } + } + static void ReadPackedPoints(BinaryReader reader, int point_count, List packPoints) + { + + int point_read = 0; + //for (int n = 0; n < point_count ; ++n) + while (point_read < point_count) + { + //Point number data runs follow after the count. + + //Each data run begins with a control byte that specifies the number of point numbers defined in the run, + //and a flag bit indicating the format of the run data. + //The control byte’s high bit specifies whether the run is represented in 8-bit or 16-bit values. + //The low 7 bits specify the number of elements in the run minus 1. + //The format of the control byte is as follows: + + byte controlByte = reader.ReadByte(); + + //Mask Name Description + //0x80 POINTS_ARE_WORDS Flag indicating the data type used for point numbers in this run. + // If set, the point numbers are stored as unsigned 16-bit values (uint16); + // if clear, the point numbers are stored as unsigned bytes (uint8). + //0x7F POINT_RUN_COUNT_MASK Mask for the low 7 bits of the control byte to give the number of point number elements, minus 1. + + + int point_run_count = (controlByte & 0x7F) + 1; + //In the first point run, the first point number is represented directly (that is, as a difference from zero). + //Each subsequent point number in that run is stored as the difference between it and the previous point number. + //In subsequent runs, all elements, including the first, represent a difference from the last point number. + + if (((controlByte & 0x80) == 0x80)) //point_are_uint16 + { + for (int i = 0; i < point_run_count; ++i) + { + point_read++; + packPoints.Add(reader.ReadUInt16()); + } + } + else + { + for (int i = 0; i < point_run_count; ++i) + { + point_read++; + packPoints.Add(reader.ReadByte()); + } + } + } + } + + GlyphVariableData ReadGlyphVariationData(BinaryReader reader) + { + //https://docs.microsoft.com/en-gb/typography/opentype/spec/otvarcommonformats#tuple-records + //------------ + //The glyphVariationData table array + //The glyphVariationData table array follows the 'gvar' header and shared tuples array. + //Each glyphVariationData table describes the variation data for a single glyph in the font. + + //GlyphVariationData header: + //Type Name Description + //uint16 tupleVariationCount A packed field. + // The high 4 bits are flags, + // and the low 12 bits are the number of tuple variation tables for this glyph. + // The number of tuple variation tables can be any number between 1 and 4095. + //Offset16 dataOffset Offset from the start of the GlyphVariationData table to the serialized data + //TupleVariationHeader tupleVariationHeaders[tupleCount] Array of tuple variation headers. + + GlyphVariableData glyphVarData = new GlyphVariableData(); + + long beginAt = reader.BaseStream.Position; + ushort tupleVariationCount = reader.ReadUInt16(); + ushort dataOffset = reader.ReadUInt16(); + + + //The tupleVariationCount field contains a packed value that includes flags and the number of + //logical tuple variation tables — which is also the number of physical tuple variation headers. + //The format of the tupleVariationCount value is as follows: + //Table 4 + //Mask Name Description + //0x8000 SHARED_POINT_NUMBERS Flag indicating that some or all tuple variation tables reference a shared set of “point” numbers. + // These shared numbers are represented as packed point number data at the start of the serialized data.*** + //0x7000 Reserved Reserved for future use — set to 0. + //0x0FFF COUNT_MASK Mask for the low bits to give the number of tuple variation tables. + + + int tupleCount = tupleVariationCount & 0xFFF;//low 12 bits are the number of tuple variation tables for this glyph + + TupleVariationHeader[] tupleHaders = new TupleVariationHeader[tupleCount]; + glyphVarData.tupleHeaders = tupleHaders; + + for (int i = 0; i < tupleCount; ++i) + { + tupleHaders[i] = TupleVariationHeader.Read(reader, axisCount); + } + + //read glyph serialized data (https://docs.microsoft.com/en-gb/typography/opentype/spec/otvarcommonformats#serialized-data) + + reader.BaseStream.Position = beginAt + dataOffset; + // + //If the sharedPointNumbers flag is set, + //then the serialized data following the header begins with packed “point” number data. + + //In the context of a GlyphVariationData table within the 'gvar' table, + //these identify outline point numbers for which deltas are explicitly provided. + //In the context of the 'cvar' table, these are interpreted as CVT indices rather than point indices. + //The format of packed point number data is described below. + //.... + + int flags = tupleVariationCount >> 12;//The high 4 bits are flags, + if ((flags & 0x8) == 0x8)//check the flags has SHARED_POINT_NUMBERS or not + { + + //The serialized data block begins with shared “point” number data, + //followed by the variation data for the tuple variation tables. + //The shared point number data is optional: + //it is present if the corresponding flag is set in the tupleVariationCount field of the header. + //If present, the shared number data is represented as packed point numbers, described below. + + //https://docs.microsoft.com/en-gb/typography/opentype/spec/otvarcommonformats#packed-point-numbers + + //... + //Packed point numbers are stored as a count followed by one or more runs of point number data. + + //The count may be stored in one or two bytes. + //After reading the first byte, the need for a second byte can be determined. + //The count bytes are processed as follows: + + glyphVarData._sharedPoints = new List(); + ReadPackedPoints(reader, glyphVarData._sharedPoints); + } + + for (int i = 0; i < tupleCount; ++i) + { + TupleVariationHeader header = tupleHaders[i]; + + ushort dataSize = header.variableDataSize; + long expect_endAt = reader.BaseStream.Position + dataSize; + +#if DEBUG + if (expect_endAt > reader.BaseStream.Length) + { + + } +#endif + //The variationDataSize value indicates the size of serialized data for the given tuple variation table that is contained in the serialized data. + //It does not include the size of the TupleVariationHeader. + + if ((header.flags & ((int)TupleIndexFormat.PRIVATE_POINT_NUMBERS >> 12)) == ((int)TupleIndexFormat.PRIVATE_POINT_NUMBERS) >> 12) + { + List privatePoints = new List(); + ReadPackedPoints(reader, privatePoints); + header.PrivatePoints = privatePoints.ToArray(); + } + else if (header.flags != 0) + { + + } + + //Packed Deltas + //Packed deltas are stored as a series of runs. Each delta run consists of a control byte followed by the actual delta values of that run. + //The control byte is a packed value with flags in the high two bits and a count in the low six bits. + //The flags specify the data size of the delta values in the run. The format of the control byte is as follows: + //Packed Deltas + //Mask Name Description + //0x80 DELTAS_ARE_ZERO Flag indicating that this run contains no data (no explicit delta values are stored), and that all of the deltas for this run are zero. + //0x40 DELTAS_ARE_WORDS Flag indicating the data type for delta values in the run. If set, the run contains 16-bit signed deltas (int16); if clear, the run contains 8-bit signed deltas (int8). + //0x3F DELTA_RUN_COUNT_MASK Mask for the low 6 bits to provide the number of delta values in the run, minus one. + + List packedDeltasXY = new List(); + while (reader.BaseStream.Position < expect_endAt) + { + byte controlByte = reader.ReadByte(); + int number_in_run = (controlByte & 0x3F) + 1; + + int flags01 = (controlByte >> 6) << 6; + + if (flags01 == 0x80) + { + for (int nn = 0; nn < number_in_run; ++nn) + { + packedDeltasXY.Add(0); + } + } + else if (flags01 == 0x40) + { + //DELTAS_ARE_WORDS Flag indicating the data type for delta values in the run.If set, + //the run contains 16 - bit signed deltas(int16); + //if clear, the run contains 8 - bit signed deltas(int8). + + for (int nn = 0; nn < number_in_run; ++nn) + { + packedDeltasXY.Add(reader.ReadInt16()); + } + } + else if (flags01 == 0) + { + for (int nn = 0; nn < number_in_run; ++nn) + { + packedDeltasXY.Add(reader.ReadByte()); + } + } + else + { + + } + } + //--- + header.PackedDeltasXY = packedDeltasXY.ToArray(); + +#if DEBUG + //ensure! + if ((packedDeltasXY.Count % 2) != 0) + { + System.Diagnostics.Debugger.Break(); + } + //ensure! + if (reader.BaseStream.Position != expect_endAt) + { + System.Diagnostics.Debugger.Break(); + } +#endif + } + + return glyphVarData; + } + } +} + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/HVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/HVar.cs new file mode 100644 index 00000000..544dfaa0 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/HVar.cs @@ -0,0 +1,153 @@ +//MIT, 2019-present, WinterDev +using System; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + //https://docs.microsoft.com/en-us/typography/opentype/spec/hvar + + /// + /// HVAR — Horizontal Metrics Variations Table + /// + class HVar : TableEntry + { + public const string _N = "HVAR"; + public override string Name => _N; + + + internal ItemVariationStoreTable _itemVartionStore; + internal DeltaSetIndexMap[] _advanceWidthMapping; + internal DeltaSetIndexMap[] _lsbMapping; + internal DeltaSetIndexMap[] _rsbMapping; + + public HVar() + { + //The HVAR table is used in variable fonts to provide variations for horizontal glyph metrics values. + //This can be used to provide variation data for advance widths in the 'hmtx' table. + //In fonts with TrueType outlines, it can also be used to provide variation data for left and right side + //bearings obtained from the 'hmtx' table and glyph bounding box. + } + protected override void ReadContentFrom(BinaryReader reader) + { + long beginAt = reader.BaseStream.Position; + + //Horizontal metrics variations table: + //Type Name Description + //uint16 majorVersion Major version number of the horizontal metrics variations table — set to 1. + //uint16 minorVersion Minor version number of the horizontal metrics variations table — set to 0. + //Offset32 itemVariationStoreOffset Offset in bytes from the start of this table to the item variation store table. + //Offset32 advanceWidthMappingOffset Offset in bytes from the start of this table to the delta-set index mapping for advance widths (may be NULL). + //Offset32 lsbMappingOffset Offset in bytes from the start of this table to the delta-set index mapping for left side bearings(may be NULL). + //Offset32 rsbMappingOffset Offset in bytes from the start of this table to the delta-set index mapping for right side bearings(may be NULL). + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + uint itemVariationStoreOffset = reader.ReadUInt32(); + uint advanceWidthMappingOffset = reader.ReadUInt32(); + uint lsbMappingOffset = reader.ReadUInt32(); + uint rsbMappingOffset = reader.ReadUInt32(); + // + + //itemVariationStore + reader.BaseStream.Position = beginAt + itemVariationStoreOffset; + _itemVartionStore = new ItemVariationStoreTable(); + _itemVartionStore.ReadContentFrom(reader); + + //----------------------------------------- + if (advanceWidthMappingOffset > 0) + { + reader.BaseStream.Position = beginAt + advanceWidthMappingOffset; + _advanceWidthMapping = ReadDeltaSetIndexMapTable(reader); + } + if (lsbMappingOffset > 0) + { + reader.BaseStream.Position = beginAt + lsbMappingOffset; + _lsbMapping = ReadDeltaSetIndexMapTable(reader); + } + if (rsbMappingOffset > 0) + { + reader.BaseStream.Position = beginAt + rsbMappingOffset; + _rsbMapping = ReadDeltaSetIndexMapTable(reader); + } + } + + const int INNER_INDEX_BIT_COUNT_MASK = 0x000F; + const int MAP_ENTRY_SIZE_MASK = 0x0030; + const int MAP_ENTRY_SIZE_SHIFT = 4; + + public readonly struct DeltaSetIndexMap + { + public readonly ushort innerIndex; + public readonly ushort outerIndex; + + public DeltaSetIndexMap(ushort innerIndex, ushort outerIndex) + { + this.innerIndex = innerIndex; + this.outerIndex = outerIndex; + } + } + + static DeltaSetIndexMap[] ReadDeltaSetIndexMapTable(BinaryReader reader) + { + + + //DeltaSetIndexMap table: + //Table 2 + //Type Name Description + //uint16 entryFormat A packed field that describes the compressed representation of delta-set indices. See details below. + //uint16 mapCount The number of mapping entries. + //uint8 mapData[variable] The delta-set index mapping data. See details below. + + ushort entryFormat = reader.ReadUInt16(); + ushort mapCount = reader.ReadUInt16(); + + //The mapCount field indicates the number of delta-set index mapping entries. + //Glyph IDs are used as the index into the mapping array. + //If a given glyph ID is greater than mapCount - 1, then the last entry is used. + + //Each mapping entry represents a delta-set outer-level index and inner-level index combination. + //Logically, each of these indices is a 16-bit, unsigned value. + //These are represented in a packed format that uses one, two, three or four bytes. + //The entryFormat field is a packed bitfield that describes the compressed representation used in + //the mapData field of the given deltaSetIndexMap table. + //The format of the entryFormat field is as follows: + + //EntryFormat Field Masks + //Table 3 + //Mask Name Description + //0x000F INNER_INDEX_BIT_COUNT_MASK Mask for the low 4 bits, which give the count of bits minus one that are used in each entry for the inner-level index. + //0x0030 MAP_ENTRY_SIZE_MASK Mask for bits that indicate the size in bytes minus one of each entry. + //0xFFC0 Reserved Reserved for future use — set to 0. + + + //see also: afdko\c\public\lib\source\varread\varread.c (Apache2) + + int entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> MAP_ENTRY_SIZE_SHIFT) + 1; + int innerIndexEntryMask = (1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1; + int outerIndexEntryShift = (entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1; + + int mapDataSize = mapCount * entrySize; + + DeltaSetIndexMap[] deltaSetIndexMaps = new DeltaSetIndexMap[mapCount]; + + for (int i = 0; i < mapCount; ++i) + { + int entry; + switch (entrySize) + { + default: throw new OpenFontNotSupportedException(); + case 1: entry = reader.ReadByte(); break; + case 2: entry = (reader.ReadByte() << 8) | reader.ReadByte(); break; + case 3: entry = (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte(); break; + case 4: entry = (reader.ReadByte() << 24) | (reader.ReadByte() << 16) | (reader.ReadByte() << 8) | reader.ReadByte(); break; + } + //*** + deltaSetIndexMaps[i] = new DeltaSetIndexMap((ushort)(entry & innerIndexEntryMask), (ushort)(entry >> outerIndexEntryShift)); + } + + return deltaSetIndexMaps; + } + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/MVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/MVar.cs new file mode 100644 index 00000000..37047549 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/MVar.cs @@ -0,0 +1,307 @@ +//MIT, 2019-present, WinterDev +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + //https://docs.microsoft.com/en-us/typography/opentype/spec/mvar + + /// + /// MVAR — Metrics Variations Table + /// + class MVar : TableEntry + { + public const string _N = "MVAR"; + public override string Name => _N; + + //The metrics variations table is used in variable fonts + //to provide variations for font-wide metric values + //found in the OS/2 table and other font tables + + //The metrics variations table contains an item variation store structure to represent variation data. + //The item variation store and constituent formats are described in the chapter, + //OpenType Font Variations Common Table Formats. + + //The item variation store is also used in the HVAR and GDEF tables, + //**BUT** is different from the formats for variation data used in the 'cvar' or 'gvar' tables. + + //The item variation store format uses delta-set indices to reference variation delta data + //for particular target font-data items to which they are applied. + //Data external to the item variation store identifies the delta-set index to be used for each given target item. + + //Within the MVAR table, an array of value tag records identifies a set of target items, + //and provides the delta-set index used for each. + //The target items are identified by four-byte tags, + //with a given tag representing some font-wide value found in another table + + //The item variation store format uses a two-level organization for variation data: + //a store can have multiple item variation data subtables, and each subtable has multiple delta-set rows. + + //A delta-set index is a two-part index: + //an outer index that selects a particular item variation data subtable, + //and an inner index that selects a particular delta-set row within that subtable. + + //A value record specifies both the outer and inner portions of the delta-set index. + + + public ValueRecord[] valueRecords; + public ItemVariationStoreTable itemVariationStore; + + public MVar() + { + + } + protected override void ReadContentFrom(BinaryReader reader) + { + long startAt = reader.BaseStream.Position; + + //Metrics variations table: + //Type Name Description + //uint16 majorVersion Major version number of the metrics variations table — set to 1. + //uint16 minorVersion Minor version number of the metrics variations table — set to 0. + //uint16 (reserved) Not used; set to 0. + //uint16 valueRecordSize The size in bytes of each value record — must be greater than zero. + //uint16 valueRecordCount The number of value records — may be zero. + //Offset16 itemVariationStoreOffset Offset in bytes from the start of this table to the item variation store table. + // If valueRecordCount is zero, set to zero; + // if valueRecordCount is greater than zero, must be greater than zero. + //ValueRecord valueRecords[valueRecordCount] Array of value records that identify target items and the associated delta-set index for each. + // The valueTag records must be in binary order of their valueTag field. + + //----------- + // + //The valueRecordSize field indicates the size of each value record. + //Future, minor version updates of the MVAR table may define compatible extensions to the value record format with additional fields. + //**Implementations must use the valueRecordSize field to determine the start of each record.** + + //The valueRecords array is an array of value records that identify the target, + //font -wide measures for which variation adjustment data is provided (target items), + //and outer and inner delta-set indices for each item into the item variation store data. + + + ushort majorVersion = reader.ReadUInt16(); + ushort minorVersion = reader.ReadUInt16(); + ushort reserved = reader.ReadUInt16(); + ushort valueRecordSize = reader.ReadUInt16(); + ushort valueRecordCount = reader.ReadUInt16(); + ushort itemVariationStoreOffset = reader.ReadUInt16(); + + valueRecords = new ValueRecord[valueRecordCount]; + + for (int i = 0; i < valueRecordCount; ++i) + { + long recStartAt = reader.BaseStream.Position; + valueRecords[i] = new ValueRecord( + reader.ReadUInt32(), + reader.ReadUInt16(), + reader.ReadUInt16() + ); + + reader.BaseStream.Position = recStartAt + valueRecordSize;//**Implementations must use the valueRecordSize field to determine the start of each record.** + } + + // + //item variation store table + if (valueRecordCount > 0) + { + reader.BaseStream.Position = startAt + itemVariationStoreOffset; + itemVariationStore = new ItemVariationStoreTable(); + itemVariationStore.ReadContentFrom(reader); + } + + } + + public readonly struct ValueRecord + { + //ValueRecord: + //Type Name Description + //Tag valueTag Four-byte tag identifying a font-wide measure. + //uint16 deltaSetOuterIndex A delta-set outer index — used to select an item variation data subtable within the item variation store. + //uint16 deltaSetInnerIndex A delta-set inner index — used to select a delta-set row within an item variation data subtable. + + //The value records must be given in binary order of the valueTag values. + //Each tag identifies a font-wide measure found in some other font table. + //For example, if a value record has a value tag of 'hasc', + //this corresponds to the OS/2.sTypoAscender field. Details on the tags used within the MVAR table are provided below. + + public readonly uint tag; + public readonly ushort deltaSetOuterIndex; + public readonly ushort deltaSetInnerIndex; + public ValueRecord(uint tag, ushort deltaSetOuterIndex, ushort deltaSetInnerIndex) + { + this.tag = tag; + this.deltaSetOuterIndex = deltaSetOuterIndex; + this.deltaSetInnerIndex = deltaSetInnerIndex; + + + //Tags in the metrics variations table are case sensitive. + //Tags defined in this table use only lowercase letters or digits. + + //Tags that are used in a font’s metrics variations table should be those that are documented in this table specification. + //A font may also use privately-defined tags, which have semantics known only by private agreement. + + //Private-use tags must use begin with an uppercase letter and use only uppercase letters or digits. + //If a private-use tag is used in a given font, any application that does not recognize that tag should ignore it. + } + public string TranslatedTag => Utils.TagToString(tag); +#if DEBUG + public override string ToString() + { + return Utils.TagToString(tag) + ",outer:" + deltaSetOuterIndex + ",inner:" + deltaSetInnerIndex; + } +#endif + } + + + class ValueTagInfo + { + public readonly string Tag; + public readonly string Mnemonic; + public readonly string ValueRepresented; + public ValueTagInfo(string tag, string mnemonic, string valueRepresented) + { + this.Tag = tag; + this.Mnemonic = mnemonic; + this.ValueRepresented = valueRepresented; + } +#if DEBUG + public override string ToString() + { + return Tag + ", " + Mnemonic + ", " + ValueRepresented; + } +#endif + } + + static class ValueTags + { + + static Dictionary s_registerTags = new Dictionary(); + public static bool TryGetValueTagInfo(string tag, out ValueTagInfo valueTagInfo) + { + return s_registerTags.TryGetValue(tag, out valueTagInfo); + } + static void RegisterValueTagInfo(string tag, string mnemonic, string valueRepresented) + { + s_registerTags.Add(tag, new ValueTagInfo(tag, mnemonic, valueRepresented)); + } + static ValueTags() + { + //Value tags, ordered by logical grouping: + //Tag Mnemonic Value represented + //'hasc' horizontal ascender OS/2.sTypoAscender + //'hdsc' horizontal descender OS/2.sTypoDescender + //'hlgp' horizontal line gap OS/2.sTypoLineGap + //'hcla' horizontal clipping ascent OS/2.usWinAscent + //'hcld' horizontal clipping descent OS/2.usWinDescent + RegisterValueTagInfo("hasc", "horizontal ascender", "OS/2.sTypoAscender"); + RegisterValueTagInfo("hdsc", "horizontal descender", "OS/2.sTypoDescender"); + RegisterValueTagInfo("hlgp", "horizontal line gap", "OS/2.sTypoLineGap"); + RegisterValueTagInfo("hcla", "horizontal clipping ascent", "OS/2.usWinAscent"); + RegisterValueTagInfo("hcld", "horizontal clipping descent", "OS/2.usWinDescent"); + + //Tag Mnemonic Value represented + //'vasc' vertical ascender vhea.ascent + //'vdsc' vertical descender vhea.descent + //'vlgp' vertical line gap vhea.lineGap + RegisterValueTagInfo("vasc", "vertical ascender", "vhea.ascent"); + RegisterValueTagInfo("vdsc", "vertical descender", "vhea.descent"); + RegisterValueTagInfo("vlgp", "vertical line gap", "vhea.lineGap"); + + //Tag Mnemonic Value represented + //'hcrs' horizontal caret rise hhea.caretSlopeRise + //'hcrn' horizontal caret run hhea.caretSlopeRun + //'hcof' horizontal caret offset hhea.caretOffset + RegisterValueTagInfo("hcrs", "horizontal caret rise", "hhea.caretSlopeRise"); + RegisterValueTagInfo("hcrn", "horizontal caret run", "hhea.caretSlopeRun"); + RegisterValueTagInfo("hcof", "horizontal caret offset", "hhea.caretOffset"); + + //Tag Mnemonic Value represented + //'vcrs' vertical caret rise vhea.caretSlopeRise + //'vcrn' vertical caret run vhea.caretSlopeRun + //'vcof' vertical caret offset vhea.caretOffset + RegisterValueTagInfo("vcrs", "vertical caret rise", "vhea.caretSlopeRise"); + RegisterValueTagInfo("vcrn", "vertical caret run", "vhea.caretSlopeRun"); + RegisterValueTagInfo("vcof", "vertical caret offset", "vhea.caretOffset"); + + //Tag Mnemonic Value represented + //'xhgt' x height OS/2.sxHeight + //'cpht' cap height OS/2.sCapHeight + + //'sbxs' subscript em x size OS/2.ySubscriptXSize + //'sbys' subscript em y size OS/2.ySubscriptYSize + + //'sbxo' subscript em x offset OS/2.ySubscriptXOffset + //'sbyo' subscript em y offset OS/2.ySubscriptYOffset + + //'spxs' superscript em x size OS/2.ySuperscriptXSize + //'spys' superscript em y size OS/2.ySuperscriptYSize + + //'spxo' superscript em x offset OS/2.ySuperscriptXOffset + //'spyo' superscript em y offset OS/2.ySuperscriptYOffset + + //'strs' strikeout size OS/2.yStrikeoutSize + //'stro' strikeout offset OS/2.yStrikeoutPosition + RegisterValueTagInfo("xhgt", "x height", "OS/2.sTypoAscender"); + RegisterValueTagInfo("cpht", "cap height", "OS/2.sTypoDescender"); + + RegisterValueTagInfo("sbxs", "subscript em x size", "OS/2.ySubscriptXSize"); + RegisterValueTagInfo("sbys", "subscript em y size", "OS/2.ySubscriptYSize"); + + RegisterValueTagInfo("sbxo", "subscript em x offset", "OS/2.ySubscriptXOffset"); + RegisterValueTagInfo("sbyo", "subscript em y offset", "OS/2.ySubscriptYOffset"); + + RegisterValueTagInfo("spxs", "superscript em x size", "OS/2.ySuperscriptXSize"); + RegisterValueTagInfo("spys", "superscript em y size", "OS/2.ySuperscriptYSize"); + + RegisterValueTagInfo("spxo", "superscript em x offset", "OS/2.ySuperscriptXOffset"); + RegisterValueTagInfo("spyo", "superscript em y offset", "OS/2.ySuperscriptYOffset"); + + RegisterValueTagInfo("strs", "strikeout size", "OS/2.yStrikeoutSize"); + RegisterValueTagInfo("stro", "strikeout offset", "OS/2.yStrikeoutPosition"); + + + //Tag Mnemonic Value represented + //'unds' underline size post.underlineThickness + //'undo' underline offset post.underlinePosition + RegisterValueTagInfo("unds", "underline size", "post.underlineThickness"); + RegisterValueTagInfo("undo", "underline offset", "post.underlinePosition"); + + + //Tag Mnemonic Value represented + //'gsp0' gaspRange[0] gasp.gaspRange[0].rangeMaxPPEM + //'gsp1' gaspRange[1] gasp.gaspRange[1].rangeMaxPPEM + //'gsp2' gaspRange[2] gasp.gaspRange[2].rangeMaxPPEM + //'gsp3' gaspRange[3] gasp.gaspRange[3].rangeMaxPPEM + //'gsp4' gaspRange[4] gasp.gaspRange[4].rangeMaxPPEM + //'gsp5' gaspRange[5] gasp.gaspRange[5].rangeMaxPPEM + //'gsp6' gaspRange[6] gasp.gaspRange[6].rangeMaxPPEM + //'gsp7' gaspRange[7] gasp.gaspRange[7].rangeMaxPPEM + //'gsp8' gaspRange[8] gasp.gaspRange[8].rangeMaxPPEM + //'gsp9' gaspRange[9] gasp.gaspRange[9].rangeMaxPPEM + RegisterValueTagInfo("gsp0", "gaspRange[0]", "gasp.gaspRange[0].rangeMaxPPEM"); + RegisterValueTagInfo("gsp1", "gaspRange[1]", "gasp.gaspRange[1].rangeMaxPPEM"); + RegisterValueTagInfo("gsp2", "gaspRange[2]", "gasp.gaspRange[2].rangeMaxPPEM"); + RegisterValueTagInfo("gsp3", "gaspRange[3]", "gasp.gaspRange[3].rangeMaxPPEM"); + RegisterValueTagInfo("gsp4", "gaspRange[4]", "gasp.gaspRange[4].rangeMaxPPEM"); + RegisterValueTagInfo("gsp5", "gaspRange[5]", "gasp.gaspRange[5].rangeMaxPPEM"); + RegisterValueTagInfo("gsp6", "gaspRange[6]", "gasp.gaspRange[6].rangeMaxPPEM"); + RegisterValueTagInfo("gsp7", "gaspRange[7]", "gasp.gaspRange[7].rangeMaxPPEM"); + RegisterValueTagInfo("gsp8", "gaspRange[8]", "gasp.gaspRange[8].rangeMaxPPEM"); + RegisterValueTagInfo("gsp9", "gaspRange[9]", "gasp.gaspRange[9].rangeMaxPPEM"); + + + } + + + + + + + + } + + + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/VVar.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/VVar.cs new file mode 100644 index 00000000..b7abf198 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables.Variations/VVar.cs @@ -0,0 +1,2 @@ +//TODO: implement this +//https://docs.microsoft.com/en-us/typography/opentype/spec/vvar \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/CharacterMap.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/CharacterMap.cs new file mode 100644 index 00000000..125e81df --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/CharacterMap.cs @@ -0,0 +1,417 @@ +//Apache2, 2017-present, WinterDev, Sam Hocevar +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + static class CharacterMapExtension + { + public static void CollectUnicodeChars(this CharacterMap cmap, List unicodes, List glyphIndexList) + { + //temp fixed + int count1 = unicodes.Count; + cmap.CollectUnicodeChars(unicodes); + int count2 = unicodes.Count; + for (int i = count1; i < count2; ++i) + { + glyphIndexList.Add(cmap.GetGlyphIndex((int)unicodes[i])); + } + } + } + + class CharMapFormat4 : CharacterMap + { + public override ushort Format => 4; + + internal readonly ushort[] _startCode; //Starting character code for each segment + internal readonly ushort[] _endCode;//Ending character code for each segment, last = 0xFFFF. + internal readonly ushort[] _idDelta; //Delta for all character codes in segment + internal readonly ushort[] _idRangeOffset; //Offset in bytes to glyph indexArray, or 0 (not offset in bytes unit) + internal readonly ushort[] _glyphIdArray; + public CharMapFormat4(ushort[] startCode, ushort[] endCode, ushort[] idDelta, ushort[] idRangeOffset, ushort[] glyphIdArray) + { + _startCode = startCode; + _endCode = endCode; + _idDelta = idDelta; + _idRangeOffset = idRangeOffset; + _glyphIdArray = glyphIdArray; + } + + public override ushort GetGlyphIndex(int codepoint) + { + // This lookup table only supports 16-bit codepoints + if (codepoint > ushort.MaxValue) + { + return 0; + } + + // https://www.microsoft.com/typography/otspec/cmap.htm#format4 + // "You search for the first endCode that is greater than or equal to the character code you want to map" + // "The segments are sorted in order of increasing endCode values" + // -> binary search is valid here + int i = Array.BinarySearch(_endCode, (ushort)codepoint); + i = i < 0 ? ~i : i; + + // https://www.microsoft.com/typography/otspec/cmap.htm#format4 + // "If the corresponding startCode is [not] less than or equal to the character code, + // then [...] the missingGlyph is returned" + // Index i should never be out of range, because the list ends with a + // 0xFFFF value. However, we also use this charmap for format 0, which + // does not have that final endcode, so there is a chance to overflow. + if (i >= _endCode.Length || _startCode[i] > codepoint) + { + return 0; + } + + if (_idRangeOffset[i] == 0) + { + //TODO: review 65536 => use bitflags + return (ushort)((codepoint + _idDelta[i]) % 65536); + } + else + { + //If the idRangeOffset value for the segment is not 0, + //the mapping of character codes relies on glyphIdArray. + //The character code offset from startCode is added to the idRangeOffset value. + //This sum is used as an offset from the current location within idRangeOffset itself to index out the correct glyphIdArray value. + //This obscure indexing trick works because glyphIdArray immediately follows idRangeOffset in the font file. + //The C expression that yields the glyph index is: + + //*(idRangeOffset[i]/2 + //+ (c - startCount[i]) + //+ &idRangeOffset[i]) + + int offset = _idRangeOffset[i] / 2 + (codepoint - _startCode[i]); + // I want to thank Microsoft for this clever pointer trick + // TODO: What if the value fetched is inside the _idRangeOffset table? + // TODO: e.g. (offset - _idRangeOffset.Length + i < 0) + return _glyphIdArray[offset - _idRangeOffset.Length + i]; + } + } + public override void CollectUnicodeChars(List unicodes) + { + for (int i = 0; i < _startCode.Length; ++i) + { + uint start = _startCode[i]; + uint stop = _endCode[i]; + for (uint u = start; u <= stop; ++u) + { + unicodes.Add(u); + } + } + } + + } + + class CharMapFormat12 : CharacterMap + { + public override ushort Format => 12; + + uint[] _startCharCodes, _endCharCodes, _startGlyphIds; + internal CharMapFormat12(uint[] startCharCodes, uint[] endCharCodes, uint[] startGlyphIds) + { + _startCharCodes = startCharCodes; + _endCharCodes = endCharCodes; + _startGlyphIds = startGlyphIds; + } + + public override ushort GetGlyphIndex(int codepoint) + { + // https://www.microsoft.com/typography/otspec/cmap.htm#format12 + // "Groups must be sorted by increasing startCharCode." + // -> binary search is valid here + int i = Array.BinarySearch(_startCharCodes, (uint)codepoint); + i = i < 0 ? ~i - 1 : i; + + if (i >= 0 && codepoint <= _endCharCodes[i]) + { + return (ushort)(_startGlyphIds[i] + codepoint - _startCharCodes[i]); + } + return 0; + } + public override void CollectUnicodeChars(List unicodes) + { + for (int i = 0; i < _startCharCodes.Length; ++i) + { + uint start = _startCharCodes[i]; + uint stop = _endCharCodes[i]; + for (uint u = start; u <= stop; ++u) + { + unicodes.Add(u); + } + } + } + + } + + class CharMapFormat6 : CharacterMap + { + public override ushort Format => 6; + + internal CharMapFormat6(ushort startCode, ushort[] glyphIdArray) + { + _glyphIdArray = glyphIdArray; + _startCode = startCode; + } + + public override ushort GetGlyphIndex(int codepoint) + { + // The firstCode and entryCount values specify a subrange (beginning at firstCode, + // length = entryCount) within the range of possible character codes. + // Codes outside of this subrange are mapped to glyph index 0. + // The offset of the code (from the first code) within this subrange is used as + // index to the glyphIdArray, which provides the glyph index value. + int i = codepoint - _startCode; + return i >= 0 && i < _glyphIdArray.Length ? _glyphIdArray[i] : (ushort)0; + } + + + internal readonly ushort _startCode; + internal readonly ushort[] _glyphIdArray; + public override void CollectUnicodeChars(List unicodes) + { + ushort u = _startCode; + for (uint i = 0; i < _glyphIdArray.Length; ++i) + { + unicodes.Add(u + i); + } + } + } + + + //https://www.microsoft.com/typography/otspec/cmap.htm#format14 + // Subtable format 14 specifies the Unicode Variation Sequences(UVSes) supported by the font. + // A Variation Sequence, according to the Unicode Standard, comprises a base character followed + // by a variation selector; e.g. . + // + // The subtable partitions the UVSes supported by the font into two categories: “default” and + // “non-default” UVSes.Given a UVS, if the glyph obtained by looking up the base character of + // that sequence in the Unicode cmap subtable(i.e.the UCS-4 or the BMP cmap subtable) is the + // glyph to use for that sequence, then the sequence is a “default” UVS; otherwise it is a + // “non-default” UVS, and the glyph to use for that sequence is specified in the format 14 + // subtable itself. + class CharMapFormat14 : CharacterMap + { + public override ushort Format => 14; + public override ushort GetGlyphIndex(int character) => 0; + public ushort CharacterPairToGlyphIndex(int codepoint, ushort defaultGlyphIndex, int nextCodepoint) + { + // Only check codepoint if nextCodepoint is a variation selector + + if (_variationSelectors.TryGetValue(nextCodepoint, out VariationSelector sel)) + { + + // If the sequence is a non-default UVS, return the mapped glyph + + if (sel.UVSMappings.TryGetValue(codepoint, out ushort ret)) + { + return ret; + } + + // If the sequence is a default UVS, return the default glyph + for (int i = 0; i < sel.DefaultStartCodes.Count; ++i) + { + if (codepoint >= sel.DefaultStartCodes[i] && codepoint < sel.DefaultEndCodes[i]) + { + return defaultGlyphIndex; + } + } + + // At this point we are neither a non-default UVS nor a default UVS, + // but we know the nextCodepoint is a variation selector. Unicode says + // this glyph should be invisible: “no visible rendering for the VS” + // (http://unicode.org/faq/unsup_char.html#4) + return defaultGlyphIndex; + } + + // In all other cases, return 0 + return 0; + } + + public override void CollectUnicodeChars(List unicodes) + { + //TODO: review here +#if DEBUG + System.Diagnostics.Debug.WriteLine("not implemented"); +#endif + } + + + public static CharMapFormat14 Create(BinaryReader reader) + { + // 'cmap' Subtable Format 14: + // Type Name Description + // uint16 format Subtable format.Set to 14. + // uint32 length Byte length of this subtable (including this header) + // uint32 numVarSelectorRecords Number of variation Selector Records + // VariationSelector varSelector[numVarSelectorRecords] Array of VariationSelector records. + // --- + // + // Each variation selector records specifies a variation selector character, and + // offsets to “default” and “non-default” tables used to map variation sequences using + // that variation selector. + // + // VariationSelector Record: + // Type Name Description + // uint24 varSelector Variation selector + // Offset32 defaultUVSOffset Offset from the start of the format 14 subtable to + // Default UVS Table.May be 0. + // Offset32 nonDefaultUVSOffset Offset from the start of the format 14 subtable to + // Non-Default UVS Table. May be 0. + // + // The Variation Selector Records are sorted in increasing order of ‘varSelector’. No + // two records may have the same ‘varSelector’. + // A Variation Selector Record and the data its offsets point to specify those UVSes + // supported by the font for which the variation selector is the ‘varSelector’ value + // of the record. The base characters of the UVSes are stored in the tables pointed + // to by the offsets.The UVSes are partitioned by whether they are default or + // non-default UVSes. + // Glyph IDs to be used for non-default UVSes are specified in the Non-Default UVS table. + + long beginAt = reader.BaseStream.Position - 2; // account for header format entry + uint length = reader.ReadUInt32(); // Byte length of this subtable (including the header) + uint numVarSelectorRecords = reader.ReadUInt32(); + + var variationSelectors = new Dictionary(); + int[] varSelectors = new int[numVarSelectorRecords]; + uint[] defaultUVSOffsets = new uint[numVarSelectorRecords]; + uint[] nonDefaultUVSOffsets = new uint[numVarSelectorRecords]; + for (int i = 0; i < numVarSelectorRecords; ++i) + { + varSelectors[i] = Utils.ReadUInt24(reader); + defaultUVSOffsets[i] = reader.ReadUInt32(); + nonDefaultUVSOffsets[i] = reader.ReadUInt32(); + } + + + for (int i = 0; i < numVarSelectorRecords; ++i) + { + var sel = new VariationSelector(); + + if (defaultUVSOffsets[i] != 0) + { + // Default UVS table + // + // A Default UVS Table is simply a range-compressed list of Unicode scalar + // values, representing the base characters of the default UVSes which use + // the ‘varSelector’ of the associated Variation Selector Record. + // + // DefaultUVS Table: + // Type Name Description + // uint32 numUnicodeValueRanges Number of Unicode character ranges. + // UnicodeRange ranges[numUnicodeValueRanges] Array of UnicodeRange records. + // + // Each Unicode range record specifies a contiguous range of Unicode values. + // + // UnicodeRange Record: + // Type Name Description + // uint24 startUnicodeValue First value in this range + // uint8 additionalCount Number of additional values in this range + // + // For example, the range U+4E4D&endash; U+4E4F (3 values) will set + // ‘startUnicodeValue’ to 0x004E4D and ‘additionalCount’ to 2. A singleton + // range will set ‘additionalCount’ to 0. + // (‘startUnicodeValue’ + ‘additionalCount’) must not exceed 0xFFFFFF. + // The Unicode Value Ranges are sorted in increasing order of + // ‘startUnicodeValue’. The ranges must not overlap; i.e., + // (‘startUnicodeValue’ + ‘additionalCount’) must be less than the + // ‘startUnicodeValue’ of the following range (if any). + + reader.BaseStream.Seek(beginAt + defaultUVSOffsets[i], SeekOrigin.Begin); + uint numUnicodeValueRanges = reader.ReadUInt32(); + for (int n = 0; n < numUnicodeValueRanges; ++n) + { + int startCode = (int)Utils.ReadUInt24(reader); + sel.DefaultStartCodes.Add(startCode); + sel.DefaultEndCodes.Add(startCode + reader.ReadByte()); + } + } + + if (nonDefaultUVSOffsets[i] != 0) + { + // Non-Default UVS table + // + // A Non-Default UVS Table is a list of pairs of Unicode scalar values and + // glyph IDs.The Unicode values represent the base characters of all + // non -default UVSes which use the ‘varSelector’ of the associated Variation + // Selector Record, and the glyph IDs specify the glyph IDs to use for the + // UVSes. + // + // NonDefaultUVS Table: + // Type Name Description + // uint32 numUVSMappings Number of UVS Mappings that follow + // UVSMapping uvsMappings[numUVSMappings] Array of UVSMapping records. + // + // Each UVSMapping record provides a glyph ID mapping for one base Unicode + // character, when that base character is used in a variation sequence with + // the current variation selector. + // + // UVSMapping Record: + // Type Name Description + // uint24 unicodeValue Base Unicode value of the UVS + // uint16 glyphID Glyph ID of the UVS + // + // The UVS Mappings are sorted in increasing order of ‘unicodeValue’. No two + // mappings in this table may have the same ‘unicodeValue’ values. + + reader.BaseStream.Seek(beginAt + nonDefaultUVSOffsets[i], SeekOrigin.Begin); + uint numUVSMappings = reader.ReadUInt32(); + for (int n = 0; n < numUVSMappings; ++n) + { + int unicodeValue = (int)Utils.ReadUInt24(reader); + ushort glyphID = reader.ReadUInt16(); + sel.UVSMappings.Add(unicodeValue, glyphID); + } + } + + variationSelectors.Add(varSelectors[i], sel); + } + + return new CharMapFormat14 { _variationSelectors = variationSelectors }; + } + + class VariationSelector + { + public List DefaultStartCodes = new List(); + public List DefaultEndCodes = new List(); + public Dictionary UVSMappings = new Dictionary(); + } + + private Dictionary _variationSelectors; + } + + /// + /// An empty character map that maps all characters to glyph 0 + /// + class NullCharMap : CharacterMap + { + public override ushort Format => 0; + public override ushort GetGlyphIndex(int character) => 0; + public override void CollectUnicodeChars(List unicodes) { /*nothing*/} + + } + + abstract class CharacterMap + { + //https://www.microsoft.com/typography/otspec/cmap.htm + public abstract ushort Format { get; } + public ushort PlatformId { get; set; } + public ushort EncodingId { get; set; } + + public ushort CharacterToGlyphIndex(int codepoint) + { + return GetGlyphIndex(codepoint); + } + public abstract ushort GetGlyphIndex(int codepoint); + public abstract void CollectUnicodeChars(List unicodes); + + public override string ToString() + { + return $"fmt:{ Format }, plat:{ PlatformId }, enc:{ EncodingId }"; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Cmap.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Cmap.cs new file mode 100644 index 00000000..3f9072b3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Cmap.cs @@ -0,0 +1,436 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; +namespace Typography.OpenFont.Tables +{ + + //--------------------------------------------------- + //cmap - Character To Glyph Index Mapping Table + //--------------------------------------------------- + //This table defines the mapping of character codes to the glyph index values used in the font. + //It may contain more than one subtable, in order to support more than one character encoding scheme. + //Character codes that do not correspond to any glyph in the font should be mapped to glyph index 0. + //The glyph at this location must be a special glyph representing a missing character, commonly known as .notdef. + + //The table header indicates the character encodings for which subtables are present. + //Each subtable is in one of seven possible formats and begins with a format code indicating the format used. + + //The platform ID and platform - specific encoding ID in the header entry(and, in the case of the Macintosh platform, + //the language field in the subtable itself) are used to specify a particular 'cmap' encoding. + //The header entries must be sorted first by platform ID, then by platform - specific encoding ID, + //and then by the language field in the corresponding subtable.Each platform ID, + //platform - specific encoding ID, and subtable language combination may appear only once in the 'cmap' table. + + //When building a Unicode font for Windows, the platform ID should be 3 and the encoding ID should be 1. + //When building a symbol font for Windows, the platform ID should be 3 and the encoding ID should be 0. + //When building a font that will be used on the Macintosh, the platform ID should be 1 and the encoding ID should be 0. + + //All Microsoft Unicode BMP encodings(Platform ID = 3, Encoding ID = 1) must provide at least a Format 4 'cmap' subtable. + //If the font is meant to support supplementary(non - BMP) Unicode characters, + //it will additionally need a Format 12 subtable with a platform encoding ID 10. + //The contents of the Format 12 subtable need to be a superset of the contents of the Format 4 subtable. + //Microsoft strongly recommends using a BMP Unicode 'cmap' for all fonts. However, some other encodings that appear in current fonts follow: + + //Windows Encodings + //Platform ID Encoding ID Description + //3 0 Symbol + //3 1 Unicode BMP(UCS - 2) + //3 2 ShiftJIS + //3 3 PRC + //3 4 Big5 + //3 5 Wansung + //3 6 Johab + //3 7 Reserved + //3 8 Reserved + //3 9 Reserved + //3 10 Unicode UCS - 4 + //--------------------------------------------------- + + + //////////////////////////////////////////////////////////////////////// + //from https://docs.microsoft.com/en-us/typography/opentype/processing-part2 + //CMAP Table + //Every glyph in a TrueType font is identified by a unique Glyph ID (GID), + //a simple sequential numbering of all the glyphs in the font. + //These GIDs are mapped to character codepoints in the font's CMAP table. + //In OpenType fonts, the principal mapping is to Unicode codepoints; that is, + //the GIDs of nominal glyph representations of specific characters are mapped to appropriate Unicode values. + + //The key to OpenType glyph processing is that not every glyph in a font is directly mapped to a codepoint. + //Variant glyph forms, ligatures, dynamically composed diacritics and other rendering forms do not require entries in the CMAP table. + //Rather, their GIDs are mapped in layout features to the GIDs of nominal character forms, + //i.e. to those glyphs that do have CMAP entries. This is the heart of glyph processing: the mapping of GIDs to each other, + //rather than directly to character codepoints. + + //In order for fonts to be able to correctly render text, + //font developers must ensure that the correct nominal glyph form GIDs are mapped to the correct Unicode codepoints. + //Application developers, of course, must ensure that their applications correctly manage input and storage of Unicode text codepoints, + //or map correctly to these codepoints from other codepages and character sets. + //////////////////////////////////////////////////////////////////////// + + public class Cmap : TableEntry + { + //https://docs.microsoft.com/en-us/typography/opentype/spec/cmap + + public const string _N = "cmap"; + public override string Name => _N; + + CharacterMap[] _charMaps = null; + List _charMap14List; + Dictionary _codepointToGlyphs = new Dictionary(); + + + /// + /// find glyph index from given codepoint(s) + /// + /// + /// + /// glyph index + + public ushort GetGlyphIndex(int codepoint, int nextCodepoint, out bool skipNextCodepoint) + { + // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap + // "character codes that do not correspond to any glyph in the font should be mapped to glyph index 0." + + skipNextCodepoint = false; //default + + if (!_codepointToGlyphs.TryGetValue(codepoint, out ushort found)) + { + for (int i = 0; i < _charMaps.Length; ++i) + { + CharacterMap cmap = _charMaps[i]; + + + + if (found == 0) + { + found = cmap.GetGlyphIndex(codepoint); + } + else if (cmap.PlatformId == 3 && cmap.EncodingId == 1) + { + //...When building a Unicode font for Windows, + // the platform ID should be 3 and the encoding ID should be 1 + ushort glyphIndex = cmap.GetGlyphIndex(codepoint); //glyphIndex=> gid + if (glyphIndex != 0) + { + found = glyphIndex; + } + } + } + _codepointToGlyphs[codepoint] = found; + } + + // If there is a second codepoint, we are asked whether this is an UVS sequence + // -> if true, return a glyph ID + // -> otherwise, return 0 + if (nextCodepoint > 0 && _charMap14List != null) + { + foreach (CharMapFormat14 cmap14 in _charMap14List) + { + ushort glyphIndex = cmap14.CharacterPairToGlyphIndex(codepoint, found, nextCodepoint); + if (glyphIndex > 0) + { + skipNextCodepoint = true; + return glyphIndex; + } + } + } + return found; + } + + protected override void ReadContentFrom(BinaryReader input) + { + //https://www.microsoft.com/typography/otspec/cmap.htm + long beginAt = input.BaseStream.Position; + // + ushort version = input.ReadUInt16(); // 0 + ushort tableCount = input.ReadUInt16(); + + ushort[] platformIds = new ushort[tableCount]; + ushort[] encodingIds = new ushort[tableCount]; + uint[] offsets = new uint[tableCount]; + for (int i = 0; i < tableCount; i++) + { + platformIds[i] = input.ReadUInt16(); + encodingIds[i] = input.ReadUInt16(); + offsets[i] = input.ReadUInt32(); + } + + _charMaps = new CharacterMap[tableCount]; + for (int i = 0; i < tableCount; i++) + { + input.BaseStream.Seek(beginAt + offsets[i], SeekOrigin.Begin); + CharacterMap cmap = ReadCharacterMap(input); + cmap.PlatformId = platformIds[i]; + cmap.EncodingId = encodingIds[i]; + _charMaps[i] = cmap; + + // + if (cmap is CharMapFormat14 cmap14) + { + if (_charMap14List == null) _charMap14List = new List(); + // + _charMap14List.Add(cmap14); + } + } + } + + static CharacterMap ReadFormat_0(BinaryReader input) + { + ushort length = input.ReadUInt16(); + //Format 0: Byte encoding table + //This is the Apple standard character to glyph index mapping table. + //Type Name Description + //uint16 format Format number is set to 0. + //uint16 length This is the length in bytes of the subtable. + //uint16 language Please see “Note on the language field in 'cmap' subtables“ in this document. + //uint8 glyphIdArray[256] An array that maps character codes to glyph index values. + //----------- + //This is a simple 1 to 1 mapping of character codes to glyph indices. + //The glyph set is limited to 256. Note that if this format is used to index into a larger glyph set, + //only the first 256 glyphs will be accessible. + + ushort language = input.ReadUInt16(); + byte[] only256Glyphs = input.ReadBytes(256); + ushort[] only256UInt16Glyphs = new ushort[256]; + for (int i = 255; i >= 0; --i) + { + //expand + only256UInt16Glyphs[i] = only256Glyphs[i]; + } + //convert to format4 cmap table + ushort[] startArray = new ushort[] { 0, 0xFFFF }; + ushort[] endArray = new ushort[] { 255, 0xFFFF }; + ushort[] deltaArray = new ushort[] { 0, 1 }; + ushort[] offsetArray = new ushort[] { 4, 0 }; + return new CharMapFormat4(startArray, endArray, deltaArray, offsetArray, only256UInt16Glyphs); + } + + static CharacterMap ReadFormat_2(BinaryReader input) + { + //Format 2: High - byte mapping through table + + //This subtable is useful for the national character code standards used for Japanese, Chinese, and Korean characters. + //These code standards use a mixed 8 / 16 - bit encoding, + //in which certain byte values signal the first byte of a 2 - byte character(but these values are also legal as the second byte of a 2 - byte character). + // + //In addition, even for the 2 - byte characters, the mapping of character codes to glyph index values depends heavily on the first byte. + //Consequently, the table begins with an array that maps the first byte to a SubHeader record. + //For 2 - byte character codes, the SubHeader is used to map the second byte's value through a subArray, as described below. + //When processing mixed 8/16-bit text, SubHeader 0 is special: it is used for single-byte character codes. + //When SubHeader 0 is used, a second byte is not needed; the single byte value is mapped through the subArray. + //------------- + // 'cmap' Subtable Format 2: + //------------- + // Type Name Description + // uint16 format Format number is set to 2. + // uint16 length This is the length in bytes of the subtable. + // uint16 language Please see “Note on the language field in 'cmap' subtables“ in this document. + // uint16 subHeaderKeys[256] Array that maps high bytes to subHeaders: value is subHeader index * 8. + // SubHeader subHeaders[] Variable - length array of SubHeader records. + // uint16 glyphIndexArray[] Variable - length array containing subarrays used for mapping the low byte of 2 - byte characters. + //------------------ + // A SubHeader is structured as follows: + // SubHeader Record: + // Type Name Description + // uint16 firstCode First valid low byte for this SubHeader. + // uint16 entryCount Number of valid low bytes for this SubHeader. + // int16 idDelta See text below. + // uint16 idRangeOffset See text below. + // + // The firstCode and entryCount values specify a subrange that begins at firstCode and has a length equal to the value of entryCount. + //This subrange stays within the 0 - 255 range of the byte being mapped. + //Bytes outside of this subrange are mapped to glyph index 0(missing glyph). + //The offset of the byte within this subrange is then used as index into a corresponding subarray of glyphIndexArray. + //This subarray is also of length entryCount. + //The value of the idRangeOffset is the number of bytes past the actual location of the idRangeOffset word + //where the glyphIndexArray element corresponding to firstCode appears. + // Finally, if the value obtained from the subarray is not 0(which indicates the missing glyph), + //you should add idDelta to it in order to get the glyphIndex. + //The value idDelta permits the same subarray to be used for several different subheaders. + //The idDelta arithmetic is modulo 65536. + + Utils.WarnUnimplemented("cmap subtable format 2"); + return new NullCharMap(); + } + + static CharMapFormat4 ReadFormat_4(BinaryReader input) + { + ushort lenOfSubTable = input.ReadUInt16(); //This is the length in bytes of the subtable. **** + //This is the Microsoft standard character to glyph index mapping table for fonts that support Unicode ranges other than the range [U+D800 - U+DFFF] (defined as Surrogates Area, in Unicode v 3.0) + //which is used for UCS-4 characters. + //If a font supports this character range (i.e. in turn supports the UCS-4 characters) a subtable in this format with a platform specific encoding ID 1 is yet needed, + //in addition to a subtable in format 12 with a platform specific encoding ID 10. Please see details on format 12 below, for fonts that support UCS-4 characters on Windows. + // + //This format is used when the character codes for the characters represented by a font fall into several contiguous ranges, + //possibly with holes in some or all of the ranges (that is, some of the codes in a range may not have a representation in the font). + //The format-dependent data is divided into three parts, which must occur in the following order: + // A four-word header gives parameters for an optimized search of the segment list; + // Four parallel arrays describe the segments (one segment for each contiguous range of codes); + // A variable-length array of glyph IDs (unsigned words). + long tableStartEndAt = input.BaseStream.Position + lenOfSubTable; + + ushort language = input.ReadUInt16(); + //Note on the language field in 'cmap' subtables: + //The language field must be set to zero for all cmap subtables whose platform IDs are other than Macintosh (platform ID 1). + //For cmap subtables whose platform IDs are Macintosh, set this field to the Macintosh language ID of the cmap subtable plus one, + //or to zero if the cmap subtable is not language-specific. + //For example, a Mac OS Turkish cmap subtable must set this field to 18, since the Macintosh language ID for Turkish is 17. + //A Mac OS Roman cmap subtable must set this field to 0, since Mac OS Roman is not a language-specific encoding. + + ushort segCountX2 = input.ReadUInt16(); //2 * segCount + ushort searchRange = input.ReadUInt16(); //2 * (2**FLOOR(log2(segCount))) + ushort entrySelector = input.ReadUInt16();//2 * (2**FLOOR(log2(segCount))) + ushort rangeShift = input.ReadUInt16(); //2 * (2**FLOOR(log2(segCount))) + int segCount = segCountX2 / 2; + ushort[] endCode = Utils.ReadUInt16Array(input, segCount);//Ending character code for each segment, last = 0xFFFF. + //>To ensure that the search will terminate, the final endCode value must be 0xFFFF. + //>This segment need not contain any valid mappings. It can simply map the single character code 0xFFFF to the missing character glyph, glyph 0. + + ushort Reserved = input.ReadUInt16(); // always 0 + ushort[] startCode = Utils.ReadUInt16Array(input, segCount); //Starting character code for each segment + ushort[] idDelta = Utils.ReadUInt16Array(input, segCount); //Delta for all character codes in segment + ushort[] idRangeOffset = Utils.ReadUInt16Array(input, segCount); //Offset in bytes to glyph indexArray, or 0 + //------------------------------------------------------------------------------------ + long remainingLen = tableStartEndAt - input.BaseStream.Position; + int recordNum2 = (int)(remainingLen / 2); + ushort[] glyphIdArray = Utils.ReadUInt16Array(input, recordNum2);//Glyph index array + return new CharMapFormat4(startCode, endCode, idDelta, idRangeOffset, glyphIdArray); + } + + static CharMapFormat6 ReadFormat_6(BinaryReader input) + { + //Format 6: Trimmed table mapping + //Type Name Description + //uint16 format Format number is set to 6. + //uint16 length This is the length in bytes of the subtable. + //uint16 language Please see “Note on the language field in 'cmap' subtables“ in this document. + //uint16 firstCode First character code of subrange. + //uint16 entryCount Number of character codes in subrange. + //uint16 glyphIdArray[entryCount] Array of glyph index values for character codes in the range. + + //The firstCode and entryCount values specify a subrange(beginning at firstCode, length = entryCount) within the range of possible character codes. + //Codes outside of this subrange are mapped to glyph index 0. + //The offset of the code(from the first code) within this subrange is used as index to the glyphIdArray, + //which provides the glyph index value. + + ushort length = input.ReadUInt16(); + ushort language = input.ReadUInt16(); + ushort firstCode = input.ReadUInt16(); + ushort entryCount = input.ReadUInt16(); + ushort[] glyphIdArray = Utils.ReadUInt16Array(input, entryCount); + return new CharMapFormat6(firstCode, glyphIdArray); + } + + static CharacterMap ReadFormat_12(BinaryReader input) + { + //TODO: test this again + // Format 12: Segmented coverage + //This is the Microsoft standard character to glyph index mapping table for fonts supporting the UCS - 4 characters + //in the Unicode Surrogates Area(U + D800 - U + DFFF). + //It is a bit like format 4, in that it defines segments for sparse representation in 4 - byte character space. + //Here's the subtable format: + //'cmap' Subtable Format 12: + //Type Name Description + //uint16 format Subtable format; set to 12. + //uint16 reserved Reserved; set to 0 + //uint32 length Byte length of this subtable(including the header) + //uint32 language Please see “Note on the language field in 'cmap' subtables“ in this document. + //uint32 numGroups Number of groupings which follow + //SequentialMapGroup groups[numGroups] Array of SequentialMapGroup records. + // + //The sequential map group record is the same format as is used for the format 8 subtable. + //The qualifications regarding 16 - bit character codes does not apply here, + //however, since characters codes are uniformly 32 - bit. + //SequentialMapGroup Record: + //Type Name Description + //uint32 startCharCode First character code in this group + //uint32 endCharCode Last character code in this group + //uint32 startGlyphID Glyph index corresponding to the starting character code + // + //Groups must be sorted by increasing startCharCode.A group's endCharCode must be less than the startCharCode of the following group, + //if any. The endCharCode is used, rather than a count, because comparisons for group matching are usually done on an existing character code, + //and having the endCharCode be there explicitly saves the necessity of an addition per group. + // + //Fonts providing Unicode - encoded UCS - 4 character support for Windows 2000 and later, + //need to have a subtable with platform ID 3, platform specific encoding ID 1 in format 4; + //and in addition, need to have a subtable for platform ID 3, platform specific encoding ID 10 in format 12. + //Please note, that the content of format 12 subtable, + //needs to be a super set of the content in the format 4 subtable. + //The format 4 subtable needs to be in the cmap table to enable backward compatibility needs. + + ushort reserved = input.ReadUInt16(); +#if DEBUG + if (reserved != 0) { throw new OpenFontNotSupportedException(); } +#endif + + uint length = input.ReadUInt32();// Byte length of this subtable(including the header) + uint language = input.ReadUInt32(); + uint numGroups = input.ReadUInt32(); + +#if DEBUG + if (numGroups > int.MaxValue) { throw new OpenFontNotSupportedException(); } +#endif + uint[] startCharCodes = new uint[(int)numGroups]; + uint[] endCharCodes = new uint[(int)numGroups]; + uint[] startGlyphIds = new uint[(int)numGroups]; + + + for (uint i = 0; i < numGroups; ++i) + { + //seq map group record + startCharCodes[i] = input.ReadUInt32(); + endCharCodes[i] = input.ReadUInt32(); + startGlyphIds[i] = input.ReadUInt32(); + } + return new CharMapFormat12(startCharCodes, endCharCodes, startGlyphIds); + } + + private static CharacterMap ReadCharacterMap(BinaryReader input) + { + ushort format = input.ReadUInt16(); + switch (format) + { + default: + Utils.WarnUnimplemented("cmap subtable format {0}", format); + return new NullCharMap(); + case 0: return ReadFormat_0(input); + case 2: return ReadFormat_2(input); + case 4: return ReadFormat_4(input); + case 6: return ReadFormat_6(input); + case 12: return ReadFormat_12(input); + case 14: return CharMapFormat14.Create(input); + } + } + + public void CollectUnicode(List unicodes) + { + for (int i = 0; i < _charMaps.Length; ++i) + { + _charMaps[i].CollectUnicodeChars(unicodes); + } + } + public void CollectUnicode(int platform, List unicodes, List glyphIndexList) + { + if (_charMaps.Length == 1) + { + + } + for (int i = 0; i < _charMaps.Length; ++i) + { + CharacterMap cmap = _charMaps[i]; + if (platform < 0) + { + cmap.CollectUnicodeChars(unicodes, glyphIndexList); + } + else if (cmap.PlatformId == platform) + { + cmap.CollectUnicodeChars(unicodes, glyphIndexList); + } + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Head.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Head.cs new file mode 100644 index 00000000..e8369bff --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Head.cs @@ -0,0 +1,122 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + + class Head : TableEntry + { + + //https://docs.microsoft.com/en-us/typography/opentype/spec/head + //This table gives global information about the font. + //The bounding box values should be computed using only glyphs that have contours. + //Glyphs with no contours should be ignored for the purposes of these calculations. + + public const string _N = "head"; + public override string Name => _N; + // + short _indexToLocFormat; + + public Head() + { + } + protected override void ReadContentFrom(BinaryReader input) + { + + //Type Name Description + //uint16 majorVersion Major version number of the font header table — set to 1. + //uint16 minorVersion Minor version number of the font header table — set to 0. + //Fixed fontRevision Set by font manufacturer. + //uint32 checkSumAdjustment To compute: set it to 0, sum the entire font as uint32, then store 0xB1B0AFBA - sum. + // If the font is used as a component in a font collection file, + // the value of this field will be invalidated by changes to the file structure and font table directory, and must be ignored. + //uint32 magicNumber Set to 0x5F0F3CF5. + //uint16 flags Bit 0: Baseline for font at y=0; + + // Bit 1: Left sidebearing point at x=0 (relevant only for TrueType rasterizers) — see the note below regarding variable fonts; + + // Bit 2: Instructions may depend on point size; + + // Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear; + + // Bit 4: Instructions may alter advance width (the advance widths might not scale linearly); + + // Bit 5: This bit is not used in OpenType, and should not be set in order to ensure compatible behavior on all platforms. If set, it may result in different behavior for vertical layout in some platforms. (See Apple’s specification for details regarding behavior in Apple platforms.) + + // Bits 6–10: These bits are not used in Opentype and should always be cleared. (See Apple’s specification for details regarding legacy used in Apple platforms.) + + // Bit 11: Font data is “lossless” as a result of having been subjected to optimizing transformation and/or compression (such as e.g. compression mechanisms defined by ISO/IEC 14496-18, MicroType Express, WOFF 2.0 or similar) where the original font functionality and features are retained but the binary compatibility between input and output font files is not guaranteed. As a result of the applied transform, the DSIG table may also be invalidated. + + // Bit 12: Font converted (produce compatible metrics) + + // Bit 13: Font optimized for ClearType™. Note, fonts that rely on embedded bitmaps (EBDT) for rendering should not be considered optimized for ClearType, and therefore should keep this bit cleared. + + // Bit 14: Last Resort font. If set, indicates that the glyphs encoded in the 'cmap' subtables are simply generic symbolic representations of code point ranges and don’t truly represent support for those code points. If unset, indicates that the glyphs encoded in the 'cmap' subtables represent proper support for those code points. + + // Bit 15: Reserved, set to 0 + //uint16 unitsPerEm Set to a value from 16 to 16384. Any value in this range is valid. + // In fonts that have TrueType outlines, a power of 2 is recommended as this allows performance optimizations in some rasterizers. + //LONGDATETIME created Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone. 64-bit integer + //LONGDATETIME modified Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone. 64-bit integer + //int16 xMin For all glyph bounding boxes. + //int16 yMin For all glyph bounding boxes. + //int16 xMax For all glyph bounding boxes. + //int16 yMax For all glyph bounding boxes. + //uint16 macStyle Bit 0: Bold (if set to 1); + // Bit 1: Italic (if set to 1) + // Bit 2: Underline (if set to 1) + // Bit 3: Outline (if set to 1) + // Bit 4: Shadow (if set to 1) + // Bit 5: Condensed (if set to 1) + // Bit 6: Extended (if set to 1) + // Bits 7–15: Reserved (set to 0). + //uint16 lowestRecPPEM Smallest readable size in pixels. + //int16 fontDirectionHint Deprecated (Set to 2). + // 0: Fully mixed directional glyphs; + // 1: Only strongly left to right; + // 2: Like 1 but also contains neutrals; + // -1: Only strongly right to left; + // -2: Like -1 but also contains neutrals. + + //(A neutral character has no inherent directionality; it is not a character with zero (0) width. Spaces and punctuation are examples of neutral characters. Non-neutral characters are those with inherent directionality. For example, Roman letters (left-to-right) and Arabic letters (right-to-left) have directionality. In a “normal” Roman font where spaces and punctuation are present, the font direction hints should be set to two (2).) + //int16 indexToLocFormat 0 for short offsets (Offset16), 1 for long (Offset32). + //int16 glyphDataFormat 0 for current format. + + + Version = input.ReadUInt32(); // 0x00010000 for version 1.0. + FontRevision = input.ReadUInt32(); + CheckSumAdjustment = input.ReadUInt32(); + MagicNumber = input.ReadUInt32(); + if (MagicNumber != 0x5F0F3CF5) throw new Exception("Invalid magic number!" + MagicNumber.ToString("x")); + + Flags = input.ReadUInt16(); + UnitsPerEm = input.ReadUInt16(); // valid is 16 to 16384 + Created = input.ReadUInt64(); // International date (8-byte field). (?) + Modified = input.ReadUInt64(); + // bounding box for all glyphs + Bounds = Utils.ReadBounds(input); + MacStyle = input.ReadUInt16(); + LowestRecPPEM = input.ReadUInt16(); + FontDirectionHint = input.ReadInt16(); + _indexToLocFormat = input.ReadInt16(); // 0 for 16-bit offsets, 1 for 32-bit. + GlyphDataFormat = input.ReadInt16(); // 0 + } + + public uint Version { get; private set; } + public uint FontRevision { get; private set; } + public uint CheckSumAdjustment { get; private set; } + public uint MagicNumber { get; private set; } + public ushort Flags { get; private set; } + public ushort UnitsPerEm { get; private set; } + public ulong Created { get; private set; } + public ulong Modified { get; private set; } + public Bounds Bounds { get; private set; } + public ushort MacStyle { get; private set; } + public ushort LowestRecPPEM { get; private set; } + public short FontDirectionHint { get; private set; } + public bool WideGlyphLocations => _indexToLocFormat > 0; + public short GlyphDataFormat { get; private set; } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalHeader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalHeader.cs new file mode 100644 index 00000000..f3602077 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalHeader.cs @@ -0,0 +1,79 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + /// + /// hhea + /// + class HorizontalHeader : TableEntry + { + public const string _N = "hhea"; + public override string Name => _N; + + //https://docs.microsoft.com/en-us/typography/opentype/spec/hhea + //hhea — Horizontal Header Table + //----- + // Type Name Description + //uint16 majorVersion Major version number of the horizontal header table — set to 1. + //uint16 minorVersion Minor version number of the horizontal header table — set to 0. + //FWORD Ascender Typographic ascent(Distance from baseline of highest ascender). + //FWORD Descender Typographic descent(Distance from baseline of lowest descender). + //FWORD LineGap Typographic line gap. + // Negative LineGap values are treated as zero in Windows 3.1, and in Mac OS System 6 and System 7. + //UFWORD advanceWidthMax Maximum advance width value in 'hmtx' table. + //FWORD minLeftSideBearing Minimum left sidebearing value in 'hmtx' table. + //FWORD minRightSideBearing Minimum right sidebearing value; calculated as Min(aw - lsb - (xMax - xMin)). + //FWORD xMaxExtent Max(lsb + (xMax - xMin)). + //int16 caretSlopeRise Used to calculate the slope of the cursor(rise/run); 1 for vertical. + //int16 caretSlopeRun 0 for vertical. + //int16 caretOffset The amount by which a slanted highlight on a glyph needs to be shifted to produce the best appearance.Set to 0 for non-slanted fonts + //int16 (reserved) set to 0 + //int16 (reserved) set to 0 + //int16 (reserved) set to 0 + //int16 (reserved) set to 0 + //int16 metricDataFormat 0 for current format. + //uint16 numberOfHMetrics Number of hMetric entries in 'hmtx' table + + public HorizontalHeader() + { + } + protected override void ReadContentFrom(BinaryReader input) + { + Version = input.ReadUInt32(); //major + minor + Ascent = input.ReadInt16(); + Descent = input.ReadInt16(); + LineGap = input.ReadInt16(); + + AdvancedWidthMax = input.ReadUInt16(); + MinLeftSideBearing = input.ReadInt16(); + MinRightSideBearing = input.ReadInt16(); + MaxXExtent = input.ReadInt16(); + + CaretSlopRise = input.ReadInt16(); + CaretSlopRun = input.ReadInt16(); + CaretOffset = input.ReadInt16(); + + //reserve 4 int16 fields, int16 x 4 fields + input.BaseStream.Seek(2 * 4, SeekOrigin.Current); + + MetricDataFormat = input.ReadInt16(); // 0 + NumberOfHMetrics = input.ReadUInt16(); + } + public uint Version { get; private set; } + public short Ascent { get; private set; } + public short Descent { get; private set; } + public short LineGap { get; private set; } + public ushort AdvancedWidthMax { get; private set; } + public short MinLeftSideBearing { get; private set; } + public short MinRightSideBearing { get; private set; } + public short MaxXExtent { get; private set; } + public short CaretSlopRise { get; private set; } + public short CaretSlopRun { get; private set; } + public short CaretOffset { get; private set; } + public short MetricDataFormat { get; private set; } + public ushort NumberOfHMetrics { get; private set; } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalMetrics.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalMetrics.cs new file mode 100644 index 00000000..70d64d54 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/HorizontalMetrics.cs @@ -0,0 +1,100 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.Collections.Generic; +using System.IO; +namespace Typography.OpenFont.Tables +{ + /// + /// hmtx + /// + class HorizontalMetrics : TableEntry + { + public const string _N = "hmtx"; + public override string Name => _N; + // + //https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx + // A font rendering engine must use the advanceWidths in the hmtx table for the advances of a CFF OFF font, + //even though the CFF table specifies its own glyph widths.//*** + + //Note that fonts in a Font Collection which share a CFF table may specify different advanceWidths in their hmtx table for a particular glyph index. + //For any glyph, xmax and xmin are given in 'glyf' table, lsb and aw are given in 'hmtx' table. rsb is calculated as follows: + // rsb = aw - (lsb + xmax - xmin) + //If pp1 and pp2 are phantom points used to control lsb and rsb, their initial position in x is calculated as follows: + // pp1 = xmin - lsb + // pp2 = pp1 + aw + + + //NOTE: + //lsb=> left-side bearing + //rsb=> right-side bearing + //aw=> advance width + + readonly ushort[] _advanceWidths; //in font design unit + readonly short[] _leftSideBearings;//lsb, in font design unit + readonly int _numOfHMetrics; + readonly int _numGlyphs; + public HorizontalMetrics(ushort numOfHMetrics, ushort numGlyphs) + { + //The value numOfHMetrics comes from the 'hhea' table** + _advanceWidths = new ushort[numGlyphs]; + _leftSideBearings = new short[numGlyphs]; + _numOfHMetrics = numOfHMetrics; + _numGlyphs = numGlyphs; +#if DEBUG + if (numGlyphs < numOfHMetrics) + { + throw new OpenFontNotSupportedException(); + } +#endif + } + + public ushort GetAdvanceWidth(ushort glyphIndex) => _advanceWidths[glyphIndex]; + + public short GetLeftSideBearing(ushort glyphIndex) => _leftSideBearings[glyphIndex]; + + public void GetHMetric(ushort glyphIndex, out ushort advWidth, out short lsb) + { + advWidth = _advanceWidths[glyphIndex]; + lsb = _leftSideBearings[glyphIndex]; + //TODO: calculate other value? + } + protected override void ReadContentFrom(BinaryReader input) + { + //=============================================================================== + //1. hMetrics : have both advance width and leftSideBearing(lsb) + //Paired advance width and left side bearing values for each glyph. + //The value numOfHMetrics comes from the 'hhea' table** + //If the font is monospaced, only one entry need be in the array, + //but that entry is required. The last entry applies to all subsequent glyphs + + int gid = 0; //gid=> glyphIndex + + int numOfHMetrics = _numOfHMetrics; + for (int i = 0; i < numOfHMetrics; i++) + { + _advanceWidths[gid] = input.ReadUInt16(); + _leftSideBearings[gid] = input.ReadInt16(); + + gid++;//*** + } + + //=============================================================================== + //2. (only) LeftSideBearing: (same advanced width (eg. monospace font), vary only left side bearing) + //Here the advanceWidth is assumed to be the same as the advanceWidth for the last entry above. + //The number of entries in this array is derived from numGlyphs (from 'maxp' table) minus numberOfHMetrics. + + int nEntries = _numGlyphs - numOfHMetrics; + ushort advanceWidth = _advanceWidths[numOfHMetrics - 1]; + + for (int i = 0; i < nEntries; i++) + { + _advanceWidths[gid] = advanceWidth; + _leftSideBearings[gid] = input.ReadInt16(); + + gid++;//*** + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/MaxProfile.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/MaxProfile.cs new file mode 100644 index 00000000..a44f3773 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/MaxProfile.cs @@ -0,0 +1,79 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + + +using System.IO; +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/maxp + class MaxProfile : TableEntry + { + public const string _N = "maxp"; + public override string Name => _N; + + //This table establishes the memory requirements for this font. + //Fonts with CFF data must use Version 0.5 of this table, + //specifying only the numGlyphs field. + + //Fonts with TrueType outlines must use Version 1.0 of this table, where all data is required. + + //Version 0.5 + //Type Name Description + //Fixed version 0x00005000 for version 0.5 + // (Note the difference in the representation of a non-zero fractional part, in Fixed numbers.) + //uint16 numGlyphs The number of glyphs in the font. + + //Version 1.0 + //Type Name Description + //Fixed version 0x00010000 for version 1.0. + //uint16 numGlyphs The number of glyphs in the font. + //uint16 maxPoints Maximum points in a non-composite glyph. + //uint16 maxContours Maximum contours in a non-composite glyph. + //uint16 maxCompositePoints Maximum points in a composite glyph. + //uint16 maxCompositeContours Maximum contours in a composite glyph. + //uint16 maxZones 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases. + //uint16 maxTwilightPoints Maximum points used in Z0. + //uint16 maxStorage Number of Storage Area locations. + //uint16 maxFunctionDefs Number of FDEFs, equal to the highest function number + 1. + //uint16 maxInstructionDefs Number of IDEFs. + //uint16 maxStackElements Maximum stack depth across Font Program ('fpgm' table), CVT Program('prep' table) and all glyph instructions(in the 'glyf' table). + //uint16 maxSizeOfInstructions Maximum byte count for glyph instructions. + //uint16 maxComponentElements Maximum number of components referenced at “top level” for any composite glyph. + //uint16 maxComponentDepth Maximum levels of recursion; 1 for simple components. + + public uint Version { get; private set; } + public ushort GlyphCount { get; private set; } + public ushort MaxPointsPerGlyph { get; private set; } + public ushort MaxContoursPerGlyph { get; private set; } + public ushort MaxPointsPerCompositeGlyph { get; private set; } + public ushort MaxContoursPerCompositeGlyph { get; private set; } + public ushort MaxZones { get; private set; } + public ushort MaxTwilightPoints { get; private set; } + public ushort MaxStorage { get; private set; } + public ushort MaxFunctionDefs { get; private set; } + public ushort MaxInstructionDefs { get; private set; } + public ushort MaxStackElements { get; private set; } + public ushort MaxSizeOfInstructions { get; private set; } + public ushort MaxComponentElements { get; private set; } + public ushort MaxComponentDepth { get; private set; } + + protected override void ReadContentFrom(BinaryReader input) + { + Version = input.ReadUInt32(); // 0x00010000 == 1.0 + GlyphCount = input.ReadUInt16(); + MaxPointsPerGlyph = input.ReadUInt16(); + MaxContoursPerGlyph = input.ReadUInt16(); + MaxPointsPerCompositeGlyph = input.ReadUInt16(); + MaxContoursPerCompositeGlyph = input.ReadUInt16(); + MaxZones = input.ReadUInt16(); + MaxTwilightPoints = input.ReadUInt16(); + MaxStorage = input.ReadUInt16(); + MaxFunctionDefs = input.ReadUInt16(); + MaxInstructionDefs = input.ReadUInt16(); + MaxStackElements = input.ReadUInt16(); + MaxSizeOfInstructions = input.ReadUInt16(); + MaxComponentElements = input.ReadUInt16(); + MaxComponentDepth = input.ReadUInt16(); + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/NameEntry.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/NameEntry.cs new file mode 100644 index 00000000..e2f139c4 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/NameEntry.cs @@ -0,0 +1,193 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System.IO; +using System.Text; +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/name + + public class NameEntry : TableEntry + { + public const string _N = "name"; + public override string Name => _N; + // + protected override void ReadContentFrom(BinaryReader reader) + { + + ushort uFSelector = reader.ReadUInt16(); + ushort uNRCount = reader.ReadUInt16(); + ushort uStorageOffset = reader.ReadUInt16(); + + uint offset = this.Header.Offset; + for (int j = 0; j <= uNRCount; j++) + { + var ttRecord = new TT_NAME_RECORD() + { + uPlatformID = reader.ReadUInt16(), + uEncodingID = reader.ReadUInt16(), + uLanguageID = reader.ReadUInt16(), + uNameID = reader.ReadUInt16(), + uStringLength = reader.ReadUInt16(), + uStringOffset = reader.ReadUInt16(), + }; + + + long nPos = reader.BaseStream.Position; + reader.BaseStream.Seek(offset + ttRecord.uStringOffset + uStorageOffset, SeekOrigin.Begin); + + byte[] buf = reader.ReadBytes(ttRecord.uStringLength); + Encoding enc2; + if (ttRecord.uEncodingID == 3 || ttRecord.uEncodingID == 1) + { + + enc2 = Encoding.BigEndianUnicode; + } + else + { + enc2 = Encoding.UTF8; + } + string strRet = enc2.GetString(buf, 0, buf.Length); + //.... + switch ((NameIdKind)ttRecord.uNameID) + { + default: + //skip + break; + case NameIdKind.VersionString: + VersionString = strRet; + break; + case NameIdKind.FontFamilyName: + FontName = strRet; + break; + case NameIdKind.FontSubfamilyName: + FontSubFamily = strRet; + break; + case NameIdKind.UniqueFontIden: + UniqueFontIden = strRet; + break; + case NameIdKind.FullFontName: + FullFontName = strRet; + break; + // + case NameIdKind.PostScriptName: + PostScriptName = strRet; + break; + case NameIdKind.PostScriptCID_FindfontName: + PostScriptCID_FindfontName = strRet; + break; + // + case NameIdKind.TypographicFamilyName: + TypographicFamilyName = strRet; + break; + case NameIdKind.TypographyicSubfamilyName: + TypographyicSubfamilyName = strRet; + break; + + } + //move to saved pos + reader.BaseStream.Seek(nPos, SeekOrigin.Begin); + } + } + + + /// + /// Font Family name. + /// This family name is assumed to be shared among fonts that + /// differ only in weight or style (italic, oblique). + /// + /// Font Family name is used in combination with Font Subfamily name (name ID 2)... + /// + public string FontName { get; private set; } + /// + /// Font Subfamily name. The Font Subfamily name distinguishes the fonts in a group with the + /// same Font Family name (name ID 1). + /// This is assumed to address style (italic, oblique) and weight variants only. + /// + /// A font with no distinctive weight or style (e.g. medium weight, not italic, and OS/2.fsSelection bit 6 set) + /// should use the string “Regular” as the Font Subfamily name (for English language). + /// + public string FontSubFamily { get; private set; } + public string UniqueFontIden { get; private set; } + /// + /// Full font name that reflects all family and relevant subfamily descriptors. + /// The full font name is generally a combination of name IDs 1 and 2, or + /// of name IDs 16 and 17, or a similar human-readable variant. + /// + public string FullFontName { get; set; } + + public string VersionString { get; set; } + + /// + /// PostScript name for the font; Name ID 6 specifies a string which is used to invoke a PostScript language font that corresponds to this OpenType font. + /// When translated to ASCII, the name string must be no longer than 63 characters and restricted to the printable ASCII subset, + /// codes 33 to 126, except for the 10 characters '[', ']', '(', ')', '{', '}', '<', '>', '/', '%'. + /// + ///In a CFF OpenType font, there is no requirement that this name be the same as the font name in the CFF’s Name INDEX.Thus, + ///the same CFF may be shared among multiple font components in a Font Collection. + ///... + /// + public string PostScriptName { get; set; } + public string PostScriptCID_FindfontName { get; set; } + // + public string TypographicFamilyName { get; set; } + public string TypographyicSubfamilyName { get; set; } + + + struct TT_NAME_RECORD + { + public ushort uPlatformID; + public ushort uEncodingID; + public ushort uLanguageID; + public ushort uNameID; + public ushort uStringLength; + public ushort uStringOffset; + } + + + + enum NameIdKind + { + //... + //[A] The key information for this table for Microsoft platforms + //relates to the use of strings 1, 2, 4, 16 and 17. + //... + + + CopyRightNotice, //0 + FontFamilyName, //1 , [A] + FontSubfamilyName,//2, [A] + UniqueFontIden, //3 + FullFontName, //4, [A] + VersionString,//5 + PostScriptName,//6 + Trademark,//7 + ManufacturerName,//8 + Designer,//9 + Description, //10 + UrlVendor, //11 + UrlDesigner,//12 + LicenseDescription, //13 + LicenseInfoUrl,//14 + Reserved,//15 + TypographicFamilyName,//16 , [A] + TypographyicSubfamilyName,//17, [A] + CompatibleFull,//18 + SampleText,//19 + PostScriptCID_FindfontName,//20 + //------------------ + WWSFamilyName,//21 + WWSSubfamilyName,//22 + //------------------ + LightBackgroundPalette,//23, CPAL + DarkBackgroundPalette,//24, CPAL + //------------------ + VariationsPostScriptNamePrefix,//25 + + } + + + + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/OS2.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/OS2.cs new file mode 100644 index 00000000..df729cdb --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/OS2.cs @@ -0,0 +1,622 @@ +//Apache2, 2016-present, WinterDev +using System.IO; + +namespace Typography.OpenFont.Tables +{ + + //https://docs.microsoft.com/en-us/typography/opentype/spec/os2 + /// + /// OS2 and Windows metrics, + /// consists of a set of metrics and other data + /// that are REQUIRED in OpenType fonts. + /// + public class OS2Table : TableEntry + { + public const string _N = "OS/2"; + public override string Name => _N; + // + + // Type Name of Entry Comments + //uint16 version 0x0005 + //int16 xAvgCharWidth + //uint16 usWeightClass + //uint16 usWidthClass + //uint16 fsType + public ushort version; //0-5 + public short xAvgCharWidth; //just average, not recommend to use. + public ushort usWeightClass; //visual weight (degree of blackness or thickness of strokes), 0-1000 + + //usWeightClass: + //Value Description C Definition (from windows.h) + //100 Thin FW_THIN + //200 Extra-light FW_EXTRALIGHT + // (Ultra-light) + //300 Light FW_LIGHT + //400 Normal FW_NORMAL + // (Regular) + //500 Medium FW_MEDIUM + //600 Semi-bold FW_SEMIBOLD + // (Demi-bold) + //700 Bold FW_BOLD + //800 Extra-bold FW_EXTRABOLD + // (Ultra-bold) + //900 Black (Heavy) FW_BLACK + + public ushort usWidthClass; //A relative change from the normal aspect ratio (width to height ratio), + //as specified by a font designer for the glyphs in a font. + //Although every glyph in a font may have a different numeric aspect ratio, + //each glyph in a font of normal width is considered to have a relative aspect ratio of one. + //When a new type style is created of a different width class (either by a font designer or by some automated means) + //the relative aspect ratio of the characters in the new font is some percentage greater or less than those same characters in the normal + //font — it is this difference that this parameter specifies. + + //usWidthClass + //Value Description C Definition % of normal + //1 Ultra-condensed FWIDTH_ULTRA_CONDENSED 50 + //2 Extra-condensed FWIDTH_EXTRA_CONDENSED 62.5 + //3 Condensed FWIDTH_CONDENSED 75 + //4 Semi-condensed FWIDTH_SEMI_CONDENSED 87.5 + //5 Medium (normal) FWIDTH_NORMAL 100 + //6 Semi-expanded FWIDTH_SEMI_EXPANDED 112.5 + //7 Expanded FWIDTH_EXPANDED 125 + //8 Extra-expanded FWIDTH_EXTRA_EXPANDED 150 + //9 Ultra-expanded FWIDTH_ULTRA_EXPANDED 200 + + + + + + + + + public ushort fsType; //Type flags., embedding licensing rights for the font + + //int16 ySubscriptXSize + //int16 ySubscriptYSize + //int16 ySubscriptXOffset + //int16 ySubscriptYOffset + //int16 ySuperscriptXSize + //int16 ySuperscriptYSize + //int16 ySuperscriptXOffset + //int16 ySuperscriptYOffset + //int16 yStrikeoutSize + //int16 yStrikeoutPosition + //int16 sFamilyClass + public short ySubscriptXSize; + public short ySubscriptYSize; + public short ySubscriptXOffset; + public short ySubscriptYOffset; + public short ySuperscriptXSize; + public short ySuperscriptYSize; + public short ySuperscriptXOffset; + public short ySuperscriptYOffset; + public short yStrikeoutSize; + public short yStrikeoutPosition; + public short sFamilyClass; //This parameter is a classification of font-family design. ,see https://www.microsoft.com/typography/otspec/ibmfc.htm + + //uint8 panose[10] (array of bytes,len =10) + public byte[] panose; + //uint32 ulUnicodeRange1 Bits 0-31 + //uint32 ulUnicodeRange2 Bits 32-63 + //uint32 ulUnicodeRange3 Bits 64-95 + //uint32 ulUnicodeRange4 Bits 96-127 + public uint ulUnicodeRange1; + public uint ulUnicodeRange2; + public uint ulUnicodeRange3; + public uint ulUnicodeRange4; + + //Tag achVendID[4] char 4 + public uint achVendID; //see 'registered venders' at https://www.microsoft.com/typography/links/vendorlist.aspx + + //uint16 fsSelection + //uint16 usFirstCharIndex + //uint16 usLastCharIndex + public ushort fsSelection; //Contains information concerning the nature of the font patterns + public ushort usFirstCharIndex; + public ushort usLastCharIndex; + //int16 sTypoAscender + //int16 sTypoDescender + //int16 sTypoLineGap + public short sTypoAscender; + public short sTypoDescender; + public short sTypoLineGap; + //uint16 usWinAscent + //uint16 usWinDescent + //uint32 ulCodePageRange1 Bits 0-31 + //uint32 ulCodePageRange2 Bits 32-63 + public ushort usWinAscent; + public ushort usWinDescent; + public uint ulCodePageRange1; + public uint ulCodePageRange2; + //int16 sxHeight + //int16 sCapHeight + public short sxHeight; + public short sCapHeight; + //uint16 usDefaultChar + //uint16 usBreakChar + //uint16 usMaxContext + //uint16 usLowerOpticalPointSize + //uint16 usUpperOpticalPointSize + public ushort usDefaultChar; + public ushort usBreakChar; + public ushort usMaxContext; + public ushort usLowerOpticalPointSize; + public ushort usUpperOpticalPointSize; + + +#if DEBUG + public override string ToString() + { + return version + "," + Utils.TagToString(this.achVendID); + } +#endif + protected override void ReadContentFrom(BinaryReader reader) + { + //Six versions of the OS/2 table have been defined: versions 0 to 5 + //Versions 0 to 4 were defined in earlier versions of the OpenType or + //TrueType specifications. + + switch (this.version = reader.ReadUInt16()) + { + default: throw new System.NotSupportedException(); + case 0: //defined in TrueType revision 1.5 + ReadVersion0(reader); + break; + case 1: // defined in TrueType revision 1.66 + ReadVersion1(reader); + break; + case 2: //defined in OpenType version 1.2 + ReadVersion2(reader); + break; + case 3: //defined in OpenType version 1.4 + ReadVersion3(reader); + break; + case 4: //defined in OpenType version 1.6 + ReadVersion4(reader); + break; + case 5: + ReadVersion5(reader); + break; + } + } + void ReadVersion0(BinaryReader reader) + { + //https://www.microsoft.com/typography/otspec/os2ver0.htm + //USHORT version 0x0000 + //SHORT xAvgCharWidth + //USHORT usWeightClass + //USHORT usWidthClass + //USHORT fsType + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulCharRange[4] Bits 0-31 + this.ulUnicodeRange1 = reader.ReadUInt32(); + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + } + + void ReadVersion1(BinaryReader reader) + { + //https://www.microsoft.com/typography/otspec/os2ver1.htm + + //SHORT xAvgCharWidth + //USHORT usWeightClass + //USHORT usWidthClass + //USHORT fsType + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + this.ulUnicodeRange1 = reader.ReadUInt32(); + this.ulUnicodeRange2 = reader.ReadUInt32(); + this.ulUnicodeRange3 = reader.ReadUInt32(); + this.ulUnicodeRange4 = reader.ReadUInt32(); + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + //ULONG ulCodePageRange1 Bits 0-31 + //ULONG ulCodePageRange2 Bits 32-63 + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + this.ulCodePageRange1 = reader.ReadUInt32(); + this.ulCodePageRange2 = reader.ReadUInt32(); + } + void ReadVersion2(BinaryReader reader) + { + //https://www.microsoft.com/typography/otspec/os2ver2.htm + + // + //SHORT xAvgCharWidth + //USHORT usWeightClass + //USHORT usWidthClass + //USHORT fsType + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + this.ulUnicodeRange1 = reader.ReadUInt32(); + this.ulUnicodeRange2 = reader.ReadUInt32(); + this.ulUnicodeRange3 = reader.ReadUInt32(); + this.ulUnicodeRange4 = reader.ReadUInt32(); + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + //ULONG ulCodePageRange1 Bits 0-31 + //ULONG ulCodePageRange2 Bits 32-63 + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + this.ulCodePageRange1 = reader.ReadUInt32(); + this.ulCodePageRange2 = reader.ReadUInt32(); + //SHORT sxHeight + //SHORT sCapHeight + //USHORT usDefaultChar + //USHORT usBreakChar + //USHORT usMaxContext + this.sxHeight = reader.ReadInt16(); + this.sCapHeight = reader.ReadInt16(); + this.usDefaultChar = reader.ReadUInt16(); + this.usBreakChar = reader.ReadUInt16(); + this.usMaxContext = reader.ReadUInt16(); + } + void ReadVersion3(BinaryReader reader) + { + + //https://www.microsoft.com/typography/otspec/os2ver3.htm + // USHORT version 0x0003 + //SHORT xAvgCharWidth + //USHORT usWeightClass + //USHORT usWidthClass + //USHORT fsType + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + this.ulUnicodeRange1 = reader.ReadUInt32(); + this.ulUnicodeRange2 = reader.ReadUInt32(); + this.ulUnicodeRange3 = reader.ReadUInt32(); + this.ulUnicodeRange4 = reader.ReadUInt32(); + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + //ULONG ulCodePageRange1 Bits 0-31 + //ULONG ulCodePageRange2 Bits 32-63 + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + this.ulCodePageRange1 = reader.ReadUInt32(); + this.ulCodePageRange2 = reader.ReadUInt32(); + //SHORT sxHeight + //SHORT sCapHeight + //USHORT usDefaultChar + //USHORT usBreakChar + //USHORT usMaxContext + this.sxHeight = reader.ReadInt16(); + this.sCapHeight = reader.ReadInt16(); + this.usDefaultChar = reader.ReadUInt16(); + this.usBreakChar = reader.ReadUInt16(); + this.usMaxContext = reader.ReadUInt16(); + } + void ReadVersion4(BinaryReader reader) + { + //https://www.microsoft.com/typography/otspec/os2ver4.htm + + //SHORT xAvgCharWidth + //USHORT usWeightClass + //USHORT usWidthClass + //USHORT fsType + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + this.ulUnicodeRange1 = reader.ReadUInt32(); + this.ulUnicodeRange2 = reader.ReadUInt32(); + this.ulUnicodeRange3 = reader.ReadUInt32(); + this.ulUnicodeRange4 = reader.ReadUInt32(); + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + //ULONG ulCodePageRange1 Bits 0-31 + //ULONG ulCodePageRange2 Bits 32-63 + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + this.ulCodePageRange1 = reader.ReadUInt32(); + this.ulCodePageRange2 = reader.ReadUInt32(); + //SHORT sxHeight + //SHORT sCapHeight + //USHORT usDefaultChar + //USHORT usBreakChar + //USHORT usMaxContext + this.sxHeight = reader.ReadInt16(); + this.sCapHeight = reader.ReadInt16(); + this.usDefaultChar = reader.ReadUInt16(); + this.usBreakChar = reader.ReadUInt16(); + this.usMaxContext = reader.ReadUInt16(); + } + + void ReadVersion5(BinaryReader reader) + { + this.xAvgCharWidth = reader.ReadInt16(); + this.usWeightClass = reader.ReadUInt16(); + this.usWidthClass = reader.ReadUInt16(); + this.fsType = reader.ReadUInt16(); + //SHORT ySubscriptXSize + //SHORT ySubscriptYSize + //SHORT ySubscriptXOffset + //SHORT ySubscriptYOffset + //SHORT ySuperscriptXSize + //SHORT ySuperscriptYSize + //SHORT ySuperscriptXOffset + //SHORT ySuperscriptYOffset + //SHORT yStrikeoutSize + //SHORT yStrikeoutPosition + //SHORT sFamilyClass + this.ySubscriptXSize = reader.ReadInt16(); + this.ySubscriptYSize = reader.ReadInt16(); + this.ySubscriptXOffset = reader.ReadInt16(); + this.ySubscriptYOffset = reader.ReadInt16(); + this.ySuperscriptXSize = reader.ReadInt16(); + this.ySuperscriptYSize = reader.ReadInt16(); + this.ySuperscriptXOffset = reader.ReadInt16(); + this.ySuperscriptYOffset = reader.ReadInt16(); + this.yStrikeoutSize = reader.ReadInt16(); + this.yStrikeoutPosition = reader.ReadInt16(); + this.sFamilyClass = reader.ReadInt16(); + + //BYTE panose[10] + this.panose = reader.ReadBytes(10); + //ULONG ulUnicodeRange1 Bits 0-31 + //ULONG ulUnicodeRange2 Bits 32-63 + //ULONG ulUnicodeRange3 Bits 64-95 + //ULONG ulUnicodeRange4 Bits 96-127 + this.ulUnicodeRange1 = reader.ReadUInt32(); + this.ulUnicodeRange2 = reader.ReadUInt32(); + this.ulUnicodeRange3 = reader.ReadUInt32(); + this.ulUnicodeRange4 = reader.ReadUInt32(); + + //CHAR achVendID[4] + this.achVendID = reader.ReadUInt32(); + //USHORT fsSelection + //USHORT usFirstCharIndex + //USHORT usLastCharIndex + this.fsSelection = reader.ReadUInt16(); + this.usFirstCharIndex = reader.ReadUInt16(); + this.usLastCharIndex = reader.ReadUInt16(); + //SHORT sTypoAscender + //SHORT sTypoDescender + //SHORT sTypoLineGap + this.sTypoAscender = reader.ReadInt16(); + this.sTypoDescender = reader.ReadInt16(); + this.sTypoLineGap = reader.ReadInt16(); + //USHORT usWinAscent + //USHORT usWinDescent + //ULONG ulCodePageRange1 Bits 0-31 + //ULONG ulCodePageRange2 Bits 32-63 + this.usWinAscent = reader.ReadUInt16(); + this.usWinDescent = reader.ReadUInt16(); + this.ulCodePageRange1 = reader.ReadUInt32(); + this.ulCodePageRange2 = reader.ReadUInt32(); + //SHORT sxHeight + //SHORT sCapHeight + //USHORT usDefaultChar + //USHORT usBreakChar + //USHORT usMaxContext + this.sxHeight = reader.ReadInt16(); + this.sCapHeight = reader.ReadInt16(); + this.usDefaultChar = reader.ReadUInt16(); + this.usBreakChar = reader.ReadUInt16(); + this.usMaxContext = reader.ReadUInt16(); + //USHORT usLowerOpticalPointSize + //USHORT usUpperOpticalPointSize + + this.usLowerOpticalPointSize = reader.ReadUInt16(); + this.usUpperOpticalPointSize = reader.ReadUInt16(); + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Post.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Post.cs new file mode 100644 index 00000000..2e421a58 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Post.cs @@ -0,0 +1,214 @@ +//Apache2, 2016-present, WinterDev + +using System.IO; +using System.Collections.Generic; + +namespace Typography.OpenFont.Tables +{ + //https://docs.microsoft.com/en-us/typography/opentype/spec/post + + //post — PostScript Table + //This table contains additional information needed to use TrueType or OpenType™ fonts on PostScript printers. + //This includes data for the FontInfo dictionary entry and the PostScript names of all the glyphs. + //For more information about PostScript names, see the Adobe document Unicode and Glyph Names. + + //Versions 1.0, 2.0, and 2.5 refer to TrueType fonts and OpenType fonts with TrueType data. + //OpenType fonts with TrueType data may also use Version 3.0. OpenType fonts with CFF data use Version 3.0 only. + //Header + + //Fixed => 32-bit signed fixed-point number (16.16) + + //The table begins as follows: + //Type Name Description + //Fixed Version 0x00010000 for version 1.0 + // 0x00020000 for version 2.0 + // 0x00025000 for version 2.5 (deprecated) + // 0x00030000 for version 3.0 + //Fixed italicAngle Italic angle in counter-clockwise degrees from the vertical. Zero for upright text, negative for text that leans to the right (forward). + //FWord underlinePosition This is the suggested distance of the top of the underline from the baseline (negative values indicate below baseline). + // The PostScript definition of this FontInfo dictionary key (the y coordinate of the center of the stroke) is not used for historical reasons. + // The value of the PostScript key may be calculated by subtracting half the underlineThickness from the value of this field. + //FWord underlineThickness Suggested values for the underline thickness. + //uint32 isFixedPitch Set to 0 if the font is proportionally spaced, non-zero if the font is not proportionally spaced (i.e. monospaced). + //uint32 minMemType42 Minimum memory usage when an OpenType font is downloaded. + //uint32 maxMemType42 Maximum memory usage when an OpenType font is downloaded. + //uint32 minMemType1 Minimum memory usage when an OpenType font is downloaded as a Type 1 font. + //uint32 maxMemType1 Maximum memory usage when an OpenType font is downloaded as a Type 1 font. + //--------- + + //The last four entries in the table are present because PostScript drivers can do better memory management + //if the virtual memory (VM) requirements of a downloadable OpenType font are known before the font is downloaded. + //This information should be supplied if known. + //If it is not known, set the value to zero. + //The driver will still work but will be less efficient. + + //Maximum memory usage is minimum memory usage plus maximum runtime memory use. + //Maximum runtime memory use depends on the maximum band size of any bitmap potentially rasterized by the font scaler. + //Runtime memory usage could be calculated by rendering characters at different point sizes and comparing memory use. + + class PostTable : TableEntry + { + public const string _N = "post"; + public override string Name => _N; + // + Dictionary _glyphNames; + Dictionary _glyphIndiceByName; + + public int Version { get; private set; } + public uint ItalicAngle { get; private set; } + public short UnderlinePosition { get; private set; } + public short UnderlineThickness { get; private set; } + + protected override void ReadContentFrom(BinaryReader reader) + { + //header + uint version = reader.ReadUInt32(); //16.16 + ItalicAngle = reader.ReadUInt32(); + UnderlinePosition = reader.ReadInt16(); + UnderlineThickness = reader.ReadInt16(); + uint isFixedPitch = reader.ReadUInt32(); + uint minMemType42 = reader.ReadUInt32(); + uint maxMemType42 = reader.ReadUInt32(); + uint minMemType1 = reader.ReadUInt32(); + uint maxMemType1 = reader.ReadUInt32(); + + //If the version is 1.0 or 3.0, the table ends here. + + //The additional entries for versions 2.0 and 2.5 are shown below. + //Apple has defined a version 4.0 for use with Apple Advanced Typography (AAT), which is described in their documentation. + + // float version_f = (float)(version) / (1 << 16); + + switch (version) + { + case 0x00010000: //version 1 + Version = 1; + break; + case 0x00030000: //version3 + Version = 3; + break; + case 0x00020000: //version 2 + { + Version = 2; + + //Version 2.0 + + //This is the version required in order to supply PostScript glyph names for fonts which do not supply them elsewhere. + //A version 2.0 'post' table can be used in fonts with TrueType or CFF version 2 outlines. + //Type Name Description + //uint16 numberOfGlyphs Number of glyphs (this should be the same as numGlyphs in 'maxp' table). + //uint16 glyphNameIndex[numGlyphs]. This is not an offset, but is the ordinal number of the glyph in 'post' string tables. + //int8 names[numberNewGlyphs] Glyph names with length bytes [variable] (a Pascal string). + + //This font file contains glyphs not in the standard Macintosh set, + //or the ordering of the glyphs in the font file differs from the standard Macintosh set. + //The glyph name array maps the glyphs in this font to name index. + //.... + //If you do not want to associate a PostScript name with a particular glyph, use index number 0 which points to the name .notdef. + + _glyphNames = new Dictionary(); + ushort numOfGlyphs = reader.ReadUInt16(); + ushort[] glyphNameIndice = Utils.ReadUInt16Array(reader, numOfGlyphs);//*** + string[] stdMacGlyphNames = MacPostFormat1.GetStdMacGlyphNames(); + + for (ushort i = 0; i < numOfGlyphs; ++i) + { + ushort glyphNameIndex = glyphNameIndice[i]; + if (glyphNameIndex < 258) + { + //If the name index is between 0 and 257, treat the name index as a glyph index in the Macintosh standard order. + //replace? + _glyphNames[i] = stdMacGlyphNames[glyphNameIndex]; + } + else + { + //If the name index is between 258 and 65535, + //then subtract 258 and use that to index into the list of Pascal strings at the end of the table. + //Thus a given font may map some of its glyphs to the standard glyph names, and some to its own names. + + //258 and 65535, + int len = reader.ReadByte(); //name len + _glyphNames.Add(i, System.Text.Encoding.UTF8.GetString(reader.ReadBytes(len), 0, len)); + } + } + + } + break; + default: + { + return; + throw new System.NotSupportedException(); + } + case 0x00025000: + //deprecated ?? + throw new System.NotSupportedException(); + } + + } + + + internal Dictionary GlyphNames => _glyphNames; + // + internal ushort GetGlyphIndex(string glyphName) + { + if (_glyphNames == null) + { + return 0; //not found! + } + // + if (_glyphIndiceByName == null) + { + //------ + //create a cache + _glyphIndiceByName = new Dictionary(); + foreach (var kp in _glyphNames) + { + //TODO: review how to handle duplicated glyph name + //1. report the error + //2. handle ... + + _glyphIndiceByName[kp.Value] = kp.Key; + //_glyphIndiceByName.Add(kp.Value, kp.Key); + } + } + _glyphIndiceByName.TryGetValue(glyphName, out ushort found); + return found; + } + + } + + + + + //Version 2.5 (deprecated) + + //This version of the 'post' table has been deprecated as of OpenType Specification v1.3. + + //This version provides a space-saving table for TrueType-based fonts which contain a pure subset of, or a simple reordering of, the standard Macintosh glyph set. + //Type Name Description + //USHORT numberOfGlyphs Number of glyphs + //CHAR offset[numGlyphs] Difference between graphic index and standard order of glyph + + //This version is useful for TrueType-based font files that contain only glyphs in the standard Macintosh glyph set but which have those glyphs arranged in a non-standard order or which are missing some glyphs. The table contains one byte for each glyph in the font file. The byte is treated as a signed offset that maps the glyph index used in this font into the standard glyph index. In other words, assuming that the font contains the three glyphs A, B, and C which are the 37th, 38th, and 39th glyphs in the standard ordering, the 'post' table would contain the bytes +36, +36, +36. This format has been deprecated by Apple, as of February 2000. + //Version 3.0 + + //The version makes it possible to create a font that is not burdened with a large 'post' table set of glyph names. A version 3.0 'post' table can be used by OpenType fonts with TrueType or CFF (version 1 or 2) data. + + //This version specifies that no PostScript name information is provided for the glyphs in this font file. The printing behavior of this version on PostScript printers is unspecified, except that it should not result in a fatal or unrecoverable error. Some drivers may print nothing; other drivers may attempt to print using a default naming scheme. + + //Windows makes use of the italic angle value in the 'post' table but does not actually require any glyph names to be stored as Pascal strings. + //'post' Table and OpenType Font Variations + + //In a variable font, various font-metric values within the 'post' table may need to be adjusted for different variation instances. Variation data for 'post' entries can be provided in the metrics variations ('MVAR') table. Different 'post' entries are associated with particular variation data in the 'MVAR' table using value tags, as follows: + //'post' entry Tag + //underlinePosition 'undo' + //underlineThickness 'unds' + + // Note: The italicAngle value is not adjusted by variation data since this corresponds to the 'slnt' variation axis that can be used to define a font’s variation space. Appropriate post.italicAngle values for a variation instance can be derived from the 'slnt' user coordinates that are used to select a particular variation instance. See the discussion of the 'slnt' axis in the Variation Axis Tags section of the 'fvar' table chapter for details on the relationship between italicAngle and the 'slnt' axis. + + //For general information on OpenType Font Variations, see the chapter, OpenType Font Variations Overview. + + + + +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntry.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntry.cs new file mode 100644 index 00000000..20041b73 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntry.cs @@ -0,0 +1,55 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.IO; +namespace Typography.OpenFont.Tables +{ + /// + /// this is base class of all 'top' font table + /// + public abstract class TableEntry + { + public TableEntry() + { + } + internal TableHeader Header { get; set; } + protected abstract void ReadContentFrom(BinaryReader reader); + public abstract string Name { get; } + internal void LoadDataFrom(BinaryReader reader) + { + //ensure that we always start at the correct offset*** + reader.BaseStream.Seek(this.Header.Offset, SeekOrigin.Begin); + ReadContentFrom(reader); + } + public uint TableLength => this.Header.Length; + + } + class UnreadTableEntry : TableEntry + { + public UnreadTableEntry(TableHeader header) + { + this.Header = header; + } + public override string Name => this.Header.Tag; + // + protected sealed override void ReadContentFrom(BinaryReader reader) + { + //intend *** + throw new NotImplementedException(); + } + + public bool HasCustomContentReader { get; protected set; } + public virtual T CreateTableEntry(BinaryReader reader, T expectedResult) + where T : TableEntry + { + throw new NotImplementedException(); + } +#if DEBUG + public override string ToString() + { + return this.Name; + } +#endif + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntryCollection.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntryCollection.cs new file mode 100644 index 00000000..7f54d261 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableEntryCollection.cs @@ -0,0 +1,30 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System.Collections.Generic; +namespace Typography.OpenFont.Tables +{ + class TableEntryCollection + { + readonly Dictionary _tables = new Dictionary(); + public TableEntryCollection() { } + + public void AddEntry(TableEntry en) => _tables.Add(en.Name, en); + + public bool TryGetTable(string tableName, out TableEntry entry) => _tables.TryGetValue(tableName, out entry); + + public void ReplaceTable(TableEntry table) => _tables[table.Name] = table; + + public TableHeader[] CloneTableHeaders() + { + TableHeader[] clones = new TableHeader[_tables.Count]; + int i = 0; + foreach (TableEntry en in _tables.Values) + { + clones[i] = en.Header.Clone(); + i++; + } + return clones; + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableHeader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableHeader.cs new file mode 100644 index 00000000..18c5c272 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/TableHeader.cs @@ -0,0 +1,40 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +namespace Typography.OpenFont.Tables +{ + class TableHeader + { + readonly uint _tag; + + public TableHeader(uint tag, uint checkSum, uint offset, uint len) + { + _tag = tag; + CheckSum = checkSum; + Offset = offset; + Length = len; + Tag = Utils.TagToString(_tag); + } + public TableHeader(string tag, uint checkSum, uint offset, uint len) + { + _tag = 0; + CheckSum = checkSum; + Offset = offset; + Length = len; + Tag = tag; + } + // + public string Tag { get; } + public uint Offset { get; } + public uint CheckSum { get; } + public uint Length { get; } + + public TableHeader Clone() => (_tag != 0) ? new TableHeader(_tag, CheckSum, Offset, Length) : new TableHeader(Tag, CheckSum, Offset, Length); +#if DEBUG + public override string ToString() + { + return "{" + Tag + "}"; + } +#endif + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Utils.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Utils.cs new file mode 100644 index 00000000..8821aee3 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Tables/Utils.cs @@ -0,0 +1,133 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev + +using System; +using System.Text; +using System.IO; + +namespace Typography.OpenFont +{ + static class Utils + { + /// + /// read float, 2.14 format + /// + /// + /// + public static float ReadF2Dot14(this BinaryReader reader) + { + return ((float)reader.ReadInt16()) / (1 << 14); /* Format 2.14 */ + } + + public static Bounds ReadBounds(BinaryReader input) + { + return new Bounds( + input.ReadInt16(),//xmin + input.ReadInt16(), //ymin + input.ReadInt16(), //xmax + input.ReadInt16());//ymax + } + + public static string TagToString(uint tag) + { + byte[] bytes = BitConverter.GetBytes(tag); + Array.Reverse(bytes); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + public static int ReadUInt24(this BinaryReader reader) + { + byte highByte = reader.ReadByte(); + return (highByte << 16) | reader.ReadUInt16(); + } + /// + /// 16.16 float format + /// + /// + /// + public static float ReadFixed(this BinaryReader reader) + { + //16.16 format + return (float)reader.ReadUInt32() / (1 << 16); + } + + public static ushort[] ReadUInt16Array(this BinaryReader reader, int nRecords) + { + ushort[] arr = new ushort[nRecords]; + for (int i = 0; i < arr.Length; ++i) + { + arr[i] = reader.ReadUInt16(); + } + return arr; + } + public static uint[] ReadUInt16ArrayAsUInt32Array(this BinaryReader reader, int nRecords) + { + uint[] arr = new uint[nRecords]; + for (int i = 0; i < arr.Length; ++i) + { + arr[i] = reader.ReadUInt16(); + } + return arr; + } + public static uint[] ReadUInt32Array(this BinaryReader reader, int nRecords) + { + uint[] arr = new uint[nRecords]; + for (int i = 0; i < arr.Length; ++i) + { + arr[i] = reader.ReadUInt32(); + } + return arr; + } + + public static T[] CloneArray(T[] original, int newArrLenExtend = 0) + { + int orgLen = original.Length; + T[] newClone = new T[orgLen + newArrLenExtend]; + Array.Copy(original, newClone, orgLen); + return newClone; + } + + public static T[] ConcatArray(T[] arr1, T[] arr2) + { + T[] newArr = new T[arr1.Length + arr2.Length]; + Array.Copy(arr1, 0, newArr, 0, arr1.Length); + Array.Copy(arr2, 0, newArr, arr1.Length, arr2.Length); + return newArr; + } + + public static void WarnUnimplemented(string format, params object[] args) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("!STUB! " + string.Format(format, args)); +#endif + } + + internal static void WarnUnimplementedCollectAssocGlyphs(string msg) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("!STUB! UnimplementedCollectAssocGlyph :" + msg); +#endif + } +#if DEBUG + public static bool dbugIsDiff(GlyphPointF[] set1, GlyphPointF[] set2) + { + int j = set1.Length; + if (j != set2.Length) + { + //yes, diff + return true; + } + for (int i = j - 1; i >= 0; --i) + { + if (!set1[i].dbugIsEqualsWith(set2[i])) + { + //yes, diff + return true; + } + } + //no, both are the same + return false; + } +#endif + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/InvalidFontException.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/InvalidFontException.cs new file mode 100644 index 00000000..071b384c --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/InvalidFontException.cs @@ -0,0 +1,22 @@ +//MIT, 2015, Michael Popoloski's SharpFont + +using System; + +namespace Typography.OpenFont +{ + + class InvalidFontException : Exception + { + public InvalidFontException() { } + public InvalidFontException(string msg) : base(msg) { } + } + class InvalidTrueTypeFontException : InvalidFontException + { + public InvalidTrueTypeFontException() { } + /// + /// Initializes a new instance of the class. + /// + public InvalidTrueTypeFontException(string msg) : base(msg) { } + } + +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/TrueTypeInterpreter.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/TrueTypeInterpreter.cs new file mode 100644 index 00000000..c4d99a41 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/TrueTypeInterperter/TrueTypeInterpreter.cs @@ -0,0 +1,2148 @@ +//MIT, 2015, Michael Popoloski's SharpFont +using System; +using System.Numerics; + +namespace Typography.OpenFont +{ + + + public class TrueTypeInterpreter + { + Typeface _currentTypeFace; + SharpFontInterpreter _interpreter; + public Typeface Typeface + { + get => _currentTypeFace; + set => SetTypeFace(value); + } + + public void SetTypeFace(Typeface typeface) + { + //still preserve this for compat with others, + //wait for other libs... + + _currentTypeFace = typeface; + Tables.MaxProfile maximumProfile = _currentTypeFace.MaxProfile; + _interpreter = new SharpFontInterpreter( + maximumProfile.MaxStackElements, + maximumProfile.MaxStorage, + maximumProfile.MaxFunctionDefs, + maximumProfile.MaxInstructionDefs, + maximumProfile.MaxTwilightPoints); + // the fpgm table optionally contains a program to run at initialization time + if (_currentTypeFace.FpgmProgramBuffer != null) + { + _interpreter.InitializeFunctionDefs(_currentTypeFace.FpgmProgramBuffer); + } + } + + + public GlyphPointF[] HintGlyph(ushort glyphIndex, float glyphSizeInPixel) + { + + Glyph glyph = _currentTypeFace.GetGlyph(glyphIndex); + //------------------------------------------- + //1. start with original points/contours from glyph + int horizontalAdv = _currentTypeFace.GetAdvanceWidthFromGlyphIndex(glyphIndex); + int hFrontSideBearing = _currentTypeFace.GetLeftSideBearing(glyphIndex); + + return HintGlyph(horizontalAdv, + hFrontSideBearing, + glyph.MinX, + glyph.MaxY, + glyph.GlyphPoints, + glyph.EndPoints, + glyph.GlyphInstructions, + glyphSizeInPixel); + + } + public GlyphPointF[] HintGlyph( + int horizontalAdv, + int hFrontSideBearing, + int minX, + int maxY, + GlyphPointF[] glyphPoints, + ushort[] contourEndPoints, + byte[] instructions, + float glyphSizeInPixel) + { + + //get glyph for its matrix + + //TODO: review here again + + int verticalAdv = 0; + int vFrontSideBearing = 0; + var pp1 = new GlyphPointF((minX - hFrontSideBearing), 0, true); + var pp2 = new GlyphPointF(pp1.X + horizontalAdv, 0, true); + var pp3 = new GlyphPointF(0, maxY + vFrontSideBearing, true); + var pp4 = new GlyphPointF(0, pp3.Y - verticalAdv, true); + //------------------------- + + //2. use a clone version extend org with 4 elems + int orgLen = glyphPoints.Length; + GlyphPointF[] newGlyphPoints = Utils.CloneArray(glyphPoints, 4); + // add phantom points; these are used to define the extents of the glyph, + // and can be modified by hinting instructions + newGlyphPoints[orgLen] = pp1; + newGlyphPoints[orgLen + 1] = pp2; + newGlyphPoints[orgLen + 2] = pp3; + newGlyphPoints[orgLen + 3] = pp4; + + //3. scale all point to target pixel size + float pxScale = _currentTypeFace.CalculateScaleToPixel(glyphSizeInPixel); + for (int i = orgLen + 3; i >= 0; --i) + { + newGlyphPoints[i].ApplyScale(pxScale); + } + + //---------------------------------------------- + //test : agg's vertical hint + //apply large scale on horizontal axis only + //translate and then scale back + float agg_x_scale = 1000; + // + if (UseVerticalHinting) + { + ApplyScaleOnlyOnXAxis(newGlyphPoints, agg_x_scale); + } + + //4. + _interpreter.SetControlValueTable(_currentTypeFace.ControlValues, + pxScale, + glyphSizeInPixel, + _currentTypeFace.PrepProgramBuffer); + //-------------------------------------------------- + //5. hint + _interpreter.HintGlyph(newGlyphPoints, contourEndPoints, instructions); + + //6. scale back + if (UseVerticalHinting) + { + ApplyScaleOnlyOnXAxis(newGlyphPoints, 1f / agg_x_scale); + } + return newGlyphPoints; + + } + + public bool UseVerticalHinting { get; set; } + + static void ApplyScaleOnlyOnXAxis(GlyphPointF[] glyphPoints, float xscale) + { + //TODO: review performance here + for (int i = glyphPoints.Length - 1; i >= 0; --i) + { + glyphPoints[i].ApplyScaleOnlyOnXAxis(xscale); + } + + } + + } + + + /// + /// SharpFont's TrueType Interpreter + /// + class SharpFontInterpreter + { + GraphicsState _state; + GraphicsState _cvtState; + ExecutionStack _stack; + InstructionStream[] _functions; + InstructionStream[] _instructionDefs; + float[] _controlValueTable; + int[] _storage; + ushort[] _contours; + float _scale; + int _ppem; + int _callStackSize; + float _fdotp; + float _roundThreshold; + float _roundPhase; + float roundPeriod; + Zone _zp0, _zp1, _zp2; + Zone _points, _twilight; + + public SharpFontInterpreter(int maxStack, int maxStorage, int maxFunctions, int maxInstructionDefs, int maxTwilightPoints) + { + _stack = new ExecutionStack(maxStack); + _storage = new int[maxStorage]; + _functions = new InstructionStream[maxFunctions]; + _instructionDefs = new InstructionStream[maxInstructionDefs > 0 ? 256 : 0]; + _state = new GraphicsState(); + _cvtState = new GraphicsState(); + _twilight = new Zone(new GlyphPointF[maxTwilightPoints], isTwilight: true); + } + + public void InitializeFunctionDefs(byte[] instructions) + { + Execute(new InstructionStream(instructions), false, true); + } + + public void SetControlValueTable(int[] cvt, float scale, float ppem, byte[] cvProgram) + { + if (_scale == scale || cvt == null) + return; + + if (_controlValueTable == null) + _controlValueTable = new float[cvt.Length]; + //copy cvt and apply scale + for (int i = cvt.Length - 1; i >= 0; --i) + _controlValueTable[i] = cvt[i] * scale; + + _scale = scale; + _ppem = (int)Math.Round(ppem); + _zp0 = _zp1 = _zp2 = _points; + _state.Reset(); + _stack.Clear(); + + if (cvProgram != null) + { + Execute(new InstructionStream(cvProgram), false, false); + + // save off the CVT graphics state so that we can restore it for each glyph we hint + if ((_state.InstructionControl & InstructionControlFlags.UseDefaultGraphicsState) != 0) + _cvtState.Reset(); + else + { + // always reset a few fields; copy the reset + _cvtState = _state; + _cvtState.Freedom = Vector2.UnitX; + _cvtState.Projection = Vector2.UnitX; + _cvtState.DualProjection = Vector2.UnitX; + _cvtState.RoundState = RoundMode.ToGrid; + _cvtState.Loop = 1; + } + } + } + + public void HintGlyph(GlyphPointF[] glyphPoints, ushort[] contours, byte[] instructions) + { + if (instructions == null || instructions.Length == 0) + return; + + // check if the CVT program disabled hinting + if ((_state.InstructionControl & InstructionControlFlags.InhibitGridFitting) != 0) + return; + + // TODO: composite glyphs + // TODO: round the phantom points? + + // save contours and points + _contours = contours; + _zp0 = _zp1 = _zp2 = _points = new Zone(glyphPoints, isTwilight: false); + + // reset all of our shared state + _state = _cvtState; + _callStackSize = 0; +#if DEBUG + debugList.Clear(); +#endif + _stack.Clear(); + OnVectorsUpdated(); + + // normalize the round state settings + switch (_state.RoundState) + { + case RoundMode.Super: SetSuperRound(1.0f); break; + case RoundMode.Super45: SetSuperRound(Sqrt2Over2); break; + } + + try + { + Execute(new InstructionStream(instructions), false, false); + } + catch (InvalidTrueTypeFontException) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("invalid_font_ex:"); +#endif + + } + } + +#if DEBUG + System.Collections.Generic.List debugList = new System.Collections.Generic.List(); +#endif + void Execute(InstructionStream stream, bool inFunction, bool allowFunctionDefs) + { + // dispatch each instruction in the stream + while (!stream.Done) + { + var opcode = stream.NextOpCode(); +#if DEBUG + debugList.Add(opcode); +#endif + switch (opcode) + { + // ==== PUSH INSTRUCTIONS ==== + case OpCode.NPUSHB: + case OpCode.PUSHB1: + case OpCode.PUSHB2: + case OpCode.PUSHB3: + case OpCode.PUSHB4: + case OpCode.PUSHB5: + case OpCode.PUSHB6: + case OpCode.PUSHB7: + case OpCode.PUSHB8: + { + var count = opcode == OpCode.NPUSHB ? stream.NextByte() : opcode - OpCode.PUSHB1 + 1; + for (int i = count - 1; i >= 0; --i) + _stack.Push(stream.NextByte()); + } + break; + case OpCode.NPUSHW: + case OpCode.PUSHW1: + case OpCode.PUSHW2: + case OpCode.PUSHW3: + case OpCode.PUSHW4: + case OpCode.PUSHW5: + case OpCode.PUSHW6: + case OpCode.PUSHW7: + case OpCode.PUSHW8: + { + var count = opcode == OpCode.NPUSHW ? stream.NextByte() : opcode - OpCode.PUSHW1 + 1; + for (int i = count - 1; i >= 0; --i) + _stack.Push(stream.NextWord()); + } + break; + + // ==== STORAGE MANAGEMENT ==== + case OpCode.RS: + { + var loc = CheckIndex(_stack.Pop(), _storage.Length); + _stack.Push(_storage[loc]); + } + break; + case OpCode.WS: + { + var value = _stack.Pop(); + var loc = CheckIndex(_stack.Pop(), _storage.Length); + _storage[loc] = value; + } + break; + + // ==== CONTROL VALUE TABLE ==== + case OpCode.WCVTP: + { + var value = _stack.PopFloat(); + var loc = CheckIndex(_stack.Pop(), _controlValueTable.Length); + _controlValueTable[loc] = value; + } + break; + case OpCode.WCVTF: + { + var value = _stack.Pop(); + var loc = CheckIndex(_stack.Pop(), _controlValueTable.Length); + _controlValueTable[loc] = value * _scale; + } + break; + case OpCode.RCVT: _stack.Push(ReadCvt()); break; + + // ==== STATE VECTORS ==== + case OpCode.SVTCA0: + case OpCode.SVTCA1: + { + var axis = opcode - OpCode.SVTCA0; + SetFreedomVectorToAxis(axis); + SetProjectionVectorToAxis(axis); + } + break; + case OpCode.SFVTPV: _state.Freedom = _state.Projection; OnVectorsUpdated(); break; + case OpCode.SPVTCA0: + case OpCode.SPVTCA1: SetProjectionVectorToAxis(opcode - OpCode.SPVTCA0); break; + case OpCode.SFVTCA0: + case OpCode.SFVTCA1: SetFreedomVectorToAxis(opcode - OpCode.SFVTCA0); break; + case OpCode.SPVTL0: + case OpCode.SPVTL1: + case OpCode.SFVTL0: + case OpCode.SFVTL1: SetVectorToLine(opcode - OpCode.SPVTL0, false); break; + case OpCode.SDPVTL0: + case OpCode.SDPVTL1: SetVectorToLine(opcode - OpCode.SDPVTL0, true); break; + case OpCode.SPVFS: + case OpCode.SFVFS: + { + var y = _stack.Pop(); + var x = _stack.Pop(); + var vec = Vector2.Normalize(new Vector2(F2Dot14ToFloat(x), F2Dot14ToFloat(y))); + if (opcode == OpCode.SFVFS) + _state.Freedom = vec; + else + { + _state.Projection = vec; + _state.DualProjection = vec; + } + OnVectorsUpdated(); + } + break; + case OpCode.GPV: + case OpCode.GFV: + { + var vec = opcode == OpCode.GPV ? _state.Projection : _state.Freedom; + _stack.Push(FloatToF2Dot14(vec.X)); + _stack.Push(FloatToF2Dot14(vec.Y)); + } + break; + + // ==== GRAPHICS STATE ==== + case OpCode.SRP0: _state.Rp0 = _stack.Pop(); break; + case OpCode.SRP1: _state.Rp1 = _stack.Pop(); break; + case OpCode.SRP2: _state.Rp2 = _stack.Pop(); break; + case OpCode.SZP0: _zp0 = GetZoneFromStack(); break; + case OpCode.SZP1: _zp1 = GetZoneFromStack(); break; + case OpCode.SZP2: _zp2 = GetZoneFromStack(); break; + case OpCode.SZPS: _zp0 = _zp1 = _zp2 = GetZoneFromStack(); break; + case OpCode.RTHG: _state.RoundState = RoundMode.ToHalfGrid; break; + case OpCode.RTG: _state.RoundState = RoundMode.ToGrid; break; + case OpCode.RTDG: _state.RoundState = RoundMode.ToDoubleGrid; break; + case OpCode.RDTG: _state.RoundState = RoundMode.DownToGrid; break; + case OpCode.RUTG: _state.RoundState = RoundMode.UpToGrid; break; + case OpCode.ROFF: _state.RoundState = RoundMode.Off; break; + case OpCode.SROUND: _state.RoundState = RoundMode.Super; SetSuperRound(1.0f); break; + case OpCode.S45ROUND: _state.RoundState = RoundMode.Super45; SetSuperRound(Sqrt2Over2); break; + case OpCode.INSTCTRL: + { + var selector = _stack.Pop(); + if (selector >= 1 && selector <= 2) + { + // value is false if zero, otherwise shift the right bit into the flags + var bit = 1 << (selector - 1); + if (_stack.Pop() == 0) + _state.InstructionControl = (InstructionControlFlags)((int)_state.InstructionControl & ~bit); + else + _state.InstructionControl = (InstructionControlFlags)((int)_state.InstructionControl | bit); + } + } + break; + case OpCode.SCANCTRL: /* instruction unspported */ _stack.Pop(); break; + case OpCode.SCANTYPE: /* instruction unspported */ _stack.Pop(); break; + case OpCode.SANGW: /* instruction unspported */ _stack.Pop(); break; + case OpCode.SLOOP: _state.Loop = _stack.Pop(); break; + case OpCode.SMD: _state.MinDistance = _stack.PopFloat(); break; + case OpCode.SCVTCI: _state.ControlValueCutIn = _stack.PopFloat(); break; + case OpCode.SSWCI: _state.SingleWidthCutIn = _stack.PopFloat(); break; + case OpCode.SSW: _state.SingleWidthValue = _stack.Pop() * _scale; break; + case OpCode.FLIPON: _state.AutoFlip = true; break; + case OpCode.FLIPOFF: _state.AutoFlip = false; break; + case OpCode.SDB: _state.DeltaBase = _stack.Pop(); break; + case OpCode.SDS: _state.DeltaShift = _stack.Pop(); break; + + // ==== POINT MEASUREMENT ==== + case OpCode.GC0: _stack.Push(Project(_zp2.GetCurrent(_stack.Pop()))); break; + case OpCode.GC1: _stack.Push(DualProject(_zp2.GetOriginal(_stack.Pop()))); break; + case OpCode.SCFS: + { + var value = _stack.PopFloat(); + var index = _stack.Pop(); + var point = _zp2.GetCurrent(index); + MovePoint(_zp2, index, value - Project(point)); + + // moving twilight points moves their "original" value also + if (_zp2.IsTwilight) + _zp2.Original[index].P = _zp2.Current[index].P; + } + break; + case OpCode.MD0: + { + var p1 = _zp1.GetOriginal(_stack.Pop()); + var p2 = _zp0.GetOriginal(_stack.Pop()); + _stack.Push(DualProject(p2 - p1)); + } + break; + case OpCode.MD1: + { + var p1 = _zp1.GetCurrent(_stack.Pop()); + var p2 = _zp0.GetCurrent(_stack.Pop()); + _stack.Push(Project(p2 - p1)); + } + break; + case OpCode.MPS: // MPS should return point size, but we assume DPI so it's the same as pixel size + case OpCode.MPPEM: _stack.Push(_ppem); break; + case OpCode.AA: /* deprecated instruction */ _stack.Pop(); break; + + // ==== POINT MODIFICATION ==== + case OpCode.FLIPPT: + { + for (int i = 0; i < _state.Loop; i++) + { + var index = _stack.Pop(); + //review here again! + _points.Current[index].onCurve = !_points.Current[index].onCurve; + //if (points.Current[index].onCurve) + // points.Current[index].onCurve = false; + //else + // points.Current[index].onCurve = true; + } + _state.Loop = 1; + } + break; + case OpCode.FLIPRGON: + { + var end = _stack.Pop(); + for (int i = _stack.Pop(); i <= end; i++) + //points.Current[i].Type = PointType.OnCurve; + _points.Current[i].onCurve = true; + } + break; + case OpCode.FLIPRGOFF: + { + var end = _stack.Pop(); + for (int i = _stack.Pop(); i <= end; i++) + //points.Current[i].Type = PointType.Quadratic; + _points.Current[i].onCurve = false; + } + break; + case OpCode.SHP0: + case OpCode.SHP1: + { + Zone zone; + int point; + var displacement = ComputeDisplacement((int)opcode, out zone, out point); + ShiftPoints(displacement); + } + break; + case OpCode.SHPIX: ShiftPoints(_stack.PopFloat() * _state.Freedom); break; + case OpCode.SHC0: + case OpCode.SHC1: + { + Zone zone; + int point; + var displacement = ComputeDisplacement((int)opcode, out zone, out point); + var touch = GetTouchState(); + var contour = _stack.Pop(); + var start = contour == 0 ? 0 : _contours[contour - 1] + 1; + var count = _zp2.IsTwilight ? _zp2.Current.Length : _contours[contour] + 1; + + for (int i = start; i < count; i++) + { + // don't move the reference point + if (zone.Current != _zp2.Current || point != i) + { + _zp2.Current[i].P += displacement; + _zp2.TouchState[i] |= touch; + } + } + } + break; + case OpCode.SHZ0: + case OpCode.SHZ1: + { + Zone zone; + int point; + var displacement = ComputeDisplacement((int)opcode, out zone, out point); + var count = 0; + if (_zp2.IsTwilight) + count = _zp2.Current.Length; + else if (_contours.Length > 0) + count = _contours[_contours.Length - 1] + 1; + + for (int i = 0; i < count; i++) + { + // don't move the reference point + if (zone.Current != _zp2.Current || point != i) + _zp2.Current[i].P += displacement; + } + } + break; + case OpCode.MIAP0: + case OpCode.MIAP1: + { + var distance = ReadCvt(); + var pointIndex = _stack.Pop(); + + // this instruction is used in the CVT to set up twilight points with original values + if (_zp0.IsTwilight) + { + var original = _state.Freedom * distance; + _zp0.Original[pointIndex].P = original; + _zp0.Current[pointIndex].P = original; + } + + // current position of the point along the projection vector + var point = _zp0.GetCurrent(pointIndex); + var currentPos = Project(point); + if (opcode == OpCode.MIAP1) + { + // only use the CVT if we are above the cut-in point + if (Math.Abs(distance - currentPos) > _state.ControlValueCutIn) + distance = currentPos; + distance = Round(distance); + } + + MovePoint(_zp0, pointIndex, distance - currentPos); + _state.Rp0 = pointIndex; + _state.Rp1 = pointIndex; + } + break; + case OpCode.MDAP0: + case OpCode.MDAP1: + { + var pointIndex = _stack.Pop(); + var point = _zp0.GetCurrent(pointIndex); + var distance = 0.0f; + if (opcode == OpCode.MDAP1) + { + distance = Project(point); + distance = Round(distance) - distance; + } + + MovePoint(_zp0, pointIndex, distance); + _state.Rp0 = pointIndex; + _state.Rp1 = pointIndex; + } + break; + case OpCode.MSIRP0: + case OpCode.MSIRP1: + { + var targetDistance = _stack.PopFloat(); + var pointIndex = _stack.Pop(); + + // if we're operating on the twilight zone, initialize the points + if (_zp1.IsTwilight) + { + _zp1.Original[pointIndex].P = _zp0.Original[_state.Rp0].P + targetDistance * _state.Freedom / _fdotp; + _zp1.Current[pointIndex].P = _zp1.Original[pointIndex].P; + } + + var currentDistance = Project(_zp1.GetCurrent(pointIndex) - _zp0.GetCurrent(_state.Rp0)); + MovePoint(_zp1, pointIndex, targetDistance - currentDistance); + + _state.Rp1 = _state.Rp0; + _state.Rp2 = pointIndex; + if (opcode == OpCode.MSIRP1) + _state.Rp0 = pointIndex; + } + break; + case OpCode.IP: + { + var originalBase = _zp0.GetOriginal(_state.Rp1); + var currentBase = _zp0.GetCurrent(_state.Rp1); + var originalRange = DualProject(_zp1.GetOriginal(_state.Rp2) - originalBase); + var currentRange = Project(_zp1.GetCurrent(_state.Rp2) - currentBase); + + for (int i = 0; i < _state.Loop; i++) + { + var pointIndex = _stack.Pop(); + var point = _zp2.GetCurrent(pointIndex); + var currentDistance = Project(point - currentBase); + var originalDistance = DualProject(_zp2.GetOriginal(pointIndex) - originalBase); + + var newDistance = 0.0f; + if (originalDistance != 0.0f) + { + // a range of 0.0f is invalid according to the spec (would result in a div by zero) + if (originalRange == 0.0f) + newDistance = originalDistance; + else + newDistance = originalDistance * currentRange / originalRange; + } + + MovePoint(_zp2, pointIndex, newDistance - currentDistance); + } + _state.Loop = 1; + } + break; + case OpCode.ALIGNRP: + { + for (int i = 0; i < _state.Loop; i++) + { + var pointIndex = _stack.Pop(); + var p1 = _zp1.GetCurrent(pointIndex); + var p2 = _zp0.GetCurrent(_state.Rp0); + MovePoint(_zp1, pointIndex, -Project(p1 - p2)); + } + _state.Loop = 1; + } + break; + case OpCode.ALIGNPTS: + { + var p1 = _stack.Pop(); + var p2 = _stack.Pop(); + var distance = Project(_zp0.GetCurrent(p2) - _zp1.GetCurrent(p1)) / 2; + MovePoint(_zp1, p1, distance); + MovePoint(_zp0, p2, -distance); + } + break; + case OpCode.UTP: _zp0.TouchState[_stack.Pop()] &= ~GetTouchState(); break; + case OpCode.IUP0: + case OpCode.IUP1: + // bail if no contours (empty outline) + if (_contours.Length == 0) + { + break; + } + + //{ + + // //WinterDev's new managed version + // GlyphPointF[] currentPnts = points.Current; + // GlyphPointF[] originalPnts = points.Original; + + // int cnt_count = contours.Length; + // int point = 0; + // // opcode controls whether we care about X or Y direction + // // do some pointer trickery so we can operate on the + // // points in a direction-agnostic manner + // TouchState touchMask; + + // if (opcode == OpCode.IUP0) + // { + // //y -axis + // touchMask = TouchState.Y; + + // // + // for (int i = 0; i < cnt_count; ++i) + // { + // int endPoint = contours[i]; + // int firstPoint = point; + // int firstTouched = -1; + // int lastTouched = -1; + + // for (; point <= endPoint; point++) + // { + // // check whether this point has been touched + // if ((points.TouchState[point] & touchMask) != 0) + // { + // // if this is the first touched point in the contour, note it and continue + // if (firstTouched < 0) + // { + // firstTouched = point; + // lastTouched = point; + // continue; + // } + + // // otherwise, interpolate all untouched points + // // between this point and our last touched point + // InterpolatePointsYAxis(currentPnts, originalPnts, lastTouched + 1, point - 1, lastTouched, point); + // lastTouched = point; + // } + // } + + // // check if we had any touched points at all in this contour + // if (firstTouched >= 0) + // { + // // there are two cases left to handle: + // // 1. there was only one touched point in the whole contour, in + // // which case we want to shift everything relative to that one + // // 2. several touched points, in which case handle the gap from the + // // beginning to the first touched point and the gap from the last + // // touched point to the end of the contour + // if (lastTouched == firstTouched) + // { + // float delta = currentPnts[lastTouched].Y - originalPnts[lastTouched].Y; + // if (delta != 0.0f) + // { + // for (int n = firstPoint; n < lastTouched; n++) + // { + // currentPnts[n].OffsetY(delta); + // } + // for (int n = lastTouched + 1; n <= endPoint; n++) + // { + // currentPnts[n].OffsetY(delta); + // } + + // } + // } + // else + // { + // InterpolatePointsYAxis(currentPnts, originalPnts, lastTouched + 1, endPoint, lastTouched, firstTouched); + // if (firstTouched > 0) + // { + // InterpolatePointsYAxis(currentPnts, originalPnts, firstPoint, firstTouched - 1, lastTouched, firstTouched); + // } + // } + // } + + // } + // } + // else + // { + // //x-axis + // touchMask = TouchState.X; + // // + // for (int i = 0; i < cnt_count; ++i) + // { + // int endPoint = contours[i]; + // int firstPoint = point; + // int firstTouched = -1; + // int lastTouched = -1; + + // for (; point <= endPoint; point++) + // { + // // check whether this point has been touched + // if ((points.TouchState[point] & touchMask) != 0) + // { + // // if this is the first touched point in the contour, note it and continue + // if (firstTouched < 0) + // { + // firstTouched = point; + // lastTouched = point; + // continue; + // } + + // // otherwise, interpolate all untouched points + // // between this point and our last touched point + // InterpolatePointsXAxis(currentPnts, originalPnts, lastTouched + 1, point - 1, lastTouched, point); + // lastTouched = point; + // } + // } + + // // check if we had any touched points at all in this contour + // if (firstTouched >= 0) + // { + // // there are two cases left to handle: + // // 1. there was only one touched point in the whole contour, in + // // which case we want to shift everything relative to that one + // // 2. several touched points, in which case handle the gap from the + // // beginning to the first touched point and the gap from the last + // // touched point to the end of the contour + // if (lastTouched == firstTouched) + // { + // float delta = currentPnts[lastTouched].X - originalPnts[lastTouched].X; + // if (delta != 0.0f) + // { + // for (int n = firstPoint; n < lastTouched; ++n) + // { + // currentPnts[n].OffsetX(delta); + // } + // for (int n = lastTouched + 1; n <= endPoint; ++n) + // { + // currentPnts[n].OffsetX(delta); + // } + // } + // } + // else + // { + // InterpolatePointsXAxis(currentPnts, originalPnts, lastTouched + 1, endPoint, lastTouched, firstTouched); + // if (firstTouched > 0) + // { + // InterpolatePointsXAxis(currentPnts, originalPnts, firstPoint, firstTouched - 1, lastTouched, firstTouched); + // } + // } + // } + // } + // } + //} + //----------------------------------------- + unsafe + { + + //unsafe version + //TODO: provide manage version + fixed (GlyphPointF* currentPtr = _points.Current) + fixed (GlyphPointF* originalPtr = _points.Original) + { + // opcode controls whether we care about X or Y direction + // do some pointer trickery so we can operate on the + // points in a direction-agnostic manner + TouchState touchMask; + byte* current; + byte* original; + if (opcode == OpCode.IUP0) + { + touchMask = TouchState.Y; + current = (byte*)¤tPtr->P.Y; + original = (byte*)&originalPtr->P.Y; + } + else + { + touchMask = TouchState.X; + current = (byte*)¤tPtr->P.X; + original = (byte*)&originalPtr->P.X; + } + + var point = 0; + for (int i = 0; i < _contours.Length; i++) + { + var endPoint = _contours[i]; + var firstPoint = point; + var firstTouched = -1; + var lastTouched = -1; + + for (; point <= endPoint; point++) + { + // check whether this point has been touched + if ((_points.TouchState[point] & touchMask) != 0) + { + // if this is the first touched point in the contour, note it and continue + if (firstTouched < 0) + { + firstTouched = point; + lastTouched = point; + continue; + } + + // otherwise, interpolate all untouched points + // between this point and our last touched point + InterpolatePoints(current, original, lastTouched + 1, point - 1, lastTouched, point); + lastTouched = point; + } + } + + // check if we had any touched points at all in this contour + if (firstTouched >= 0) + { + // there are two cases left to handle: + // 1. there was only one touched point in the whole contour, in + // which case we want to shift everything relative to that one + // 2. several touched points, in which case handle the gap from the + // beginning to the first touched point and the gap from the last + // touched point to the end of the contour + if (lastTouched == firstTouched) + { + var delta = *GetPoint(current, lastTouched) - *GetPoint(original, lastTouched); + if (delta != 0.0f) + { + for (int j = firstPoint; j < lastTouched; j++) + *GetPoint(current, j) += delta; + for (int j = lastTouched + 1; j <= endPoint; j++) + *GetPoint(current, j) += delta; + } + } + else + { + InterpolatePoints(current, original, lastTouched + 1, endPoint, lastTouched, firstTouched); + if (firstTouched > 0) + InterpolatePoints(current, original, firstPoint, firstTouched - 1, lastTouched, firstTouched); + } + } + } + } + } + break; + case OpCode.ISECT: + { + // move point P to the intersection of lines A and B + var b1 = _zp0.GetCurrent(_stack.Pop()); + var b0 = _zp0.GetCurrent(_stack.Pop()); + var a1 = _zp1.GetCurrent(_stack.Pop()); + var a0 = _zp1.GetCurrent(_stack.Pop()); + var index = _stack.Pop(); + + // calculate intersection using determinants: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + var da = a0 - a1; + var db = b0 - b1; + var den = (da.X * db.Y) - (da.Y * db.X); + if (Math.Abs(den) <= Epsilon) + { + // parallel lines; spec says to put the ppoint "into the middle of the two lines" + _zp2.Current[index].P = (a0 + a1 + b0 + b1) / 4; + } + else + { + var t = (a0.X * a1.Y) - (a0.Y * a1.X); + var u = (b0.X * b1.Y) - (b0.Y * b1.X); + var p = new Vector2( + (t * db.X) - (da.X * u), + (t * db.Y) - (da.Y * u) + ); + _zp2.Current[index].P = p / den; + } + _zp2.TouchState[index] = TouchState.Both; + } + break; + + // ==== STACK MANAGEMENT ==== + case OpCode.DUP: _stack.Duplicate(); break; + case OpCode.POP: _stack.Pop(); break; + case OpCode.CLEAR: _stack.Clear(); break; + case OpCode.SWAP: _stack.Swap(); break; + case OpCode.DEPTH: _stack.Depth(); break; + case OpCode.CINDEX: _stack.Copy(); break; + case OpCode.MINDEX: _stack.Move(); break; + case OpCode.ROLL: _stack.Roll(); break; + + // ==== FLOW CONTROL ==== + case OpCode.IF: + { + // value is false; jump to the next else block or endif marker + // otherwise, we don't have to do anything; we'll keep executing this block + if (!_stack.PopBool()) + { + int indent = 1; + while (indent > 0) + { + opcode = SkipNext(ref stream); + switch (opcode) + { + case OpCode.IF: indent++; break; + case OpCode.EIF: indent--; break; + case OpCode.ELSE: + if (indent == 1) + indent = 0; + break; + } + } + } + } + break; + case OpCode.ELSE: + { + // assume we hit the true statement of some previous if block + // if we had hit false, we would have jumped over this + int indent = 1; + while (indent > 0) + { + opcode = SkipNext(ref stream); + switch (opcode) + { + case OpCode.IF: indent++; break; + case OpCode.EIF: indent--; break; + } + } + } + break; + case OpCode.EIF: /* nothing to do */ break; + case OpCode.JROT: + case OpCode.JROF: + { + if (_stack.PopBool() == (opcode == OpCode.JROT)) + stream.Jump(_stack.Pop() - 1); + else + _stack.Pop(); // ignore the offset + } + break; + case OpCode.JMPR: stream.Jump(_stack.Pop() - 1); break; + + // ==== LOGICAL OPS ==== + case OpCode.LT: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a < b); + } + break; + case OpCode.LTEQ: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a <= b); + } + break; + case OpCode.GT: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a > b); + } + break; + case OpCode.GTEQ: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a >= b); + } + break; + case OpCode.EQ: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a == b); + } + break; + case OpCode.NEQ: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a != b); + } + break; + case OpCode.AND: + { + var b = _stack.PopBool(); + var a = _stack.PopBool(); + _stack.Push(a && b); + } + break; + case OpCode.OR: + { + var b = _stack.PopBool(); + var a = _stack.PopBool(); + _stack.Push(a || b); + } + break; + case OpCode.NOT: _stack.Push(!_stack.PopBool()); break; + case OpCode.ODD: + { + var value = (int)Round(_stack.PopFloat()); + _stack.Push(value % 2 != 0); + } + break; + case OpCode.EVEN: + { + var value = (int)Round(_stack.PopFloat()); + _stack.Push(value % 2 == 0); + } + break; + + // ==== ARITHMETIC ==== + case OpCode.ADD: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a + b); + } + break; + case OpCode.SUB: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + _stack.Push(a - b); + } + break; + case OpCode.DIV: + { + var b = _stack.Pop(); + if (b == 0) + throw new InvalidTrueTypeFontException("Division by zero."); + + var a = _stack.Pop(); + var result = ((long)a << 6) / b; + _stack.Push((int)result); + } + break; + case OpCode.MUL: + { + var b = _stack.Pop(); + var a = _stack.Pop(); + var result = ((long)a * b) >> 6; + _stack.Push((int)result); + } + break; + case OpCode.ABS: _stack.Push(Math.Abs(_stack.Pop())); break; + case OpCode.NEG: _stack.Push(-_stack.Pop()); break; + case OpCode.FLOOR: _stack.Push(_stack.Pop() & ~63); break; + case OpCode.CEILING: _stack.Push((_stack.Pop() + 63) & ~63); break; + case OpCode.MAX: _stack.Push(Math.Max(_stack.Pop(), _stack.Pop())); break; + case OpCode.MIN: _stack.Push(Math.Min(_stack.Pop(), _stack.Pop())); break; + + // ==== FUNCTIONS ==== + case OpCode.FDEF: + { + if (!allowFunctionDefs || inFunction) + throw new InvalidTrueTypeFontException("Can't define functions here."); + + _functions[_stack.Pop()] = stream; + while (SkipNext(ref stream) != OpCode.ENDF) ; + } + break; + case OpCode.IDEF: + { + if (!allowFunctionDefs || inFunction) + throw new InvalidTrueTypeFontException("Can't define functions here."); + + _instructionDefs[_stack.Pop()] = stream; + while (SkipNext(ref stream) != OpCode.ENDF) ; + } + break; + case OpCode.ENDF: + { + if (!inFunction) + throw new InvalidTrueTypeFontException("Found invalid ENDF marker outside of a function definition."); + return; + } + case OpCode.CALL: + case OpCode.LOOPCALL: + { + _callStackSize++; + if (_callStackSize > MaxCallStack) + throw new InvalidTrueTypeFontException("Stack overflow; infinite recursion?"); + + var function = _functions[_stack.Pop()]; + var count = opcode == OpCode.LOOPCALL ? _stack.Pop() : 1; + for (int i = 0; i < count; i++) + Execute(function, true, false); + _callStackSize--; + } + break; + + // ==== ROUNDING ==== + // we don't have "engine compensation" so the variants are unnecessary + case OpCode.ROUND0: + case OpCode.ROUND1: + case OpCode.ROUND2: + case OpCode.ROUND3: _stack.Push(Round(_stack.PopFloat())); break; + case OpCode.NROUND0: + case OpCode.NROUND1: + case OpCode.NROUND2: + case OpCode.NROUND3: break; + + // ==== DELTA EXCEPTIONS ==== + case OpCode.DELTAC1: + case OpCode.DELTAC2: + case OpCode.DELTAC3: + { + var last = _stack.Pop(); + for (int i = 1; i <= last; i++) + { + var cvtIndex = _stack.Pop(); + var arg = _stack.Pop(); + + // upper 4 bits of the 8-bit arg is the relative ppem + // the opcode specifies the base to add to the ppem + var triggerPpem = (arg >> 4) & 0xF; + triggerPpem += (opcode - OpCode.DELTAC1) * 16; + triggerPpem += _state.DeltaBase; + + // if the current ppem matches the trigger, apply the exception + if (_ppem == triggerPpem) + { + // the lower 4 bits of the arg is the amount to shift + // it's encoded such that 0 isn't an allowable value (who wants to shift by 0 anyway?) + var amount = (arg & 0xF) - 8; + if (amount >= 0) + amount++; + amount *= 1 << (6 - _state.DeltaShift); + + // update the CVT + CheckIndex(cvtIndex, _controlValueTable.Length); + _controlValueTable[cvtIndex] += F26Dot6ToFloat(amount); + } + } + } + break; + case OpCode.DELTAP1: + case OpCode.DELTAP2: + case OpCode.DELTAP3: + { + var last = _stack.Pop(); + for (int i = 1; i <= last; i++) + { + var pointIndex = _stack.Pop(); + var arg = _stack.Pop(); + + // upper 4 bits of the 8-bit arg is the relative ppem + // the opcode specifies the base to add to the ppem + var triggerPpem = (arg >> 4) & 0xF; + triggerPpem += _state.DeltaBase; + if (opcode != OpCode.DELTAP1) + triggerPpem += (opcode - OpCode.DELTAP2 + 1) * 16; + + // if the current ppem matches the trigger, apply the exception + if (_ppem == triggerPpem) + { + // the lower 4 bits of the arg is the amount to shift + // it's encoded such that 0 isn't an allowable value (who wants to shift by 0 anyway?) + var amount = (arg & 0xF) - 8; + if (amount >= 0) + amount++; + amount *= 1 << (6 - _state.DeltaShift); + + MovePoint(_zp0, pointIndex, F26Dot6ToFloat(amount)); + } + } + } + break; + + // ==== MISCELLANEOUS ==== + case OpCode.DEBUG: _stack.Pop(); break; + case OpCode.GETINFO: + { + var selector = _stack.Pop(); + var result = 0; + if ((selector & 0x1) != 0) + { + // pretend we are MS Rasterizer v35 + result = 35; + } + + // TODO: rotation and stretching + //if ((selector & 0x2) != 0) + //if ((selector & 0x4) != 0) + + // we're always rendering in grayscale + if ((selector & 0x20) != 0) + result |= 1 << 12; + + // TODO: ClearType flags + + _stack.Push(result); + } + break; + + default: + if (opcode >= OpCode.MIRP) + MoveIndirectRelative(opcode - OpCode.MIRP); + else if (opcode >= OpCode.MDRP) + MoveDirectRelative(opcode - OpCode.MDRP); + else + { + // check if this is a runtime-defined opcode + var index = (int)opcode; + if (index > _instructionDefs.Length || !_instructionDefs[index].IsValid) + throw new InvalidTrueTypeFontException("Unknown opcode in font program."); + + _callStackSize++; + if (_callStackSize > MaxCallStack) + throw new InvalidTrueTypeFontException("Stack overflow; infinite recursion?"); + + Execute(_instructionDefs[index], true, false); + _callStackSize--; + } + break; + } + } + } + + int CheckIndex(int index, int length) + { + if (index < 0 || index >= length) + throw new InvalidTrueTypeFontException(); + return index; + } + + float ReadCvt() { return _controlValueTable[CheckIndex(_stack.Pop(), _controlValueTable.Length)]; } + + void OnVectorsUpdated() + { + _fdotp = (float)Vector2.Dot(_state.Freedom, _state.Projection); + if (Math.Abs(_fdotp) < Epsilon) + _fdotp = 1.0f; + } + + void SetFreedomVectorToAxis(int axis) + { + _state.Freedom = axis == 0 ? Vector2.UnitY : Vector2.UnitX; + OnVectorsUpdated(); + } + + void SetProjectionVectorToAxis(int axis) + { + _state.Projection = axis == 0 ? Vector2.UnitY : Vector2.UnitX; + _state.DualProjection = _state.Projection; + + OnVectorsUpdated(); + } + + void SetVectorToLine(int mode, bool dual) + { + // mode here should be as follows: + // 0: SPVTL0 + // 1: SPVTL1 + // 2: SFVTL0 + // 3: SFVTL1 + var index1 = _stack.Pop(); + var index2 = _stack.Pop(); + var p1 = _zp2.GetCurrent(index1); + var p2 = _zp1.GetCurrent(index2); + + var line = p2 - p1; + if (line.LengthSquared() == 0) + { + // invalid; just set to whatever + if (mode >= 2) + _state.Freedom = Vector2.UnitX; + else + { + _state.Projection = Vector2.UnitX; + _state.DualProjection = Vector2.UnitX; + } + } + else + { + // if mode is 1 or 3, we want a perpendicular vector + if ((mode & 0x1) != 0) + line = new Vector2(-line.Y, line.X); + line = Vector2.Normalize(line); + + if (mode >= 2) + _state.Freedom = line; + else + { + _state.Projection = line; + _state.DualProjection = line; + } + } + + // set the dual projection vector using original points + if (dual) + { + p1 = _zp2.GetOriginal(index1); + p2 = _zp2.GetOriginal(index2); + line = p2 - p1; + + if (line.LengthSquared() == 0) + _state.DualProjection = Vector2.UnitX; + else + { + if ((mode & 0x1) != 0) + line = new Vector2(-line.Y, line.X); + + _state.DualProjection = Vector2.Normalize(line); + } + } + + OnVectorsUpdated(); + } + + Zone GetZoneFromStack() + { + switch (_stack.Pop()) + { + case 0: return _twilight; + case 1: return _points; + default: throw new InvalidTrueTypeFontException("Invalid zone pointer."); + } + } + + void SetSuperRound(float period) + { + // mode is a bunch of packed flags + // bits 7-6 are the period multiplier + var mode = _stack.Pop(); + switch (mode & 0xC0) + { + case 0: roundPeriod = period / 2; break; + case 0x40: roundPeriod = period; break; + case 0x80: roundPeriod = period * 2; break; + default: throw new InvalidTrueTypeFontException("Unknown rounding period multiplier."); + } + + // bits 5-4 are the phase + switch (mode & 0x30) + { + case 0: _roundPhase = 0; break; + case 0x10: _roundPhase = roundPeriod / 4; break; + case 0x20: _roundPhase = roundPeriod / 2; break; + case 0x30: _roundPhase = roundPeriod * 3 / 4; break; + } + + // bits 3-0 are the threshold + if ((mode & 0xF) == 0) + _roundThreshold = roundPeriod - 1; + else + _roundThreshold = ((mode & 0xF) - 4) * roundPeriod / 8; + } + + void MoveIndirectRelative(int flags) + { + // this instruction tries to make the current distance between a given point + // and the reference point rp0 be equivalent to the same distance in the original outline + // there are a bunch of flags that control how that distance is measured + var cvt = ReadCvt(); + var pointIndex = _stack.Pop(); + + if (Math.Abs(cvt - _state.SingleWidthValue) < _state.SingleWidthCutIn) + { + if (cvt >= 0) + cvt = _state.SingleWidthValue; + else + cvt = -_state.SingleWidthValue; + } + + // if we're looking at the twilight zone we need to prepare the points there + var originalReference = _zp0.GetOriginal(_state.Rp0); + if (_zp1.IsTwilight) + { + var initialValue = originalReference + _state.Freedom * cvt; + _zp1.Original[pointIndex].P = initialValue; + _zp1.Current[pointIndex].P = initialValue; + } + + var point = _zp1.GetCurrent(pointIndex); + var originalDistance = DualProject(_zp1.GetOriginal(pointIndex) - originalReference); + var currentDistance = Project(point - _zp0.GetCurrent(_state.Rp0)); + + if (_state.AutoFlip && Math.Sign(originalDistance) != Math.Sign(cvt)) + cvt = -cvt; + + // if bit 2 is set, round the distance and look at the cut-in value + var distance = cvt; + if ((flags & 0x4) != 0) + { + // only perform cut-in tests when both points are in the same zone + if (_zp0.IsTwilight == _zp1.IsTwilight && Math.Abs(cvt - originalDistance) > _state.ControlValueCutIn) + cvt = originalDistance; + distance = Round(cvt); + } + + // if bit 3 is set, constrain to the minimum distance + if ((flags & 0x8) != 0) + { + if (originalDistance >= 0) + distance = Math.Max(distance, _state.MinDistance); + else + distance = Math.Min(distance, -_state.MinDistance); + } + + // move the point + MovePoint(_zp1, pointIndex, distance - currentDistance); + _state.Rp1 = _state.Rp0; + _state.Rp2 = pointIndex; + if ((flags & 0x10) != 0) + _state.Rp0 = pointIndex; + } + + void MoveDirectRelative(int flags) + { + // determine the original distance between the two reference points + var pointIndex = _stack.Pop(); + var p1 = _zp0.GetOriginal(_state.Rp0); + var p2 = _zp1.GetOriginal(pointIndex); + var originalDistance = DualProject(p2 - p1); + + // single width cutin test + if (Math.Abs(originalDistance - _state.SingleWidthValue) < _state.SingleWidthCutIn) + { + if (originalDistance >= 0) + originalDistance = _state.SingleWidthValue; + else + originalDistance = -_state.SingleWidthValue; + } + + // if bit 2 is set, perform rounding + var distance = originalDistance; + if ((flags & 0x4) != 0) + distance = Round(distance); + + // if bit 3 is set, constrain to the minimum distance + if ((flags & 0x8) != 0) + { + if (originalDistance >= 0) + distance = Math.Max(distance, _state.MinDistance); + else + distance = Math.Min(distance, -_state.MinDistance); + } + + // move the point + originalDistance = Project(_zp1.GetCurrent(pointIndex) - _zp0.GetCurrent(_state.Rp0)); + MovePoint(_zp1, pointIndex, distance - originalDistance); + _state.Rp1 = _state.Rp0; + _state.Rp2 = pointIndex; + if ((flags & 0x10) != 0) + _state.Rp0 = pointIndex; + } + + Vector2 ComputeDisplacement(int mode, out Zone zone, out int point) + { + // compute displacement of the reference point + if ((mode & 1) == 0) + { + zone = _zp1; + point = _state.Rp2; + } + else + { + zone = _zp0; + point = _state.Rp1; + } + + var distance = Project(zone.GetCurrent(point) - zone.GetOriginal(point)); + return distance * _state.Freedom / _fdotp; + } + + TouchState GetTouchState() + { + var touch = TouchState.None; + if (_state.Freedom.X != 0) + touch = TouchState.X; + if (_state.Freedom.Y != 0) + touch |= TouchState.Y; + + return touch; + } + + void ShiftPoints(Vector2 displacement) + { + var touch = GetTouchState(); + for (int i = 0; i < _state.Loop; i++) + { + var pointIndex = _stack.Pop(); + _zp2.Current[pointIndex].P += displacement; + _zp2.TouchState[pointIndex] |= touch; + } + _state.Loop = 1; + } + + void MovePoint(Zone zone, int index, float distance) + { + var point = zone.GetCurrent(index) + distance * _state.Freedom / _fdotp; + var touch = GetTouchState(); + zone.Current[index].P = point; + zone.TouchState[index] |= touch; + } + + float Round(float value) + { + switch (_state.RoundState) + { + case RoundMode.ToGrid: return value >= 0 ? (float)Math.Round(value) : -(float)Math.Round(-value); + case RoundMode.ToHalfGrid: return value >= 0 ? (float)Math.Floor(value) + 0.5f : -((float)Math.Floor(-value) + 0.5f); + case RoundMode.ToDoubleGrid: return value >= 0 ? (float)(Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2) : -(float)(Math.Round(-value * 2, MidpointRounding.AwayFromZero) / 2); + case RoundMode.DownToGrid: return value >= 0 ? (float)Math.Floor(value) : -(float)Math.Floor(-value); + case RoundMode.UpToGrid: return value >= 0 ? (float)Math.Ceiling(value) : -(float)Math.Ceiling(-value); + case RoundMode.Super: + case RoundMode.Super45: + float result; + if (value >= 0) + { + result = value - _roundPhase + _roundThreshold; + result = (float)Math.Truncate(result / roundPeriod) * roundPeriod; + result += _roundPhase; + if (result < 0) + result = _roundPhase; + } + else + { + result = -value - _roundPhase + _roundThreshold; + result = -(float)Math.Truncate(result / roundPeriod) * roundPeriod; + result -= _roundPhase; + if (result > 0) + result = -_roundPhase; + } + return result; + + default: return value; + } + } + + float Project(Vector2 point) { return (float)Vector2.Dot(point, _state.Projection); } + float DualProject(Vector2 point) { return (float)Vector2.Dot(point, _state.DualProjection); } + + static OpCode SkipNext(ref InstructionStream stream) + { + // grab the next opcode, and if it's one of the push instructions skip over its arguments + var opcode = stream.NextOpCode(); + switch (opcode) + { + case OpCode.NPUSHB: + case OpCode.PUSHB1: + case OpCode.PUSHB2: + case OpCode.PUSHB3: + case OpCode.PUSHB4: + case OpCode.PUSHB5: + case OpCode.PUSHB6: + case OpCode.PUSHB7: + case OpCode.PUSHB8: + { + var count = opcode == OpCode.NPUSHB ? stream.NextByte() : opcode - OpCode.PUSHB1 + 1; + for (int i = 0; i < count; i++) + stream.NextByte(); + } + break; + case OpCode.NPUSHW: + case OpCode.PUSHW1: + case OpCode.PUSHW2: + case OpCode.PUSHW3: + case OpCode.PUSHW4: + case OpCode.PUSHW5: + case OpCode.PUSHW6: + case OpCode.PUSHW7: + case OpCode.PUSHW8: + { + var count = opcode == OpCode.NPUSHW ? stream.NextByte() : opcode - OpCode.PUSHW1 + 1; + for (int i = 0; i < count; i++) + stream.NextWord(); + } + break; + } + + return opcode; + } + + static unsafe void InterpolatePoints(byte* current, byte* original, int start, int end, int ref1, int ref2) + { + if (start > end) + return; + + // figure out how much the two reference points + // have been shifted from their original positions + float delta1, delta2; + float lower = *GetPoint(original, ref1); + float upper = *GetPoint(original, ref2); + if (lower > upper) + { + var temp = lower; + lower = upper; + upper = temp; + + delta1 = *GetPoint(current, ref2) - lower; + delta2 = *GetPoint(current, ref1) - upper; + } + else + { + delta1 = *GetPoint(current, ref1) - lower; + delta2 = *GetPoint(current, ref2) - upper; + } + + float lowerCurrent = delta1 + lower; + float upperCurrent = delta2 + upper; + float scale = (upperCurrent - lowerCurrent) / (upper - lower); + + for (int i = start; i <= end; i++) + { + // three cases: if it's to the left of the lower reference point or to + // the right of the upper reference point, do a shift based on that ref point. + // otherwise, interpolate between the two of them + float pos = *GetPoint(original, i); + if (pos <= lower) + { + pos += delta1; + } + else if (pos >= upper) + { + pos += delta2; + } + else + { + pos = lowerCurrent + (pos - lower) * scale; + } + *GetPoint(current, i) = pos; + } + } + + static void InterpolatePointsXAxis(GlyphPointF[] current, GlyphPointF[] original, int start, int end, int ref1, int ref2) + { + if (start > end) + return; + + // figure out how much the two reference points + // have been shifted from their original positions + float delta1, delta2; + float lower = original[ref1].X; + float upper = original[ref2].X; + if (lower > upper) + { + var temp = lower; + lower = upper; + upper = temp; + + delta1 = current[ref2].X - lower; + delta2 = current[ref1].X - upper; + } + else + { + delta1 = current[ref1].X - lower; + delta2 = current[ref2].X - upper; + } + + float lowerCurrent = delta1 + lower; + float upperCurrent = delta2 + upper; + float scale = (upperCurrent - lowerCurrent) / (upper - lower); + + for (int i = start; i <= end; i++) + { + // three cases: if it's to the left of the lower reference point or to + // the right of the upper reference point, do a shift based on that ref point. + // otherwise, interpolate between the two of them + float pos = original[i].X; + if (pos <= lower) + { + pos += delta1; + } + else if (pos >= upper) + { + pos += delta2; + } + else + { + pos = lowerCurrent + (pos - lower) * scale; + } + current[i].UpdateX(pos); + } + } + static void InterpolatePointsYAxis(GlyphPointF[] current, GlyphPointF[] original, int start, int end, int ref1, int ref2) + { + if (start > end) + return; + + // figure out how much the two reference points + // have been shifted from their original positions + float delta1, delta2; + float lower = original[ref1].Y; + float upper = original[ref2].Y; + if (lower > upper) + { + float temp = lower; //swap + lower = upper; + upper = temp; + + delta1 = current[ref2].Y - lower; + delta2 = current[ref1].Y - upper; + } + else + { + delta1 = current[ref1].Y - lower; + delta2 = current[ref2].Y - upper; + } + + float lowerCurrent = delta1 + lower; + float upperCurrent = delta2 + upper; + float scale = (upperCurrent - lowerCurrent) / (upper - lower); + + for (int i = start; i <= end; i++) + { + // three cases: if it's to the left of the lower reference point or to + // the right of the upper reference point, do a shift based on that ref point. + // otherwise, interpolate between the two of them + float pos = original[i].Y; + if (pos <= lower) + { + pos += delta1; + } + else if (pos >= upper) + { + pos += delta2; + } + else + { + pos = lowerCurrent + (pos - lower) * scale; + } + current[i].UpdateY(pos); + } + } + static float F2Dot14ToFloat(int value) => (short)value / 16384.0f; + static int FloatToF2Dot14(float value) => (int)(uint)(short)Math.Round(value * 16384.0f); + static float F26Dot6ToFloat(int value) => value / 64.0f; + static int FloatToF26Dot6(float value) => (int)Math.Round(value * 64.0f); + + //TODO: review here again + unsafe static float* GetPoint(byte* data, int index) => (float*)(data + sizeof(GlyphPointF) * index); + + static readonly float Sqrt2Over2 = (float)(Math.Sqrt(2) / 2); + + const int MaxCallStack = 128; + const float Epsilon = 0.000001f; + + struct InstructionStream + { + readonly byte[] _instructions; + int _ip; + + public bool IsValid => _instructions != null; + public bool Done => _ip >= _instructions.Length; + + public InstructionStream(byte[] instructions) + { + _instructions = instructions; + _ip = 0; + } + + public int NextByte() + { + if (Done) + throw new InvalidTrueTypeFontException(); + return _instructions[_ip++]; + } + + public OpCode NextOpCode() => (OpCode)NextByte(); + public int NextWord() => (short)(ushort)(NextByte() << 8 | NextByte()); + public void Jump(int offset) { _ip += offset; } + } + + struct GraphicsState + { + public Vector2 Freedom; + public Vector2 DualProjection; + public Vector2 Projection; + public InstructionControlFlags InstructionControl; + public RoundMode RoundState; + public float MinDistance; + public float ControlValueCutIn; + public float SingleWidthCutIn; + public float SingleWidthValue; + public int DeltaBase; + public int DeltaShift; + public int Loop; + public int Rp0; + public int Rp1; + public int Rp2; + public bool AutoFlip; + + public void Reset() + { + Freedom = Vector2.UnitX; + Projection = Vector2.UnitX; + DualProjection = Vector2.UnitX; + InstructionControl = InstructionControlFlags.None; + RoundState = RoundMode.ToGrid; + MinDistance = 1.0f; + ControlValueCutIn = 17.0f / 16.0f; + SingleWidthCutIn = 0.0f; + SingleWidthValue = 0.0f; + DeltaBase = 9; + DeltaShift = 3; + Loop = 1; + Rp0 = Rp1 = Rp2 = 0; + AutoFlip = true; + } + } + + class ExecutionStack + { + int[] _s; + int _count; + + public ExecutionStack(int maxStack) + { + _s = new int[maxStack]; + } + + public int Peek() => Peek(0); + public bool PopBool() => Pop() != 0; + public float PopFloat() => F26Dot6ToFloat(Pop()); + public void Push(bool value) => Push(value ? 1 : 0); + public void Push(float value) => Push(FloatToF26Dot6(value)); + + public void Clear() => _count = 0; + public void Depth() => Push(_count); + public void Duplicate() => Push(Peek()); + public void Copy() => Copy(Pop() - 1); + public void Copy(int index) => Push(Peek(index)); + public void Move() => Move(Pop() - 1); + public void Roll() => Move(2); + + public void Move(int index) + { + var val = Peek(index); + for (int i = _count - index - 1; i < _count - 1; i++) + _s[i] = _s[i + 1]; + _s[_count - 1] = val; + } + + public void Swap() + { + if (_count < 2) + throw new InvalidTrueTypeFontException(); + + var tmp = _s[_count - 1]; + _s[_count - 1] = _s[_count - 2]; + _s[_count - 2] = tmp; + } + + public void Push(int value) + { + if (_count == _s.Length) + throw new InvalidTrueTypeFontException(); + _s[_count++] = value; + } + + public int Pop() + { + if (_count == 0) + throw new InvalidTrueTypeFontException(); + return _s[--_count]; + } + + public int Peek(int index) + { + if (index < 0 || index >= _count) + throw new InvalidTrueTypeFontException(); + return _s[_count - index - 1]; + } + } + + readonly struct Zone + { + public readonly GlyphPointF[] Current; + public readonly GlyphPointF[] Original; + public readonly TouchState[] TouchState; + public readonly bool IsTwilight; + + public Zone(GlyphPointF[] points, bool isTwilight) + { + IsTwilight = isTwilight; + Current = points; + Original = (GlyphPointF[])points.Clone(); + TouchState = new TouchState[points.Length]; + } + + public Vector2 GetCurrent(int index) => Current[index].P; + public Vector2 GetOriginal(int index) => Original[index].P; + } + + enum RoundMode + { + ToHalfGrid, + ToGrid, + ToDoubleGrid, + DownToGrid, + UpToGrid, + Off, + Super, + Super45 + } + + [Flags] + enum InstructionControlFlags + { + None, + InhibitGridFitting = 0x1, + UseDefaultGraphicsState = 0x2 + } + + [Flags] + enum TouchState + { + None = 0, + X = 0x1, + Y = 0x2, + Both = X | Y + } + + enum OpCode : byte + { + SVTCA0, + SVTCA1, + SPVTCA0, + SPVTCA1, + SFVTCA0, + SFVTCA1, + SPVTL0, + SPVTL1, + SFVTL0, + SFVTL1, + SPVFS, + SFVFS, + GPV, + GFV, + SFVTPV, + ISECT, + SRP0, + SRP1, + SRP2, + SZP0, + SZP1, + SZP2, + SZPS, + SLOOP, + RTG, + RTHG, + SMD, + ELSE, + JMPR, + SCVTCI, + SSWCI, + SSW, + DUP, + POP, + CLEAR, + SWAP, + DEPTH, + CINDEX, + MINDEX, + ALIGNPTS, + /* unused: 0x28 */ + UTP = 0x29, + LOOPCALL, + CALL, + FDEF, + ENDF, + MDAP0, + MDAP1, + IUP0, + IUP1, + SHP0, + SHP1, + SHC0, + SHC1, + SHZ0, + SHZ1, + SHPIX, + IP, + MSIRP0, + MSIRP1, + ALIGNRP, + RTDG, + MIAP0, + MIAP1, + NPUSHB, + NPUSHW, + WS, + RS, + WCVTP, + RCVT, + GC0, + GC1, + SCFS, + MD0, + MD1, + MPPEM, + MPS, + FLIPON, + FLIPOFF, + DEBUG, + LT, + LTEQ, + GT, + GTEQ, + EQ, + NEQ, + ODD, + EVEN, + IF, + EIF, + AND, + OR, + NOT, + DELTAP1, + SDB, + SDS, + ADD, + SUB, + DIV, + MUL, + ABS, + NEG, + FLOOR, + CEILING, + ROUND0, + ROUND1, + ROUND2, + ROUND3, + NROUND0, + NROUND1, + NROUND2, + NROUND3, + WCVTF, + DELTAP2, + DELTAP3, + DELTAC1, + DELTAC2, + DELTAC3, + SROUND, + S45ROUND, + JROT, + JROF, + ROFF, + /* unused: 0x7B */ + RUTG = 0x7C, + RDTG, + SANGW, + AA, + FLIPPT, + FLIPRGON, + FLIPRGOFF, + /* unused: 0x83 - 0x84 */ + SCANCTRL = 0x85, + SDPVTL0, + SDPVTL1, + GETINFO, + IDEF, + ROLL, + MAX, + MIN, + SCANTYPE, + INSTCTRL, + /* unused: 0x8F - 0xAF */ + PUSHB1 = 0xB0, + PUSHB2, + PUSHB3, + PUSHB4, + PUSHB5, + PUSHB6, + PUSHB7, + PUSHB8, + PUSHW1, + PUSHW2, + PUSHW3, + PUSHW4, + PUSHW5, + PUSHW6, + PUSHW7, + PUSHW8, + MDRP, // range of 32 values, 0xC0 - 0xDF, + MIRP = 0xE0 // range of 32 values, 0xE0 - 0xFF + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface.cs new file mode 100644 index 00000000..e49ee940 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface.cs @@ -0,0 +1,569 @@ +//Apache2, 2017-present, WinterDev +//Apache2, 2014-2016, Samuel Carlsson, WinterDev +using System; +using System.Collections.Generic; +using Typography.OpenFont.Tables; + +namespace Typography.OpenFont +{ + + + public partial class Typeface + { + + //TODO: implement vertical metrics + HorizontalMetrics _hMetrics; + NameEntry _nameEntry; + Glyph[] _glyphs; + CFF.Cff1FontSet _cff1FontSet; + TableHeader[] _tblHeaders; + bool _hasTtfOutline; + bool _hasCffData; + internal bool _useTypographicMertic; +#if DEBUG + static int s_dbugTotalId; + public readonly int dbugId = ++s_dbugTotalId; +#endif + + internal Typeface() + { + //blank typefaces +#if DEBUG + if (dbugId == 5) + { + + } +#endif + } + + internal void SetTableEntryCollection(TableHeader[] headers) => _tblHeaders = headers; + + internal void SetBasicTypefaceTables(OS2Table os2Table, + NameEntry nameEntry, + Head head, + HorizontalMetrics horizontalMetrics) + { + OS2Table = os2Table; + _nameEntry = nameEntry; + Head = head; + Bounds = head.Bounds; + UnitsPerEm = head.UnitsPerEm; + _hMetrics = horizontalMetrics; + } + + internal Head Head { get; set; } + + internal void SetTtfGlyphs(Glyph[] glyphs) + { + _glyphs = glyphs; + _hasTtfOutline = true; + } + internal void SetBitmapGlyphs(Glyph[] glyphs, BitmapFontGlyphSource bitmapFontGlyphSource) + { + _glyphs = glyphs; + _bitmapFontGlyphSource = bitmapFontGlyphSource; + } + internal void SetCffFontSet(CFF.Cff1FontSet cff1FontSet) + { + _cff1FontSet = cff1FontSet; + _hasCffData = true; + + Glyph[] exisitingGlyphs = _glyphs; + + _glyphs = cff1FontSet._fonts[0]._glyphs; //TODO: review _fonts[0] + + if (exisitingGlyphs != null) + { + // +#if DEBUG + if (_glyphs.Length != exisitingGlyphs.Length) + { + throw new OpenFontNotSupportedException(); + } +#endif + for (int i = 0; i < exisitingGlyphs.Length; ++i) + { + Glyph.CopyExistingGlyphInfo(exisitingGlyphs[i], _glyphs[i]); + } + } + + } + + public Languages Languages { get; } = new Languages(); + /// + /// control values in Font unit + /// + internal int[] ControlValues { get; set; } + internal byte[] PrepProgramBuffer { get; set; } + internal byte[] FpgmProgramBuffer { get; set; } + + internal MaxProfile MaxProfile { get; set; } + internal Cmap CmapTable { get; set; } + internal Kern KernTable { get; set; } + internal Gasp GaspTable { get; set; } + internal HorizontalHeader HheaTable { get; set; } + public OS2Table OS2Table { get; set; } + // + public bool HasPrepProgramBuffer => PrepProgramBuffer != null; + + + /// + /// actual font filename (optional) + /// + public string Filename { get; set; } + /// + /// OS2 sTypoAscender/HheaTable.Ascent, in font designed unit + /// + public short Ascender => _useTypographicMertic ? OS2Table.sTypoAscender : HheaTable.Ascent; + + /// + /// OS2 sTypoDescender, in font designed unit + /// + public short Descender => _useTypographicMertic ? OS2Table.sTypoDescender : HheaTable.Descent; + /// + /// OS2 usWinAscender + /// + public ushort ClipedAscender => OS2Table.usWinAscent; + /// + /// OS2 usWinDescender + /// + public ushort ClipedDescender => OS2Table.usWinDescent; + + /// + /// OS2 Linegap + /// + public short LineGap => _useTypographicMertic ? OS2Table.sTypoLineGap : HheaTable.LineGap; + //The typographic line gap for this font. + //Remember that this is not the same as the LineGap value in the 'hhea' table, + //which Apple defines in a far different manner. + //The suggested usage for sTypoLineGap is + //that it be used in conjunction with unitsPerEm + //to compute a typographically correct default line spacing. + // + //Typical values average 7 - 10 % of units per em. + //The goal is to free applications from Macintosh or Windows - specific metrics + //which are constrained by backward compatability requirements + //(see chapter, “Recommendations for OpenType Fonts”). + //These new metrics, when combined with the character design widths, + //will allow applications to lay out documents in a typographically correct and portable fashion. + //These metrics will be exposed through Windows APIs. + //Macintosh applications will need to access the 'sfnt' resource and + //parse it to extract this data from the “OS / 2” table + //(unless Apple exposes the 'OS/2' table through a new API) + //--------------- + + public string Name => _nameEntry.FontName; + public string FontSubFamily => _nameEntry.FontSubFamily; + public string PostScriptName => _nameEntry.PostScriptName; + public string VersionString => _nameEntry.VersionString; + public string UniqueFontIden => _nameEntry.UniqueFontIden; + + internal NameEntry NameEntry => _nameEntry; + + public int GlyphCount => _glyphs.Length; + /// + /// find glyph index by codepoint + /// + /// + /// + /// + + public ushort GetGlyphIndex(int codepoint, int nextCodepoint, out bool skipNextCodepoint) + { + return CmapTable.GetGlyphIndex(codepoint, nextCodepoint, out skipNextCodepoint); + } + public ushort GetGlyphIndex(int codepoint) + { + return CmapTable.GetGlyphIndex(codepoint, 0, out bool skipNextCodepoint); + } + public void CollectUnicode(List unicodes) + { + CmapTable.CollectUnicode(unicodes); + } + public void CollectUnicode(int platform, List unicodes, List glyphIndexList) + { + CmapTable.CollectUnicode(platform, unicodes, glyphIndexList); + } + public Glyph GetGlyphByName(string glyphName) => GetGlyph(GetGlyphIndexByName(glyphName)); + + Dictionary _cachedGlyphDicByName; + + void UpdateCff1FontSetNamesCache() + { + if (_cff1FontSet != null && _cachedGlyphDicByName == null) + { + //create cache data + _cachedGlyphDicByName = new Dictionary(); + for (int i = 1; i < _glyphs.Length; ++i) + { + Glyph glyph = _glyphs[i]; + + if (glyph._cff1GlyphData != null && glyph._cff1GlyphData.Name != null) + { + _cachedGlyphDicByName.Add(glyph._cff1GlyphData.Name, (ushort)i); + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("Cff unknown glyphname"); +#endif + } + } + } + } + public ushort GetGlyphIndexByName(string glyphName) + { + if (glyphName == null) return 0; + + if (_cff1FontSet != null && _cachedGlyphDicByName == null) + { + //we create a dictionary + //create cache data + _cachedGlyphDicByName = new Dictionary(); + for (int i = 1; i < _glyphs.Length; ++i) + { + Glyph glyph = _glyphs[i]; + if (glyph._cff1GlyphData.Name != null) + { + _cachedGlyphDicByName.Add(glyph._cff1GlyphData.Name, (ushort)i); + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("Cff unknown glyphname"); +#endif + } + } + return _cachedGlyphDicByName.TryGetValue(glyphName, out ushort glyphIndex) ? glyphIndex : (ushort)0; + } + else if (PostTable != null) + { + if (PostTable.Version == 2) + { + return PostTable.GetGlyphIndex(glyphName); + } + else + { + //check data from adobe glyph list + //from the unicode value + //select glyph index + + //we use AdobeGlyphList + //from https://github.com/adobe-type-tools/agl-aglfn/blob/master/glyphlist.txt + + //but user can provide their own map here... + + return GetGlyphIndex(AdobeGlyphList.GetUnicodeValueByGlyphName(glyphName)); + } + } + return 0; + } + + public IEnumerable GetGlyphNameIter() + { + if (_cachedGlyphDicByName == null && _cff1FontSet != null) + { + UpdateCff1FontSetNamesCache(); + } + + if (_cachedGlyphDicByName != null) + { + //iter from here + foreach (var kv in _cachedGlyphDicByName) + { + yield return new GlyphNameMap(kv.Value, kv.Key); + } + } + else if (PostTable.Version == 2) + { + foreach (var kp in PostTable.GlyphNames) + { + yield return new GlyphNameMap(kp.Key, kp.Value); + } + } + } + public Glyph GetGlyph(ushort glyphIndex) + { + if (glyphIndex < _glyphs.Length) + { + return _glyphs[glyphIndex]; + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("found unknown glyph:" + glyphIndex); +#endif + return _glyphs[0]; //return empty glyph?; + } + } + + public ushort GetAdvanceWidthFromGlyphIndex(ushort glyphIndex) => _hMetrics.GetAdvanceWidth(glyphIndex); + public short GetLeftSideBearing(ushort glyphIndex) => _hMetrics.GetLeftSideBearing(glyphIndex); + public short GetKernDistance(ushort leftGlyphIndex, ushort rightGlyphIndex) + { + //DEPRECATED -> use OpenFont layout instead + return this.KernTable.GetKerningDistance(leftGlyphIndex, rightGlyphIndex); + } + // + public Bounds Bounds { get; private set; } + public ushort UnitsPerEm { get; private set; } + public short UnderlinePosition => PostTable.UnderlinePosition; //TODO: review here + // + + const int s_pointsPerInch = 72;//point per inch, fix? + + /// + /// default dpi + /// + public static uint DefaultDpi { get; set; } = 96; + + /// + /// convert from point-unit value to pixel value + /// + /// + /// dpi + /// + public static float ConvPointsToPixels(float targetPointSize, int resolution = -1) + { + //http://stackoverflow.com/questions/139655/convert-pixels-to-points + //points = pixels * 72 / 96 + //------------------------------------------------ + //pixels = targetPointSize * 96 /72 + //pixels = targetPointSize * resolution / pointPerInch + + if (resolution < 0) + { + //use current DefaultDPI + resolution = (int)DefaultDpi; + } + + return targetPointSize * resolution / s_pointsPerInch; + } + /// + /// calculate scale to target pixel size based on current typeface's UnitsPerEm + /// + /// target font size in point unit + /// + public float CalculateScaleToPixel(float targetPixelSize) + { + //1. return targetPixelSize / UnitsPerEm + return targetPixelSize / this.UnitsPerEm; + } + /// + /// calculate scale to target pixel size based on current typeface's UnitsPerEm + /// + /// target font size in point unit + /// dpi + /// + public float CalculateScaleToPixelFromPointSize(float targetPointSize, int resolution = -1) + { + //1. var sizeInPixels = ConvPointsToPixels(sizeInPointUnit); + //2. return sizeInPixels / UnitsPerEm + + if (resolution < 0) + { + //use current DefaultDPI + resolution = (int)DefaultDpi; + } + return (targetPointSize * resolution / s_pointsPerInch) / this.UnitsPerEm; + } + + + internal BASE BaseTable { get; set; } + internal GDEF GDEFTable { get; set; } + + public COLR COLRTable { get; private set; } + public CPAL CPALTable { get; private set; } + + internal bool HasColorAndPal { get; private set; } + internal void SetColorAndPalTable(COLR colr, CPAL cpal) + { + COLRTable = colr; + CPALTable = cpal; + HasColorAndPal = colr != null; + } + + public GPOS GPOSTable { get; internal set; } + public GSUB GSUBTable { get; internal set; } + + internal void LoadOpenFontLayoutInfo(GDEF gdefTable, GSUB gsubTable, GPOS gposTable, BASE baseTable, COLR colrTable, CPAL cpalTable) + { + + //*** + this.GDEFTable = gdefTable; + this.GSUBTable = gsubTable; + this.GPOSTable = gposTable; + this.BaseTable = baseTable; + this.COLRTable = colrTable; + this.CPALTable = cpalTable; + //--------------------------- + //fill glyph definition + if (gdefTable != null) + { + gdefTable.FillGlyphData(_glyphs); + } + } + + internal PostTable PostTable { get; set; } + internal bool _evalCffGlyphBounds; + public bool IsCffFont => _hasCffData; + + //Math Table + + MathGlyphs.MathGlyphInfo[] _mathGlyphInfos; + internal MathTable _mathTable; + // + public MathGlyphs.MathConstants MathConsts => _mathTable?._mathConstTable; + internal void LoadMathGlyphInfos(MathGlyphs.MathGlyphInfo[] mathGlyphInfos) + { + _mathGlyphInfos = mathGlyphInfos; + if (mathGlyphInfos != null) + { + //fill to original glyph? + for (int glyphIndex = 0; glyphIndex < _glyphs.Length; ++glyphIndex) + { + _glyphs[glyphIndex].MathGlyphInfo = mathGlyphInfos[glyphIndex]; + } + } + } + public MathGlyphs.MathGlyphInfo GetMathGlyphInfo(ushort glyphIndex) => _mathGlyphInfos[glyphIndex]; + + //------------------------- + //svg and bitmap font + SvgTable _svgTable; + + internal bool HasSvgTable { get; private set; } + internal void SetSvgTable(SvgTable svgTable) + { + HasSvgTable = (_svgTable = svgTable) != null; + } + public void ReadSvgContent(ushort glyphIndex, System.Text.StringBuilder output) => _svgTable?.ReadSvgContent(glyphIndex, output); + + + internal BitmapFontGlyphSource _bitmapFontGlyphSource; + public bool IsBitmapFont => _bitmapFontGlyphSource != null; + public void ReadBitmapContent(Glyph glyph, System.IO.Stream output) + { + _bitmapFontGlyphSource.CopyBitmapContent(glyph, output); + } + + /// + /// undate lang info + /// + /// + internal void UpdateLangs(Meta metaTable) => Languages.Update(OS2Table, metaTable, CmapTable, this.GSUBTable, this.GPOSTable); + + + + internal ushort _whitespaceWidth; //common used value + + internal void UpdateFrequentlyUsedValues() + { + //whitespace + ushort whitespace_glyphIndex = this.GetGlyphIndex(' '); + if (whitespace_glyphIndex > 0) + { + _whitespaceWidth = this.GetAdvanceWidthFromGlyphIndex(whitespace_glyphIndex); + } + } + +#if DEBUG + public override string ToString() => Name; +#endif + + } + + public interface IGlyphPositions + { + int Count { get; } + + GlyphClassKind GetGlyphClassKind(int index); + void AppendGlyphOffset(int index, short appendOffsetX, short appendOffsetY); + void AppendGlyphAdvance(int index, short appendAdvX, short appendAdvY); + + ushort GetGlyph(int index, out short advW); + ushort GetGlyph(int index, out ushort inputOffset, out short offsetX, out short offsetY, out short advW); + // + void GetOffset(int index, out short offsetX, out short offsetY); + } + + + public static class StringUtils + { + public static void FillWithCodepoints(List codepoints, char[] str, int startAt = 0, int len = -1) + { + + if (len == -1) len = str.Length; + // this is important! + // ----------------------- + // from @samhocevar's PR: (https://github.com/LayoutFarm/Typography/pull/56/commits/b71c7cf863531ebf5caa478354d3249bde40b96e) + // In many places, "char" is not a valid type to handle characters, because it + // only supports 16 bits.In order to handle the full range of Unicode characters, + // we need to use "int". + // This allows characters such as 🙌 or 𐐷 or to be treated as single codepoints even + // though they are encoded as two "char"s in a C# string. + for (int i = 0; i < len; ++i) + { + char ch = str[startAt + i]; + int codepoint = ch; + if (char.IsHighSurrogate(ch) && i + 1 < len) + { + char nextCh = str[startAt + i + 1]; + if (char.IsLowSurrogate(nextCh)) + { + ++i; + codepoint = char.ConvertToUtf32(ch, nextCh); + } + } + codepoints.Add(codepoint); + } + } + public static IEnumerable GetCodepoints(char[] str, int startAt = 0, int len = -1) + { + if (len == -1) len = str.Length; + // this is important! + // ----------------------- + // from @samhocevar's PR: (https://github.com/LayoutFarm/Typography/pull/56/commits/b71c7cf863531ebf5caa478354d3249bde40b96e) + // In many places, "char" is not a valid type to handle characters, because it + // only supports 16 bits.In order to handle the full range of Unicode characters, + // we need to use "int". + // This allows characters such as 🙌 or 𐐷 or to be treated as single codepoints even + // though they are encoded as two "char"s in a C# string. + for (int i = 0; i < len; ++i) + { + char ch = str[startAt + i]; + int codepoint = ch; + if (char.IsHighSurrogate(ch) && i + 1 < len) + { + char nextCh = str[startAt + i + 1]; + if (char.IsLowSurrogate(nextCh)) + { + ++i; + codepoint = char.ConvertToUtf32(ch, nextCh); + } + } + yield return codepoint; + } + } + } + + + + + public readonly struct GlyphNameMap + { + public readonly ushort glyphIndex; + public readonly string glyphName; + public GlyphNameMap(ushort glyphIndex, string glyphName) + { + this.glyphIndex = glyphIndex; + this.glyphName = glyphName; + } + } + + +} + + + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_Extensions.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_Extensions.cs new file mode 100644 index 00000000..69a16740 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_Extensions.cs @@ -0,0 +1,652 @@ +//Apache2, 2017-present, WinterDev + + +using Typography.OpenFont.Tables; + +namespace Typography.OpenFont.Extensions +{ + public enum LineSpacingChoice + { + TypoMetric, + Windows, + Mac + } + + public readonly struct OS2FsSelection + { + //Bit # macStyle bit C definition Description + //0 bit 1 ITALIC Font contains italic or oblique characters, otherwise they are upright. + //1 UNDERSCORE Characters are underscored. + //2 NEGATIVE Characters have their foreground and background reversed. + //3 OUTLINED Outline(hollow) characters, otherwise they are solid. + //4 STRIKEOUT Characters are overstruck. + //5 bit 0 BOLD Characters are emboldened. + //6 REGULAR Characters are in the standard weight / style for the font. + //7 USE_TYPO_METRICS If set, it is strongly recommended to use OS / 2.sTypoAscender - OS / 2.sTypoDescender + OS / 2.sTypoLineGap as a value for default line spacing for this font. + //8 WWS The font has ‘name’ table strings consistent with a weight / width / slope family without requiring use of ‘name’ IDs 21 and 22. (Please see more detailed description below.) + //9 OBLIQUE Font contains oblique characters. + readonly ushort _fsSelection; + public OS2FsSelection(ushort fsSelection) + { + _fsSelection = fsSelection; + } + public bool IsItalic => (_fsSelection & 0x1) != 0; + public bool IsUnderScore => ((_fsSelection >> 1) & 0x1) != 0; + public bool IsNegative => ((_fsSelection >> 2) & 0x1) != 0; + public bool IsOutline => ((_fsSelection >> 3) & 0x1) != 0; + public bool IsStrikeOut => ((_fsSelection >> 4) & 0x1) != 0; + public bool IsBold => ((_fsSelection >> 5) & 0x1) != 0; + public bool IsRegular => ((_fsSelection >> 6) & 0x1) != 0; + public bool USE_TYPO_METRICS => ((_fsSelection >> 7) & 0x1) != 0; + public bool WWS => ((_fsSelection >> 8) & 0x1) != 0; + public bool IsOblique => ((_fsSelection >> 9) & 0x1) != 0; + + } + + + + [System.Flags] + public enum TranslatedOS2FontStyle : ushort + { + + //@prepare's note, please note:=> this is not real value, this is 'translated' value from OS2.fsSelection + + UNSET = 0, + + ITALIC = 1, + BOLD = 1 << 1, + REGULAR = 1 << 2, + OBLIQUE = 1 << 3, + } + + + public enum OS2WidthClass : byte + { + //from https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass + + // + //Value Description C Definition % of normal + //1 Ultra-condensed FWIDTH_ULTRA_CONDENSED 50 + //2 Extra-condensed FWIDTH_EXTRA_CONDENSED 62.5 + //3 Condensed FWIDTH_CONDENSED 75 + //4 Semi-condensed FWIDTH_SEMI_CONDENSED 87.5 + //5 Medium (normal) FWIDTH_NORMAL 100 + //6 Semi-expanded FWIDTH_SEMI_EXPANDED 112.5 + //7 Expanded FWIDTH_EXPANDED 125 + //8 Extra-expanded FWIDTH_EXTRA_EXPANDED 150 + //9 Ultra-expanded FWIDTH_ULTRA_EXPANDED 200 + + Unknown,//@prepare's => my custom + UltraCondensed, + ExtraCondensed, + Condensed, + SemiCondensed, + Medium = 5, + Normal = 5, + SemiExpanded = 6, + Expanded = 7, + ExtraExpanded = 8, + UltraExpanded = 9 + } + + public static partial class TypefaceExtensions + { + + public static ushort GetWhitespaceWidth(this Typeface typeface) => typeface._whitespaceWidth; + + public static bool RecommendToUseTypoMetricsForLineSpacing(this Typeface typeface) + { + //https://www.microsoft.com/typography/otspec/os2.htm + // + //fsSelection ... + // + //bit name + //7 USE_TYPO_METRICS + // + // Description + // If set, it is strongly recommended to use + // OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap + // as a value for default line spacing for this font. + + return ((typeface.OS2Table.fsSelection >> 7) & 1) != 0; + } + + public static TranslatedOS2FontStyle TranslateOS2FontStyle(this Typeface typeface) => TranslateOS2FontStyle(typeface.OS2Table); + + internal static TranslatedOS2FontStyle TranslateOS2FontStyle(OS2Table os2Table) + { + //@prepare's note, please note:=> this is not real value, this is 'translated' value from OS2.fsSelection + + + //https://www.microsoft.com/typography/otspec/os2.htm + //Bit # macStyle bit C definition Description + //0 bit 1 ITALIC Font contains italic or oblique characters, otherwise they are upright. + //1 UNDERSCORE Characters are underscored. + //2 NEGATIVE Characters have their foreground and background reversed. + //3 OUTLINED Outline(hollow) characters, otherwise they are solid. + //4 STRIKEOUT Characters are overstruck. + //5 bit 0 BOLD Characters are emboldened. + //6 REGULAR Characters are in the standard weight / style for the font. + //7 USE_TYPO_METRICS If set, it is strongly recommended to use OS / 2.sTypoAscender - OS / 2.sTypoDescender + OS / 2.sTypoLineGap as a value for default line spacing for this font. + //8 WWS The font has ‘name’ table strings consistent with a weight / width / slope family without requiring use of ‘name’ IDs 21 and 22. (Please see more detailed description below.) + //9 OBLIQUE Font contains oblique characters. + //10–15 < reserved > Reserved; set to 0. + ushort fsSelection = os2Table.fsSelection; + TranslatedOS2FontStyle result = Extensions.TranslatedOS2FontStyle.UNSET; + + if ((fsSelection & 0x1) != 0) + { + + result |= Extensions.TranslatedOS2FontStyle.ITALIC; + } + + if (((fsSelection >> 5) & 0x1) != 0) + { + result |= Extensions.TranslatedOS2FontStyle.BOLD; + } + + if (((fsSelection >> 6) & 0x1) != 0) + { + result |= Extensions.TranslatedOS2FontStyle.REGULAR; + } + if (((fsSelection >> 9) & 0x1) != 0) + { + result |= Extensions.TranslatedOS2FontStyle.OBLIQUE; + } + + return result; + } + + internal static OS2FsSelection TranslateOS2FsSelection(OS2Table os2Table) => new OS2FsSelection(os2Table.fsSelection); + + + + public static OS2WidthClass TranslateOS2WidthClass(ushort os2Weight) + { + if (os2Weight >= (ushort)OS2WidthClass.UltraExpanded) + { + return OS2WidthClass.Unknown; + } + else + { + return (OS2WidthClass)os2Weight; + } + } + + + /// + /// overall calculated line spacing + /// + static int Calculate_TypoMetricLineSpacing(Typeface typeface) + { + + //from https://www.microsoft.com/typography/OTSpec/recom.htm#tad + //sTypoAscender, sTypoDescender and sTypoLineGap + //sTypoAscender is used to determine the optimum offset from the top of a text frame to the first baseline. + //sTypoDescender is used to determine the optimum offset from the last baseline to the bottom of the text frame. + //The value of (sTypoAscender - sTypoDescender) is recommended to equal one em. + // + //While the OpenType specification allows for CJK (Chinese, Japanese, and Korean) fonts' sTypoDescender and sTypoAscender + //fields to specify metrics different from the HorizAxis.ideo and HorizAxis.idtp baselines in the 'BASE' table, + //CJK font developers should be aware that existing applications may not read the 'BASE' table at all but simply use + //the sTypoDescender and sTypoAscender fields to describe the bottom and top edges of the ideographic em-box. + //If developers want their fonts to work correctly with such applications, + //they should ensure that any ideographic em-box values in the 'BASE' table describe the same bottom and top edges as the sTypoDescender and + //sTypoAscender fields. + //See the sections “OpenType CJK Font Guidelines“ and ”Ideographic Em-Box“ for more details. + + //For Western fonts, the Ascender and Descender fields in Type 1 fonts' AFM files are a good source of sTypoAscender + //and sTypoDescender, respectively. + //The Minion Pro font family (designed on a 1000-unit em), + //for example, sets sTypoAscender = 727 and sTypoDescender = -273. + + //sTypoAscender, sTypoDescender and sTypoLineGap specify the recommended line spacing for single-spaced horizontal text. + //The baseline-to-baseline value is expressed by: + //OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap + + + + + //sTypoLineGap will usually be set by the font developer such that the value of the above expression is approximately 120% of the em. + //The application can use this value as the default horizontal line spacing. + //The Minion Pro font family (designed on a 1000-unit em), for example, sets sTypoLineGap = 200. + + + return typeface.Ascender - typeface.Descender + typeface.LineGap; + + } + + /// + /// calculate Baseline-to-Baseline Distance (BTBD) for Windows + /// + /// + /// return 'unscaled-to-pixel' BTBD value + static int Calculate_BTBD_Windows(Typeface typeface) + { + + //from https://www.microsoft.com/typography/otspec/recom.htm#tad + + //Baseline to Baseline Distances + //The 'OS/2' table fields sTypoAscender, sTypoDescender, and sTypoLineGap + //free applications from Macintosh-or Windows - specific metrics + //which are constrained by backward compatibility requirements. + // + //The following discussion only pertains to the platform-specific metrics. + //The suggested Baseline to Baseline Distance(BTBD) is computed differently for Windows and the Macintosh, + //and it is based on different OpenType metrics. + //However, if the recommendations below are followed, the BTBD will be the same for both Windows and the Mac. + + //Windows Metric OpenType Metric + //ascent usWinAscent + //descent usWinDescent + //internal leading usWinAscent + usWinDescent - unitsPerEm + //external leading MAX(0, LineGap - ((usWinAscent + usWinDescent) - (Ascender - Descender))) + + //The suggested BTBD = ascent + descent + external leading + + //It should be clear that the “external leading” can never be less than zero. + //Pixels above the ascent or below the descent will be clipped from the character; + //this is true for all output devices. + + //The usWinAscent and usWinDescent are values + //from the 'OS/2' table. + //The unitsPerEm value is from the 'head' table. + //The LineGap, Ascender and Descender values are from the 'hhea' table. + + int usWinAscent = typeface.OS2Table.usWinAscent; + int usWinDescent = typeface.OS2Table.usWinDescent; + int internal_leading = usWinAscent + usWinDescent - typeface.UnitsPerEm; + HorizontalHeader hhea = typeface.HheaTable; + int external_leading = System.Math.Max(0, hhea.LineGap - ((usWinAscent + usWinDescent) - (hhea.Ascent - hhea.Descent))); + return usWinAscent + usWinDescent + external_leading; + } + /// + /// calculate Baseline-to-Baseline Distance (BTBD) for macOS + /// + /// + /// return 'unscaled-to-pixel' BTBD value + static int CalculateBTBD_Mac(Typeface typeface) + { + //from https://www.microsoft.com/typography/otspec/recom.htm#tad + + //Ascender and Descender are metrics defined by Apple + //and are not to be confused with the Windows ascent or descent, + //nor should they be confused with the true typographic ascender and descender that are found in AFM files. + //The Macintosh metrics below are returned by the Apple Advanced Typography(AAT) GetFontInfo() API. + // + // + //Macintosh Metric OpenType Metric + //ascender Ascender + //descender Descender + //leading LineGap + + //The suggested BTBD = ascent + descent + leading + //If pixels extend above the ascent or below the descent, + //the character will be squashed in the vertical direction + //so that all pixels fit within these limitations; this is true for screen display only. + + //TODO: please test this + HorizontalHeader hhea = typeface.HheaTable; + return hhea.Ascent + hhea.Descent + hhea.LineGap; + } + + + public static int CalculateRecommendLineSpacing(this Typeface typeface, out LineSpacingChoice choice) + { + + //from https://docs.microsoft.com/en-us/typography/opentype/spec/os2#wa + //usWinAscent + //Format: uint16 + //Description: + //The “Windows ascender” metric. + //This should be used to specify the height above the baseline for a clipping region. + + //This is similar to the sTypoAscender field, + //and also to the ascender field in the 'hhea' table. + //There are important differences between these, however. + + //In the Windows GDI implementation, + //the usWinAscent and usWinDescent values have been used to determine + //the size of the bitmap surface in the TrueType rasterizer. + //Windows GDI will clip any portion of a TrueType glyph outline that appears above the usWinAscent value. + //If any clipping is unacceptable, then the value should be set greater than or equal to yMax. + + //Note: This pertains to the default position of glyphs, + //not their final position in layout after data from the GPOS or 'kern' table has been applied. + //Also, this clipping behavior also interacts with the VDMX table: + //if a VDMX table is present and there is data for the current device aspect ratio and rasterization size, + //then the VDMX data will supersede the usWinAscent and usWinDescent values. + + //**** + //Some legacy applications use the usWinAscent and usWinDescent values to determine default line spacing. + //This is **strongly discouraged**. The sTypo* fields should be used for this purpose. + + //Note that some applications use either the usWin* values or the sTypo* values to determine default line spacing, + //depending on whether the USE_TYPO_METRICS flag (bit 7) of the fsSelection field is set. + //This may be useful to provide **compatibility with legacy documents using older fonts**, + //while also providing better and more-portable layout using newer fonts. + //See fsSelection for additional details. + + //Applications that use the sTypo* fields for default line spacing can use the usWin* + //values to determine the size of a clipping region. + //Some applications use a clipping region for editing scenarios to determine what portion of the display surface to re-draw when text is edited, or how large a selection rectangle to draw when text is selected. This is an appropriate use for the usWin* values. + + //Early versions of this specification suggested that the usWinAscent value be computed as the yMax + //for all characters in the Windows “ANSI” character set. + + //For new fonts, the value should be determined based on the primary languages the font is designed to support, + //and **should take into consideration additional height that may be required to accommodate tall glyphs or mark positioning.*** + + //----------------------------------------------------------------------------------- + //usWinDescent + //Format: uint16 + //Description: + //The “Windows descender” metric.This should be used to specify the vertical extent + //below the baseline for a clipping region. + + //This is similar to the sTypoDescender field, + //and also to the descender field in the 'hhea' table. + + //*** + //There are important differences between these, however. + //Some of these differences are described below. + //In addition, the usWinDescent value treats distances below the baseline as positive values; + //thus, usWinDescent is usually a positive value, while sTypoDescender and hhea.descender are usually negative. + + //In the Windows GDI implementation, + //the usWinDescent and usWinAscent values have been used + //to determine the size of the bitmap surface in the TrueType rasterizer. + //Windows GDI will clip any portion of a TrueType glyph outline that appears below(-1 × usWinDescent). + //If any clipping is unacceptable, then the value should be set greater than or equal to(-yMin). + + //Note: This pertains to the default position of glyphs, + //not their final position in layout after data from the GPOS or 'kern' table has been applied. + //Also, this clipping behavior also interacts with the VDMX table: + //if a VDMX table is present and there is data for the current device aspect ratio and rasterization size, + //***then the VDMX data will supersede the usWinAscent and usWinDescent values.**** + //----------------------------------------------------------------------------------- + + //so ... + choice = LineSpacingChoice.TypoMetric; + return Calculate_TypoMetricLineSpacing(typeface); + + //if (RecommendToUseTypoMetricsForLineSpacing(typeface)) + //{ + // choice = LineSpacingChoice.TypoMetric; + // return Calculate_TypoMetricLineSpacing(typeface); + //} + //else + //{ + // //check if we are on Windows or mac + // if (CurrentEnv.CurrentOSName == CurrentOSName.Mac) + // { + // choice = LineSpacingChoice.Mac; + // return CalculateBTBD_Mac(typeface); + // } + // else + // { + // choice = LineSpacingChoice.Windows; + // return Calculate_BTBD_Windows(typeface); + // } + //} + + } + public static int CalculateRecommendLineSpacing(this Typeface typeface) + { + return CalculateMaxLineClipHeight(typeface); + //return CalculateRecommendLineSpacing(typeface, out var _); + } + public static int CalculateLineSpacing(this Typeface typeface, LineSpacingChoice choice) + { + switch (choice) + { + default: + case LineSpacingChoice.Windows: + return Calculate_BTBD_Windows(typeface); + case LineSpacingChoice.Mac: + return CalculateBTBD_Mac(typeface); + case LineSpacingChoice.TypoMetric: + return Calculate_TypoMetricLineSpacing(typeface); + } + } + public static int CalculateMaxLineClipHeight(this Typeface typeface) + { + //TODO: review here + return typeface.OS2Table.usWinAscent + typeface.OS2Table.usWinDescent; + } + + + //------------------------------------------- + public static bool HasMathTable(this Typeface typeface) => typeface.MathConsts != null; + + public static bool HasSvgTable(this Typeface typeface) => typeface.HasSvgTable; + + public static bool HasColorTable(this Typeface typeface) => typeface.HasColorAndPal; + + + class CffBoundFinder : IGlyphTranslator + { + + float _minX, _maxX, _minY, _maxY; + float _curX, _curY; + float _latestMove_X, _latestMove_Y; + /// + /// curve flatten steps => this a copy from Typography.Contours's GlyphPartFlattener + /// + int _nsteps = 3; + bool _contourOpen = false; + bool _first_eval = true; + public CffBoundFinder() + { + + } + public void Reset() + { + _curX = _curY = _latestMove_X = _latestMove_Y = 0; + _minX = _minY = float.MaxValue;//** + _maxX = _maxY = float.MinValue;//** + _first_eval = true; + _contourOpen = false; + } + public void BeginRead(int contourCount) + { + + } + public void EndRead() + { + + } + public void CloseContour() + { + _contourOpen = false; + _curX = _latestMove_X; + _curY = _latestMove_Y; + } + public void Curve3(float x1, float y1, float x2, float y2) + { + + //this a copy from Typography.Contours -> GlyphPartFlattener + + float eachstep = (float)1 / _nsteps; + float t = eachstep;//start + + for (int n = 1; n < _nsteps; ++n) + { + float c = 1.0f - t; + + UpdateMinMax( + (c * c * _curX) + (2 * t * c * x1) + (t * t * x2), //x + (c * c * _curY) + (2 * t * c * y1) + (t * t * y2)); //y + + t += eachstep; + } + + // + UpdateMinMax( + _curX = x2, + _curY = y2); + + _contourOpen = true; + } + + public void Curve4(float x1, float y1, float x2, float y2, float x3, float y3) + { + + //this a copy from Typography.Contours -> GlyphPartFlattener + + + float eachstep = (float)1 / _nsteps; + float t = eachstep;//start + + for (int n = 1; n < _nsteps; ++n) + { + float c = 1.0f - t; + + UpdateMinMax( + (_curX * c * c * c) + (x1 * 3 * t * c * c) + (x2 * 3 * t * t * c) + x3 * t * t * t, //x + (_curY * c * c * c) + (y1 * 3 * t * c * c) + (y2 * 3 * t * t * c) + y3 * t * t * t); //y + + t += eachstep; + } + // + UpdateMinMax( + _curX = x3, + _curY = y3); + + _contourOpen = true; + } + public void LineTo(float x1, float y1) + { + UpdateMinMax( + _curX = x1, + _curY = y1); + + _contourOpen = true; + } + public void MoveTo(float x0, float y0) + { + + if (_contourOpen) + { + CloseContour(); + } + + UpdateMinMax( + _curX = x0, + _curY = y0); + } + void UpdateMinMax(float x0, float y0) + { + + if (_first_eval) + { + //4 times + + if (x0 < _minX) + { + _minX = x0; + } + // + if (x0 > _maxX) + { + _maxX = x0; + } + // + if (y0 < _minY) + { + _minY = y0; + } + // + if (y0 > _maxY) + { + _maxY = y0; + } + + _first_eval = false; + } + else + { + //2 times + + if (x0 < _minX) + { + _minX = x0; + } + else if (x0 > _maxX) + { + _maxX = x0; + } + + if (y0 < _minY) + { + _minY = y0; + } + else if (y0 > _maxY) + { + _maxY = y0; + } + } + + } + + public Bounds GetResultBounds() + { + return new Bounds( + (short)System.Math.Floor(_minX), + (short)System.Math.Floor(_minY), + (short)System.Math.Ceiling(_maxX), + (short)System.Math.Ceiling(_maxY)); + } + } + public static void UpdateAllCffGlyphBounds(this Typeface typeface) + { + //TODO: review here again, + + if (typeface.IsCffFont && !typeface._evalCffGlyphBounds) + { + int j = typeface.GlyphCount; + CFF.CffEvaluationEngine evalEngine = new CFF.CffEvaluationEngine(); + CffBoundFinder boundFinder = new CffBoundFinder(); + for (ushort i = 0; i < j; ++i) + { + Glyph g = typeface.GetGlyph(i); + boundFinder.Reset(); + + evalEngine.Run(boundFinder, g._cff1GlyphData.GlyphInstructions); + + g.Bounds = boundFinder.GetResultBounds(); + } + typeface._evalCffGlyphBounds = true; + } + } + + + + //------------------------------------------- + //my custom extension + public static int GetCustomTypefaceKey(Typeface typeface) => typeface._typefaceKey; + public static int SetCustomTypefaceKey(Typeface typeface, int typefaceKey) => typeface._typefaceKey = typefaceKey; + } +} + +namespace Typography.OpenFont.Tables +{ + /// + /// access to some openfont table directly + /// + public static class TypefaceInternalTypeAccessExtensions + { + public static OS2Table GetOS2Table(this Typeface typeface) => typeface.OS2Table; + public static NameEntry GetNameEntry(this Typeface typeface) => typeface.NameEntry; + } +} + + +namespace Typography.OpenFont +{ + partial class Typeface + { + //my extension, use for store unique iden for the font + internal int _typefaceKey; + } +} \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_TrimableExtensions.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_TrimableExtensions.cs new file mode 100644 index 00000000..dceae0d1 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/Typeface_TrimableExtensions.cs @@ -0,0 +1,206 @@ +//MIT, 2020-present, WinterDev + +using System; +using System.IO; + +namespace Typography.OpenFont.Trimmable +{ + + //------------------------- + //This is our extension***, + //NOT in OpenFont spec + //------------------------- + //user can reload a new clone of glyphs with fewer detail + //or restore a new clone of glyphs with full detail + + //for unload and reload + + using Typography.OpenFont.Tables; + + public class RestoreTicket + { + internal RestoreTicket() + { + } + internal string TypefaceName { get; set; } + + internal TableHeader[] Headers; + internal bool HasTtf; + internal bool HasCff; + internal bool HasSvg; + internal bool HasBitmapSource; + + internal bool ControlValues; + internal bool PrepProgramBuffer; + internal bool FpgmProgramBuffer; + internal bool CPALTable; + internal bool COLRTable; + internal bool GaspTable; + } + + public enum TrimMode + { + /// + /// No trim, full glyph instruction + /// + No, //default + /// + /// only essential info for glyph layout + /// + EssentailLayoutInfo, + /// + /// restore again + /// + Restored, + } + + public static class TypefaceExtensions + { + public static RestoreTicket TrimDown(this Typeface typeface) => typeface.TrimDownAndRemoveGlyphBuildingDetail(); + public static TrimMode GetTrimMode(this Typeface typeface) => typeface._typefaceTrimMode; + public static bool IsTrimmed(this Typeface typeface) => typeface._typefaceTrimMode == TrimMode.EssentailLayoutInfo; + + public static void RestoreUp(this Typeface typeface, RestoreTicket ticket, OpenFontReader openFontReader, Stream fontStream) + { + if (typeface.IsTrimmed()) + { + openFontReader.Read(typeface, ticket, fontStream); + } + } + public static void RestoreUp(this Typeface typeface, RestoreTicket ticket, Stream fontStream) + { + //use default opent font reader + RestoreUp(typeface, ticket, new OpenFontReader(), fontStream); + } + } +} + + +namespace Typography.OpenFont +{ + using Typography.OpenFont.Tables; + using Typography.OpenFont.Trimmable; + + //------------------------- + //This is our extension***, + //NOT in OpenFont spec + //------------------------- + //user can reload a new clone of glyphs with fewer detail + //or restore a new clone of glyphs with full detail + + partial class Typeface + { + internal TrimMode _typefaceTrimMode; + + + internal RestoreTicket TrimDownAndRemoveGlyphBuildingDetail() + { + switch (_typefaceTrimMode) + { + default: throw new OpenFontNotSupportedException(); + case TrimMode.EssentailLayoutInfo: return null;//same mode + case TrimMode.Restored: + case TrimMode.No: + { + RestoreTicket ticket = new RestoreTicket(); + ticket.TypefaceName = Name; + ticket.Headers = _tblHeaders; //a copy + + //FROM:GlyphLoadingMode.Full => TO: GlyphLoadingMode.EssentailLayoutInfo + + ticket.HasTtf = _hasTtfOutline; + + //cache glyph name before unload + if (_cff1FontSet != null) + { + ticket.HasCff = true; + UpdateCff1FontSetNamesCache();//*** + _cff1FontSet = null; + } + + //1.Ttf and Otf => clone each glyphs in NO building + Glyph[] newClones = new Glyph[_glyphs.Length]; + for (int i = 0; i < newClones.Length; ++i) + { + newClones[i] = Glyph.Clone_NO_BuildingInstructions(_glyphs[i]); + } + _glyphs = newClones; + + //and since glyph has no building instructions in this mode + //so ... + + ticket.ControlValues = ControlValues != null; + ControlValues = null; + + ticket.PrepProgramBuffer = PrepProgramBuffer != null; + PrepProgramBuffer = null; + + ticket.FpgmProgramBuffer = FpgmProgramBuffer != null; + FpgmProgramBuffer = null; + + ticket.CPALTable = CPALTable != null; + CPALTable = null; + + ticket.COLRTable = COLRTable != null; + COLRTable = null; + + ticket.GaspTable = GaspTable != null; + GaspTable = null; + + // + //3. Svg=> remove SvgTable + if (_svgTable != null) + { + ticket.HasSvg = true; + _svgTable.UnloadSvgData(); + _svgTable = null; + } + + //4. Bitmap Font => remove embeded bitmap data + if (_bitmapFontGlyphSource != null) + { + ticket.HasBitmapSource = true; + _bitmapFontGlyphSource.UnloadCBDT(); + } + + + _typefaceTrimMode = TrimMode.EssentailLayoutInfo; + + return ticket; + } + } + } + + + internal bool CompareOriginalHeadersWithNewlyLoadOne(TableHeader[] others) + { + if (_tblHeaders != null && others != null && + _tblHeaders.Length == others.Length) + { + for (int i = 0; i < _tblHeaders.Length; ++i) + { + TableHeader a = _tblHeaders[i]; + TableHeader b = others[i]; + + if (a.Tag != b.Tag || + a.Offset != b.Offset || + a.Length != b.Length || + a.CheckSum != b.CheckSum) + { +#if DEBUG + System.Diagnostics.Debugger.Break(); +#endif + + return false; + } + } + //pass all + return true; + } + return false; + } + } + +} + + diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/Woff2Reader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/Woff2Reader.cs new file mode 100644 index 00000000..38315f17 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/Woff2Reader.cs @@ -0,0 +1,1928 @@ +//MIT, 2019-present, WinterDev +using System.IO; +using System.Collections.Generic; + +using Typography.OpenFont.Tables; +using Typography.OpenFont.Trimmable; + +//see https://www.w3.org/TR/WOFF2/ + +namespace Typography.OpenFont.WebFont +{ + //NOTE: Web Font file structure is not part of 'Open Font Format'. + + class Woff2Header + { + //WOFF2 Header + //UInt32 signature 0x774F4632 'wOF2' + //UInt32 flavor The "sfnt version" of the input font. + //UInt32 length Total size of the WOFF file. + //UInt16 numTables Number of entries in directory of font tables. + //UInt16 reserved Reserved; set to 0. + //UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header, + // directory, and font tables(including padding). + //UInt32 totalCompressedSize Total length of the compressed data block. + //UInt16 majorVersion Major version of the WOFF file. + //UInt16 minorVersion Minor version of the WOFF file. + //UInt32 metaOffset Offset to metadata block, from beginning of WOFF file. + //UInt32 metaLength Length of compressed metadata block. + //UInt32 metaOrigLength Uncompressed size of metadata block. + //UInt32 privOffset Offset to private data block, from beginning of WOFF file. + //UInt32 privLength Length of private data block. + + public uint flavor; + public uint length; + public uint numTables; + + //public ushort reserved; + public uint totalSfntSize; + + public uint totalCompressedSize; //*** + public ushort majorVersion; + public ushort minorVersion; + public uint metaOffset; + public uint metaLength; + public uint metaOriginalLength; + public uint privOffset; + public uint privLength; + } + + class Woff2TableDirectory + { + //TableDirectoryEntry + //UInt8 flags table type and flags + //UInt32 tag 4-byte tag(optional) + //UIntBase128 origLength length of original table + //UIntBase128 transformLength transformed length(if applicable) + + public uint origLength; + public uint transformLength; + + //translated values + public string Name { get; set; } //translate from tag + + public byte PreprocessingTransformation { get; set; } + public long ExpectedStartAt { get; set; } +#if DEBUG + + public override string ToString() + { + return Name + " " + PreprocessingTransformation; + } + +#endif + } + + public delegate bool BrotliDecompressStreamFunc(byte[] compressedInput, Stream decompressStream); + + public static class Woff2DefaultBrotliDecompressFunc + { + public static BrotliDecompressStreamFunc DecompressHandler; + } + + class TransformedGlyf : UnreadTableEntry + { + private static TripleEncodingTable s_encTable = TripleEncodingTable.GetEncTable(); + + public TransformedGlyf(TableHeader header, Woff2TableDirectory tableDir) : base(header) + { + HasCustomContentReader = true; + TableDir = tableDir; + } + + public Woff2TableDirectory TableDir { get; } + + public override T CreateTableEntry(BinaryReader reader, T expectedResult) + { + if (!(expectedResult is Glyf glyfTable)) throw new System.NotSupportedException(); + + ReconstructGlyfTable(reader, TableDir, glyfTable); + + return expectedResult; + } + + struct TempGlyph + { + public readonly ushort glyphIndex; + public readonly short numContour; + + public ushort instructionLen; + public bool compositeHasInstructions; + + public TempGlyph(ushort glyphIndex, short contourCount) + { + this.glyphIndex = glyphIndex; + this.numContour = contourCount; + + instructionLen = 0; + compositeHasInstructions = false; + } + +#if DEBUG + + public override string ToString() + { + return glyphIndex + " " + numContour; + } + +#endif + } + + static void ReconstructGlyfTable(BinaryReader reader, Woff2TableDirectory woff2TableDir, Glyf glyfTable) + { + //fill the information to glyfTable + //reader.BaseStream.Position += woff2TableDir.transformLength; + //For greater compression effectiveness, + //the glyf table is split into several substreams, to group like data together. + + //The transformed table consists of a number of fields specifying the size of each of the substreams, + //followed by the substreams in sequence. + + //During the decoding process the reverse transformation takes place, + //where data from various separate substreams are recombined to create a complete glyph record + //for each entry of the original glyf table. + + //Transformed glyf Table + //Data-Type Semantic Description and value type(if applicable) + //Fixed version = 0x00000000 + //UInt16 numGlyphs Number of glyphs + //UInt16 indexFormatOffset format for loca table, + // should be consistent with indexToLocFormat of + // the original head table(see[OFF] specification) + + //UInt32 nContourStreamSize Size of nContour stream in bytes + //UInt32 nPointsStreamSize Size of nPoints stream in bytes + //UInt32 flagStreamSize Size of flag stream in bytes + //UInt32 glyphStreamSize Size of glyph stream in bytes(a stream of variable-length encoded values, see description below) + //UInt32 compositeStreamSize Size of composite stream in bytes(a stream of variable-length encoded values, see description below) + //UInt32 bboxStreamSize Size of bbox data in bytes representing combined length of bboxBitmap(a packed bit array) and bboxStream(a stream of Int16 values) + //UInt32 instructionStreamSize Size of instruction stream(a stream of UInt8 values) + + //Int16 nContourStream[] Stream of Int16 values representing number of contours for each glyph record + //255UInt16 nPointsStream[] Stream of values representing number of outline points for each contour in glyph records + //UInt8 flagStream[] Stream of UInt8 values representing flag values for each outline point. + //Vary glyphStream[] Stream of bytes representing point coordinate values using variable length encoding format(defined in subclause 5.2) + //Vary compositeStream[] Stream of bytes representing component flag values and associated composite glyph data + //UInt8 bboxBitmap[] Bitmap(a numGlyphs-long bit array) indicating explicit bounding boxes + //Int16 bboxStream[] Stream of Int16 values representing glyph bounding box data + //UInt8 instructionStream[] Stream of UInt8 values representing a set of instructions for each corresponding glyph + + reader.BaseStream.Position = woff2TableDir.ExpectedStartAt; + + long start = reader.BaseStream.Position; + + uint version = reader.ReadUInt32(); + ushort numGlyphs = reader.ReadUInt16(); + ushort indexFormatOffset = reader.ReadUInt16(); + + uint nContourStreamSize = reader.ReadUInt32(); //in bytes + uint nPointsStreamSize = reader.ReadUInt32(); //in bytes + uint flagStreamSize = reader.ReadUInt32(); //in bytes + uint glyphStreamSize = reader.ReadUInt32(); //in bytes + uint compositeStreamSize = reader.ReadUInt32(); //in bytes + uint bboxStreamSize = reader.ReadUInt32(); //in bytes + uint instructionStreamSize = reader.ReadUInt32(); //in bytes + + long expected_nCountStartAt = reader.BaseStream.Position; + long expected_nPointStartAt = expected_nCountStartAt + nContourStreamSize; + long expected_FlagStreamStartAt = expected_nPointStartAt + nPointsStreamSize; + long expected_GlyphStreamStartAt = expected_FlagStreamStartAt + flagStreamSize; + long expected_CompositeStreamStartAt = expected_GlyphStreamStartAt + glyphStreamSize; + + long expected_BboxStreamStartAt = expected_CompositeStreamStartAt + compositeStreamSize; + long expected_InstructionStreamStartAt = expected_BboxStreamStartAt + bboxStreamSize; + long expected_EndAt = expected_InstructionStreamStartAt + instructionStreamSize; + + //--------------------------------------------- + Glyph[] glyphs = new Glyph[numGlyphs]; + TempGlyph[] allGlyphs = new TempGlyph[numGlyphs]; + List compositeGlyphs = new List(); + int contourCount = 0; + for (ushort i = 0; i < numGlyphs; ++i) + { + short numContour = reader.ReadInt16(); + allGlyphs[i] = new TempGlyph(i, numContour); + if (numContour > 0) + { + contourCount += numContour; + //>0 => simple glyph + //-1 = compound + //0 = empty glyph + } + else if (numContour < 0) + { + //composite glyph, resolve later + compositeGlyphs.Add(i); + } + else + { + } + } + + //-------------------------------------------------------------------------------------------- + //glyphStream + //5.2.Decoding of variable-length X and Y coordinates + + //Simple glyph data structure defines all contours that comprise a glyph outline, + //which are presented by a sequence of on- and off-curve coordinate points. + + //These point coordinates are encoded as delta values representing the incremental values + //between the previous and current corresponding X and Y coordinates of a point, + //the first point of each outline is relative to (0, 0) point. + + //To minimize the size of the dataset of point coordinate values, + //each point is presented as a (flag, xCoordinate, yCoordinate) triplet. + + //The flag value is stored in a separate data stream + //and the coordinate values are stored as part of the glyph data stream using a variable-length encoding format + //consuming a total of 2 - 5 bytes per point. + + //Decoding of Simple Glyphs: + + //For a simple glyph(when nContour > 0), the process continues as follows: + // 1) Read numberOfContours 255UInt16 values from the nPoints stream. + // Each of these is the number of points of that contour. + // Convert this into the endPtsOfContours[] array by computing the cumulative sum, then subtracting one. + // For example, if the values in the stream are[2, 4], then the endPtsOfContours array is [1, 5].Also, + // the sum of all the values in the array is the total number of points in the glyph, nPoints. + // In the example given, the value of nPoints is 6. + + // 2) Read nPoints UInt8 values from the flags stream.Each corresponds to one point in the reconstructed glyph outline. + // The interpretation of the flag byte is described in details in subclause 5.2. + + // 3) For each point(i.e.nPoints times), read a number of point coordinate bytes from the glyph stream. + // The number of point coordinate bytes is a function of the flag byte read in the previous step: + // for (flag < 0x7f) in the range 0 to 83 inclusive, it is one byte. + // In the range 84 to 119 inclusive, it is two bytes. + // In the range 120 to 123 inclusive, it is three bytes, + // and in the range 124 to 127 inclusive, it is four bytes. + // Decode these bytes according to the procedure specified in the subclause 5.2 to reconstruct delta-x and delta-y values of the glyph point coordinates. + // Store these delta-x and delta-y values in the reconstructed glyph using the standard TrueType glyph encoding[OFF] subclause 5.3.3. + + // 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes. + // 5) Read instructionLength bytes from instructionStream, and store these in the reconstituted glyph as instructions. + //-------- +#if DEBUG + if (reader.BaseStream.Position != expected_nPointStartAt) + { + System.Diagnostics.Debug.WriteLine("ERR!!"); + } +#endif + // + //1) nPoints stream, npoint for each contour + + ushort[] pntPerContours = new ushort[contourCount]; + for (int i = 0; i < contourCount; ++i) + { + // Each of these is the number of points of that contour. + pntPerContours[i] = Woff2Utils.Read255UInt16(reader); + } +#if DEBUG + if (reader.BaseStream.Position != expected_FlagStreamStartAt) + { + System.Diagnostics.Debug.WriteLine("ERR!!"); + } +#endif + //2) flagStream, flags value for each point + //each byte in flags stream represents one point + byte[] flagStream = reader.ReadBytes((int)flagStreamSize); + +#if DEBUG + if (reader.BaseStream.Position != expected_GlyphStreamStartAt) + { + System.Diagnostics.Debug.WriteLine("ERR!!"); + } +#endif + + //*** + //some composite glyphs have instructions=> so we must check all composite glyphs + //before read the glyph stream + //** + using (MemoryStream compositeMS = new MemoryStream()) + { + reader.BaseStream.Position = expected_CompositeStreamStartAt; + compositeMS.Write(reader.ReadBytes((int)compositeStreamSize), 0, (int)compositeStreamSize); + compositeMS.Position = 0; + + int j = compositeGlyphs.Count; + ByteOrderSwappingBinaryReader compositeReader = new ByteOrderSwappingBinaryReader(compositeMS); + for (ushort i = 0; i < j; ++i) + { + ushort compositeGlyphIndex = compositeGlyphs[i]; + allGlyphs[compositeGlyphIndex].compositeHasInstructions = CompositeHasInstructions(compositeReader, compositeGlyphIndex); + } + reader.BaseStream.Position = expected_GlyphStreamStartAt; + } + //-------- + int curFlagsIndex = 0; + int pntContourIndex = 0; + for (int i = 0; i < allGlyphs.Length; ++i) + { + glyphs[i] = BuildSimpleGlyphStructure(reader, + ref allGlyphs[i], + glyfTable._emptyGlyph, + pntPerContours, ref pntContourIndex, + flagStream, ref curFlagsIndex); + } + +#if DEBUG + if (pntContourIndex != pntPerContours.Length) + { + } + if (curFlagsIndex != flagStream.Length) + { + } +#endif + //-------------------------------------------------------------------------------------------- + //compositeStream + //-------------------------------------------------------------------------------------------- +#if DEBUG + if (expected_CompositeStreamStartAt != reader.BaseStream.Position) + { + //*** + + reader.BaseStream.Position = expected_CompositeStreamStartAt; + } +#endif + { + //now we read the composite stream again + //and create composite glyphs + int j = compositeGlyphs.Count; + for (ushort i = 0; i < j; ++i) + { + int compositeGlyphIndex = compositeGlyphs[i]; + glyphs[compositeGlyphIndex] = ReadCompositeGlyph(glyphs, reader, i, glyfTable._emptyGlyph); + } + } + + //-------------------------------------------------------------------------------------------- + //bbox stream + //-------------------------------------------------------------------------------------------- + + //Finally, for both simple and composite glyphs, + //if the corresponding bit in the bounding box bit vector is set, + //then additionally read 4 Int16 values from the bbox stream, + //representing xMin, yMin, xMax, and yMax, respectively, + //and record these into the corresponding fields of the reconstructed glyph. + //For simple glyphs, if the corresponding bit in the bounding box bit vector is not set, + //then derive the bounding box by computing the minimum and maximum x and y coordinates in the outline, and storing that. + + //A composite glyph MUST have an explicitly supplied bounding box. + //The motivation is that computing bounding boxes is more complicated, + //and would require resolving references to component glyphs taking into account composite glyph instructions and + //the specified scales of individual components, which would conflict with a purely streaming implementation of font decoding. + + //A decoder MUST check for presence of the bounding box info as part of the composite glyph record + //and MUST NOT load a font file with the composite bounding box data missing. +#if DEBUG + if (expected_BboxStreamStartAt != reader.BaseStream.Position) + { + } +#endif + int bitmapCount = (numGlyphs + 7) / 8; + byte[] bboxBitmap = ExpandBitmap(reader.ReadBytes(bitmapCount)); + for (ushort i = 0; i < numGlyphs; ++i) + { + TempGlyph tempGlyph = allGlyphs[i]; + Glyph glyph = glyphs[i]; + + byte hasBbox = bboxBitmap[i]; + if (hasBbox == 1) + { + //read bbox from the bboxstream + glyph.Bounds = new Bounds( + reader.ReadInt16(), + reader.ReadInt16(), + reader.ReadInt16(), + reader.ReadInt16()); + } + else + { + //no bbox + // + if (tempGlyph.numContour < 0) + { + //composite must have bbox + //if not=> err + throw new System.NotSupportedException(); + } + else if (tempGlyph.numContour > 0) + { + //simple glyph + //use simple calculation + //...For simple glyphs, if the corresponding bit in the bounding box bit vector is not set, + //then derive the bounding box by computing the minimum and maximum x and y coordinates in the outline, and storing that. + glyph.Bounds = FindSimpleGlyphBounds(glyph); + } + } + } + //-------------------------------------------------------------------------------------------- + //instruction stream +#if DEBUG + if (reader.BaseStream.Position < expected_InstructionStreamStartAt) + { + } + else if (expected_InstructionStreamStartAt == reader.BaseStream.Position) + { + } + else + { + } +#endif + + reader.BaseStream.Position = expected_InstructionStreamStartAt; + //-------------------------------------------------------------------------------------------- + + for (ushort i = 0; i < numGlyphs; ++i) + { + TempGlyph tempGlyph = allGlyphs[i]; + if (tempGlyph.instructionLen > 0) + { + glyphs[i].GlyphInstructions = reader.ReadBytes(tempGlyph.instructionLen); + } + } + +#if DEBUG + if (reader.BaseStream.Position != expected_EndAt) + { + } +#endif + + glyfTable.Glyphs = glyphs; + } + + static Bounds FindSimpleGlyphBounds(Glyph glyph) + { + GlyphPointF[] glyphPoints = glyph.GlyphPoints; + + int j = glyphPoints.Length; + float xmin = float.MaxValue; + float ymin = float.MaxValue; + float xmax = float.MinValue; + float ymax = float.MinValue; + + for (int i = 0; i < j; ++i) + { + GlyphPointF p = glyphPoints[i]; + if (p.X < xmin) xmin = p.X; + if (p.X > xmax) xmax = p.X; + if (p.Y < ymin) ymin = p.Y; + if (p.Y > ymax) ymax = p.Y; + } + + return new Bounds( + (short)System.Math.Round(xmin), + (short)System.Math.Round(ymin), + (short)System.Math.Round(xmax), + (short)System.Math.Round(ymax)); + } + + static byte[] ExpandBitmap(byte[] orgBBoxBitmap) + { + byte[] expandArr = new byte[orgBBoxBitmap.Length * 8]; + + int index = 0; + for (int i = 0; i < orgBBoxBitmap.Length; ++i) + { + byte b = orgBBoxBitmap[i]; + expandArr[index++] = (byte)((b >> 7) & 0x1); + expandArr[index++] = (byte)((b >> 6) & 0x1); + expandArr[index++] = (byte)((b >> 5) & 0x1); + expandArr[index++] = (byte)((b >> 4) & 0x1); + expandArr[index++] = (byte)((b >> 3) & 0x1); + expandArr[index++] = (byte)((b >> 2) & 0x1); + expandArr[index++] = (byte)((b >> 1) & 0x1); + expandArr[index++] = (byte)((b >> 0) & 0x1); + } + return expandArr; + } + + static Glyph BuildSimpleGlyphStructure(BinaryReader glyphStreamReader, + ref TempGlyph tmpGlyph, + Glyph emptyGlyph, + ushort[] pntPerContours, ref int pntContourIndex, + byte[] flagStream, ref int flagStreamIndex) + { + //reading from glyphstream*** + //Building a SimpleGlyph + // 1) Read numberOfContours 255UInt16 values from the nPoints stream. + // Each of these is the number of points of that contour. + // Convert this into the endPtsOfContours[] array by computing the cumulative sum, then subtracting one. + // For example, if the values in the stream are[2, 4], then the endPtsOfContours array is [1, 5].Also, + // the sum of all the values in the array is the total number of points in the glyph, nPoints. + // In the example given, the value of nPoints is 6. + + // 2) Read nPoints UInt8 values from the flags stream.Each corresponds to one point in the reconstructed glyph outline. + // The interpretation of the flag byte is described in details in subclause 5.2. + + // 3) For each point(i.e.nPoints times), read a number of point coordinate bytes from the glyph stream. + // The number of point coordinate bytes is a function of the flag byte read in the previous step: + // for (flag < 0x7f) + // in the range 0 to 83 inclusive, it is one byte. + // In the range 84 to 119 inclusive, it is two bytes. + // In the range 120 to 123 inclusive, it is three bytes, + // and in the range 124 to 127 inclusive, it is four bytes. + // Decode these bytes according to the procedure specified in the subclause 5.2 to reconstruct delta-x and delta-y values of the glyph point coordinates. + // Store these delta-x and delta-y values in the reconstructed glyph using the standard TrueType glyph encoding[OFF] subclause 5.3.3. + + // 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes. + // 5) Read instructionLength bytes from instructionStream, and store these in the reconstituted glyph as instructions. + + if (tmpGlyph.numContour == 0) return emptyGlyph; + if (tmpGlyph.numContour < 0) + { + //composite glyph, + //check if this has instruction or not + if (tmpGlyph.compositeHasInstructions) + { + tmpGlyph.instructionLen = Woff2Utils.Read255UInt16(glyphStreamReader); + } + return null;//skip composite glyph (resolve later) + } + + //----- + int curX = 0; + int curY = 0; + + int numContour = tmpGlyph.numContour; + + var _endContours = new ushort[numContour]; + ushort pointCount = 0; + + //create contours + for (ushort i = 0; i < numContour; ++i) + { + ushort numPoint = pntPerContours[pntContourIndex++];//increament pntContourIndex AFTER + pointCount += numPoint; + _endContours[i] = (ushort)(pointCount - 1); + } + + //collect point for our contours + var _glyphPoints = new GlyphPointF[pointCount]; + int n = 0; + for (int i = 0; i < numContour; ++i) + { + //read point detail + //step 3) + + //foreach contour + //read 1 byte flags for each contour + + //1) The most significant bit of a flag indicates whether the point is on- or off-curve point, + //2) the remaining seven bits of the flag determine the format of X and Y coordinate values and + //specify 128 possible combinations of indices that have been assigned taking into consideration + //typical statistical distribution of data found in TrueType fonts. + + //When X and Y coordinate values are recorded using nibbles(either 4 bits per coordinate or 12 bits per coordinate) + //the bits are packed in the byte stream with most significant bit of X coordinate first, + //followed by the value for Y coordinate (most significant bit first). + //As a result, the size of the glyph dataset is significantly reduced, + //and the grouping of the similar values(flags, coordinates) in separate and contiguous data streams allows + //more efficient application of the entropy coding applied as the second stage of encoding process. + + int endContour = _endContours[i]; + for (; n <= endContour; ++n) + { + byte f = flagStream[flagStreamIndex++]; //increment the flagStreamIndex AFTER read + + //int f1 = (f >> 7); // most significant 1 bit -> on/off curve + + int xyFormat = f & 0x7F; // remainging 7 bits x,y format + + TripleEncodingRecord enc = s_encTable[xyFormat]; //0-128 + + byte[] packedXY = glyphStreamReader.ReadBytes(enc.ByteCount - 1); //byte count include 1 byte flags, so actual read=> byteCount-1 + //read x and y + + int x = 0; + int y = 0; + + switch (enc.XBits) + { + default: + throw new System.NotSupportedException();//??? + case 0: //0,8, + x = 0; + y = enc.Ty(packedXY[0]); + break; + + case 4: //4,4 + x = enc.Tx(packedXY[0] >> 4); + y = enc.Ty(packedXY[0] & 0xF); + break; + + case 8: //8,0 or 8,8 + x = enc.Tx(packedXY[0]); + y = (enc.YBits == 8) ? + enc.Ty(packedXY[1]) : + 0; + break; + + case 12: //12,12 + //x = enc.Tx((packedXY[0] << 8) | (packedXY[1] >> 4)); + //y = enc.Ty(((packedXY[1] & 0xF)) | (packedXY[2] >> 4)); + x = enc.Tx((packedXY[0] << 4) | (packedXY[1] >> 4)); + y = enc.Ty(((packedXY[1] & 0xF) << 8) | (packedXY[2])); + break; + + case 16: //16,16 + x = enc.Tx((packedXY[0] << 8) | packedXY[1]); + y = enc.Ty((packedXY[2] << 8) | packedXY[3]); + break; + } + + //incremental point format*** + _glyphPoints[n] = new GlyphPointF(curX += x, curY += y, (f >> 7) == 0); // most significant 1 bit -> on/off curve + } + } + + //---- + //step 4) Read one 255UInt16 value from the glyph stream, which is instructionLength, the number of instruction bytes. + tmpGlyph.instructionLen = Woff2Utils.Read255UInt16(glyphStreamReader); + //step 5) resolve it later + + return new Glyph(_glyphPoints, + _endContours, + new Bounds(), //calculate later + null, //load instruction later + tmpGlyph.glyphIndex); + } + + static bool CompositeHasInstructions(BinaryReader reader, ushort compositeGlyphIndex) + { + //To find if a composite has instruction or not. + + //This method is similar to ReadCompositeGlyph() (below) + //but this dose not create actual composite glyph. + + Glyf.CompositeGlyphFlags flags; + do + { + flags = (Glyf.CompositeGlyphFlags)reader.ReadUInt16(); + ushort glyphIndex = reader.ReadUInt16(); + short arg1 = 0; + short arg2 = 0; + ushort arg1and2 = 0; + + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS)) + { + arg1 = reader.ReadInt16(); + arg2 = reader.ReadInt16(); + } + else + { + arg1and2 = reader.ReadUInt16(); + } + //----------------------------------------- + float xscale = 1; + float scale01 = 0; + float scale10 = 0; + float yscale = 1; + + bool useMatrix = false; + //----------------------------------------- + bool hasScale = false; + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_SCALE)) + { + //If the bit WE_HAVE_A_SCALE is set, + //the scale value is read in 2.14 format-the value can be between -2 to almost +2. + //The glyph will be scaled by this value before grid-fitting. + xscale = yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE)) + { + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO)) + { + //The bit WE_HAVE_A_TWO_BY_TWO allows for linear transformation of the X and Y coordinates by specifying a 2 × 2 matrix. + //This could be used for scaling and 90-degree*** rotations of the glyph components, for example. + + //2x2 matrix + + //The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value. + //For example, an i-circumflex (U+00EF) is often composed of the circumflex and a dotless-i. + //In order to force the composite to have the same metrics as the dotless-i, + //set USE_MY_METRICS for the dotless-i component of the composite. + //Without this bit, the rsb and lsb would be calculated from the hmtx entry for the composite + //(or would need to be explicitly set with TrueType instructions). + + //Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components. + useMatrix = true; + hasScale = true; + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + scale01 = reader.ReadF2Dot14(); /* Format 2.14 */ + scale10 = reader.ReadF2Dot14();/* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + } + } while (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.MORE_COMPONENTS)); + + // + return Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS); + } + + static Glyph ReadCompositeGlyph(Glyph[] createdGlyphs, BinaryReader reader, ushort compositeGlyphIndex, Glyph emptyGlyph) + { + //Decoding of Composite Glyphs + //For a composite glyph(nContour == -1), the following steps take the place of (Building Simple Glyph, steps 1 - 5 above): + + //1a.Read a UInt16 from compositeStream. + // This is interpreted as a component flag word as in the TrueType spec. + // Based on the flag values, there are between 4 and 14 additional argument bytes, + // interpreted as glyph index, arg1, arg2, and optional scale or affine matrix. + + //2a.Read the number of argument bytes as determined in step 2a from the composite stream, + //and store these in the reconstructed glyph. + //If the flag word read in step 2a has the FLAG_MORE_COMPONENTS bit(bit 5) set, go back to step 2a. + + //3a.If any of the flag words had the FLAG_WE_HAVE_INSTRUCTIONS bit(bit 8) set, + //then read the instructions from the glyph and store them in the reconstructed glyph, + //using the same process as described in steps 4 and 5 above (see Building Simple Glyph). + + Glyph finalGlyph = null; + Glyf.CompositeGlyphFlags flags; + do + { + flags = (Glyf.CompositeGlyphFlags)reader.ReadUInt16(); + ushort glyphIndex = reader.ReadUInt16(); + if (createdGlyphs[glyphIndex] == null) + { + // This glyph is not read yet, resolve it first! + long storedOffset = reader.BaseStream.Position; + Glyph missingGlyph = ReadCompositeGlyph(createdGlyphs, reader, glyphIndex, emptyGlyph); + createdGlyphs[glyphIndex] = missingGlyph; + reader.BaseStream.Position = storedOffset; + } + + Glyph newGlyph = Glyph.TtfOutlineGlyphClone(createdGlyphs[glyphIndex], compositeGlyphIndex); + + short arg1 = 0; + short arg2 = 0; + ushort arg1and2 = 0; + + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS)) + { + arg1 = reader.ReadInt16(); + arg2 = reader.ReadInt16(); + } + else + { + arg1and2 = reader.ReadUInt16(); + } + //----------------------------------------- + float xscale = 1; + float scale01 = 0; + float scale10 = 0; + float yscale = 1; + + bool useMatrix = false; + //----------------------------------------- + bool hasScale = false; + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_SCALE)) + { + //If the bit WE_HAVE_A_SCALE is set, + //the scale value is read in 2.14 format-the value can be between -2 to almost +2. + //The glyph will be scaled by this value before grid-fitting. + xscale = yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE)) + { + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + hasScale = true; + } + else if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO)) + { + //The bit WE_HAVE_A_TWO_BY_TWO allows for linear transformation of the X and Y coordinates by specifying a 2 × 2 matrix. + //This could be used for scaling and 90-degree*** rotations of the glyph components, for example. + + //2x2 matrix + + //The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value. + //For example, an i-circumflex (U+00EF) is often composed of the circumflex and a dotless-i. + //In order to force the composite to have the same metrics as the dotless-i, + //set USE_MY_METRICS for the dotless-i component of the composite. + //Without this bit, the rsb and lsb would be calculated from the hmtx entry for the composite + //(or would need to be explicitly set with TrueType instructions). + + //Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components. + useMatrix = true; + hasScale = true; + xscale = reader.ReadF2Dot14(); /* Format 2.14 */ + scale01 = reader.ReadF2Dot14(); /* Format 2.14 */ + scale10 = reader.ReadF2Dot14(); /* Format 2.14 */ + yscale = reader.ReadF2Dot14(); /* Format 2.14 */ + + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.UNSCALED_COMPONENT_OFFSET)) + { + } + else + { + } + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.USE_MY_METRICS)) + { + } + } + + //-------------------------------------------------------------------- + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ARGS_ARE_XY_VALUES)) + { + //Argument1 and argument2 can be either x and y offsets to be added to the glyph or two point numbers. + //x and y offsets to be added to the glyph + //When arguments 1 and 2 are an x and a y offset instead of points and the bit ROUND_XY_TO_GRID is set to 1, + //the values are rounded to those of the closest grid lines before they are added to the glyph. + //X and Y offsets are described in FUnits. + + if (useMatrix) + { + //use this matrix + Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, scale01, scale10, yscale); + Glyph.TtfOffsetXY(newGlyph, arg1, arg2); + } + else + { + if (hasScale) + { + if (xscale == 1.0 && yscale == 1.0) + { + } + else + { + Glyph.TtfTransformWith2x2Matrix(newGlyph, xscale, 0, 0, yscale); + } + Glyph.TtfOffsetXY(newGlyph, arg1, arg2); + } + else + { + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.ROUND_XY_TO_GRID)) + { + //TODO: implement round xy to grid*** + //---------------------------- + } + //just offset*** + Glyph.TtfOffsetXY(newGlyph, arg1, arg2); + } + } + } + else + { + //two point numbers. + //the first point number indicates the point that is to be matched to the new glyph. + //The second number indicates the new glyph's “matched” point. + //Once a glyph is added,its point numbers begin directly after the last glyphs (endpoint of first glyph + 1) + } + + // + if (finalGlyph == null) + { + finalGlyph = newGlyph; + } + else + { + //merge + Glyph.TtfAppendGlyph(finalGlyph, newGlyph); + } + } while (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.MORE_COMPONENTS)); + + // + if (Glyf.HasFlag(flags, Glyf.CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS)) + { + //read this later + //ushort numInstr = reader.ReadUInt16(); + //byte[] insts = reader.ReadBytes(numInstr); + //finalGlyph.GlyphInstructions = insts; + } + + return finalGlyph ?? emptyGlyph; + } + + readonly struct TripleEncodingRecord + { + public readonly byte ByteCount; + public readonly byte XBits; + public readonly byte YBits; + public readonly ushort DeltaX; + public readonly ushort DeltaY; + public readonly sbyte Xsign; + public readonly sbyte Ysign; + + public TripleEncodingRecord( + byte byteCount, + byte xbits, byte ybits, + ushort deltaX, ushort deltaY, + sbyte xsign, sbyte ysign) + { + ByteCount = byteCount; + XBits = xbits; + YBits = ybits; + DeltaX = deltaX; + DeltaY = deltaY; + Xsign = xsign; + Ysign = ysign; + //#if DEBUG + // debugIndex = -1; + //#endif + } + +#if DEBUG + + //public int debugIndex; + public override string ToString() + { + return ByteCount + " " + XBits + " " + YBits + " " + DeltaX + " " + DeltaY + " " + Xsign + " " + Ysign; + } + +#endif + + /// + /// translate X + /// + /// + /// + public int Tx(int orgX) => (orgX + DeltaX) * Xsign; + + /// + /// translate Y + /// + /// + /// + public int Ty(int orgY) => (orgY + DeltaY) * Ysign; + } + + class TripleEncodingTable + { + private static TripleEncodingTable s_encTable; + + private List _records = new List(); + + public static TripleEncodingTable GetEncTable() + { + if (s_encTable == null) + { + s_encTable = new TripleEncodingTable(); + } + return s_encTable; + } + + private TripleEncodingTable() + { + BuildTable(); + +#if DEBUG + if (_records.Count != 128) + { + throw new System.Exception(); + } + dbugValidateTable(); +#endif + } + +#if DEBUG + + void dbugValidateTable() + { +#if DEBUG + for (int xyFormat = 0; xyFormat < 128; ++xyFormat) + { + TripleEncodingRecord tripleRec = _records[xyFormat]; + if (xyFormat < 84) + { + //0-83 inclusive + if ((tripleRec.ByteCount - 1) != 1) + { + throw new System.NotSupportedException(); + } + } + else if (xyFormat < 120) + { + //84-119 inclusive + if ((tripleRec.ByteCount - 1) != 2) + { + throw new System.NotSupportedException(); + } + } + else if (xyFormat < 124) + { + //120-123 inclusive + if ((tripleRec.ByteCount - 1) != 3) + { + throw new System.NotSupportedException(); + } + } + else if (xyFormat < 128) + { + //124-127 inclusive + if ((tripleRec.ByteCount - 1) != 4) + { + throw new System.NotSupportedException(); + } + } + } + +#endif + } + +#endif + public TripleEncodingRecord this[int index] => _records[index]; + + void BuildTable() + { + // Each of the 128 index values define the following properties and specified in details in the table below: + + // Byte count(total number of bytes used for this set of coordinate values including one byte for 'flag' value). + // Number of bits used to represent X coordinate value(X bits). + // Number of bits used to represent Y coordinate value(Y bits). + // An additional incremental amount to be added to X bits value(delta X). + // An additional incremental amount to be added to Y bits value(delta Y). + // The sign of X coordinate value(X sign). + // The sign of Y coordinate value(Y sign). + + //Please note that “Byte Count” field reflects total size of the triplet(flag, xCoordinate, yCoordinate), + //including ‘flag’ value that is encoded in a separate stream. + + //Triplet Encoding + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + + //(set 1.1) + //0 2 0 8 N/A 0 N/A - + //1 0 + + //2 256 - + //3 256 + + //4 512 - + //5 512 + + //6 768 - + //7 768 + + //8 1024 - + //9 1024 + + BuildRecords(2, 0, 8, null, new ushort[] { 0, 256, 512, 768, 1024 }); //2*5 + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 1.2) + //10 2 8 0 0 N/A - N/A + //11 0 + + //12 256 - + //13 256 + + //14 512 - + //15 512 + + //16 768 - + //17 768 + + //18 1024 - + //19 1024 + + BuildRecords(2, 8, 0, new ushort[] { 0, 256, 512, 768, 1024 }, null); //2*5 + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 2.1) + //20 2 4 4 1 1 - - + //21 1 + - + //22 1 - + + //23 1 + + + //24 17 - - + //25 17 + - + //26 17 - + + //27 17 + + + //28 33 - - + //29 33 + - + //30 33 - + + //31 33 + + + //32 49 - - + //33 49 + - + //34 49 - + + //35 49 + + + BuildRecords(2, 4, 4, new ushort[] { 1 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 2.2) + //36 2 4 4 17 1 - - + //37 1 + - + //38 1 - + + //39 1 + + + //40 17 - - + //41 17 + - + //42 17 - + + //43 17 + + + //44 33 - - + //45 33 + - + //46 33 - + + //47 33 + + + //48 49 - - + //49 49 + - + //50 49 - + + //51 49 + + + BuildRecords(2, 4, 4, new ushort[] { 17 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 2.3) + //52 2 4 4 33 1 - - + //53 1 + - + //54 1 - + + //55 1 + + + //56 17 - - + //57 17 + - + //58 17 - + + //59 17 + + + //60 33 - - + //61 33 + - + //62 33 - + + //63 33 + + + //64 49 - - + //65 49 + - + //66 49 - + + //67 49 + + + BuildRecords(2, 4, 4, new ushort[] { 33 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 2.4) + //68 2 4 4 49 1 - - + //69 1 + - + //70 1 - + + //71 1 + + + //72 17 - - + //73 17 + - + //74 17 - + + //75 17 + + + //76 33 - - + //77 33 + - + //78 33 - + + //79 33 + + + //80 49 - - + //81 49 + - + //82 49 - + + //83 49 + + + BuildRecords(2, 4, 4, new ushort[] { 49 }, new ushort[] { 1, 17, 33, 49 });// 4*4 => 16 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 3.1) + //84 3 8 8 1 1 - - + //85 1 + - + //86 1 - + + //87 1 + + + //88 257 - - + //89 257 + - + //90 257 - + + //91 257 + + + //92 513 - - + //93 513 + - + //94 513 - + + //95 513 + + + BuildRecords(3, 8, 8, new ushort[] { 1 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 3.2) + //96 3 8 8 257 1 - - + //97 1 + - + //98 1 - + + //99 1 + + + //100 257 - - + //101 257 + - + //102 257 - + + //103 257 + + + //104 513 - - + //105 513 + - + //106 513 - + + //107 513 + + + BuildRecords(3, 8, 8, new ushort[] { 257 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 3.3) + //108 3 8 8 513 1 - - + //109 1 + - + //110 1 - + + //111 1 + + + //112 257 - - + //113 257 + - + //114 257 - + + //115 257 + + + //116 513 - - + //117 513 + - + //118 513 - + + //119 513 + + + BuildRecords(3, 8, 8, new ushort[] { 513 }, new ushort[] { 1, 257, 513 });// 4*3 => 12 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 4) + //120 4 12 12 0 0 - - + //121 + - + //122 - + + //123 + + + BuildRecords(4, 12, 12, new ushort[] { 0 }, new ushort[] { 0 }); // 4*1 => 4 records + + //--------------------------------------------------------------------- + //Index ByteCount Xbits Ybits DeltaX DeltaY Xsign Ysign + //(set 5) + //124 5 16 16 0 0 - - + //125 + - + //126 - + + //127 + + + BuildRecords(5, 16, 16, new ushort[] { 0 }, new ushort[] { 0 });// 4*1 => 4 records + } + + void AddRecord(byte byteCount, byte xbits, byte ybits, ushort deltaX, ushort deltaY, sbyte xsign, sbyte ysign) + { + var rec = new TripleEncodingRecord(byteCount, xbits, ybits, deltaX, deltaY, xsign, ysign); +#if DEBUG + //rec.debugIndex = _records.Count; +#endif + _records.Add(rec); + } + + void BuildRecords(byte byteCount, byte xbits, byte ybits, ushort[] deltaXs, ushort[] deltaYs) + { + if (deltaXs == null) + { + //(set 1.1) + for (int y = 0; y < deltaYs.Length; ++y) + { + AddRecord(byteCount, xbits, ybits, 0, deltaYs[y], 0, -1); + AddRecord(byteCount, xbits, ybits, 0, deltaYs[y], 0, 1); + } + } + else if (deltaYs == null) + { + //(set 1.2) + for (int x = 0; x < deltaXs.Length; ++x) + { + AddRecord(byteCount, xbits, ybits, deltaXs[x], 0, -1, 0); + AddRecord(byteCount, xbits, ybits, deltaXs[x], 0, 1, 0); + } + } + else + { + //set 2.1, - set5 + for (int x = 0; x < deltaXs.Length; ++x) + { + ushort deltaX = deltaXs[x]; + + for (int y = 0; y < deltaYs.Length; ++y) + { + ushort deltaY = deltaYs[y]; + + AddRecord(byteCount, xbits, ybits, deltaX, deltaY, -1, -1); + AddRecord(byteCount, xbits, ybits, deltaX, deltaY, 1, -1); + AddRecord(byteCount, xbits, ybits, deltaX, deltaY, -1, 1); + AddRecord(byteCount, xbits, ybits, deltaX, deltaY, 1, 1); + } + } + } + } + } + } + + class TransformedLoca : UnreadTableEntry + { + public TransformedLoca(TableHeader header, Woff2TableDirectory tableDir) : base(header) + { + HasCustomContentReader = true; + TableDir = tableDir; + } + + public Woff2TableDirectory TableDir { get; } + + public override T CreateTableEntry(BinaryReader reader, T expectedResult) + { + GlyphLocations loca = expectedResult as GlyphLocations; + if (loca == null) throw new System.NotSupportedException(); + + //nothing todo here :) + return expectedResult; + } + } + + class Woff2Reader + { + private Woff2Header _header; + + public BrotliDecompressStreamFunc DecompressHandler; + + public Woff2Reader() + { +#if DEBUG + dbugVerifyKnownTables(); +#endif + } + +#if DEBUG + + private static bool s_dbugPassVeriKnownTables; + + static void dbugVerifyKnownTables() + { + if (s_dbugPassVeriKnownTables) + { + return; + } + //-------------- + Dictionary uniqueNames = new Dictionary(); + foreach (string name in s_knownTableTags) + { + if (!uniqueNames.ContainsKey(name)) + { + uniqueNames.Add(name, true); + } + else + { + throw new System.Exception(); + } + } + } + +#endif + + public PreviewFontInfo ReadPreview(BinaryReader reader) + { + _header = ReadHeader(reader); + if (_header == null) return null; //=> return here and notify user too. + Woff2TableDirectory[] woff2TablDirs = ReadTableDirectories(reader); + if (DecompressHandler == null) + { + //if no Brotli decoder=> return here and notify user too. + if (Woff2DefaultBrotliDecompressFunc.DecompressHandler != null) + { + DecompressHandler = Woff2DefaultBrotliDecompressFunc.DecompressHandler; + } + else + { + //return here and notify user too. + return null; + } + } + + //try read each compressed tables + byte[] compressedBuffer = reader.ReadBytes((int)_header.totalCompressedSize); + if (compressedBuffer.Length != _header.totalCompressedSize) + { + //error! + return null; //can't read this, notify user too. + } + using (MemoryStream decompressedStream = new MemoryStream()) + { + if (!DecompressHandler(compressedBuffer, decompressedStream)) + { + //...Most notably, + //the data for the font tables is compressed in a SINGLE data stream comprising all the font tables. + + //if not pass set to null + //decompressedBuffer = null; + return null; + } + //from decoded stream we read each table + decompressedStream.Position = 0;//reset pos + + using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressedStream)) + { + TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woff2TablDirs); + OpenFontReader openFontReader = new OpenFontReader(); + return openFontReader.ReadPreviewFontInfo(tableEntryCollection, reader2); + } + } + } + + internal bool Read(Typeface typeface, BinaryReader reader, RestoreTicket ticket) + { + _header = ReadHeader(reader); + if (_header == null) { return false; } //=> return here and notify user too. + + Woff2TableDirectory[] woff2TablDirs = ReadTableDirectories(reader); + if (DecompressHandler == null) + { + //if no Brotli decoder=> return here and notify user too. + if (Woff2DefaultBrotliDecompressFunc.DecompressHandler != null) + { + DecompressHandler = Woff2DefaultBrotliDecompressFunc.DecompressHandler; + } + else + { + //return here and notify user too. + return false; + } + } + + byte[] compressedBuffer = reader.ReadBytes((int)_header.totalCompressedSize); + if (compressedBuffer.Length != _header.totalCompressedSize) + { + //error! + return false; //can't read this, notify user too. + } + + using (MemoryStream decompressedStream = new MemoryStream()) + { + if (!DecompressHandler(compressedBuffer, decompressedStream)) + { + //...Most notably, + //the data for the font tables is compressed in a SINGLE data stream comprising all the font tables. + + //if not pass set to null + //decompressedBuffer = null; + return false; + } + //from decoded stream we read each table + decompressedStream.Position = 0;//reset pos + + using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressedStream)) + { + TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woff2TablDirs); + OpenFontReader openFontReader = new OpenFontReader(); + return openFontReader.ReadTableEntryCollection(typeface, ticket, tableEntryCollection, reader2); + } + } + } + + Woff2Header ReadHeader(BinaryReader reader) + { + //WOFF2 Header + //UInt32 signature 0x774F4632 'wOF2' + //UInt32 flavor The "sfnt version" of the input font. + //UInt32 length Total size of the WOFF file. + //UInt16 numTables Number of entries in directory of font tables. + //UInt16 reserved Reserved; set to 0. + //UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header, + // directory, and font tables(including padding). + //UInt32 totalCompressedSize Total length of the compressed data block. + //UInt16 majorVersion Major version of the WOFF file. + //UInt16 minorVersion Minor version of the WOFF file. + //UInt32 metaOffset Offset to metadata block, from beginning of WOFF file. + //UInt32 metaLength Length of compressed metadata block. + //UInt32 metaOrigLength Uncompressed size of metadata block. + //UInt32 privOffset Offset to private data block, from beginning of WOFF file. + //UInt32 privLength Length of private data block. + + Woff2Header header = new Woff2Header(); + byte b0 = reader.ReadByte(); + byte b1 = reader.ReadByte(); + byte b2 = reader.ReadByte(); + byte b3 = reader.ReadByte(); + if (!(b0 == 0x77 && b1 == 0x4f && b2 == 0x46 && b3 == 0x32)) + { + return null; + } + + header.flavor = reader.ReadUInt32BE(); // sfnt version + string flavorName = Utils.TagToString(header.flavor); + + header.length = reader.ReadUInt32BE(); + header.numTables = reader.ReadUInt16BE(); + ushort reserved = reader.ReadUInt16BE(); + header.totalSfntSize = reader.ReadUInt32BE(); + header.totalCompressedSize = reader.ReadUInt32BE(); + + header.majorVersion = reader.ReadUInt16BE(); + header.minorVersion = reader.ReadUInt16BE(); + + header.metaOffset = reader.ReadUInt32BE(); + header.metaLength = reader.ReadUInt32BE(); + header.metaOriginalLength = reader.ReadUInt32BE(); + + header.privOffset = reader.ReadUInt32BE(); + header.privLength = reader.ReadUInt32BE(); + + return header; + } + + Woff2TableDirectory[] ReadTableDirectories(BinaryReader reader) + { + uint tableCount = (uint)_header.numTables; //? + var tableDirs = new Woff2TableDirectory[tableCount]; + + long expectedTableStartAt = 0; + + for (int i = 0; i < tableCount; ++i) + { + //TableDirectoryEntry + //UInt8 flags table type and flags + //UInt32 tag 4-byte tag(optional) + //UIntBase128 origLength length of original table + //UIntBase128 transformLength transformed length(if applicable) + + Woff2TableDirectory table = new Woff2TableDirectory(); + byte flags = reader.ReadByte(); + //The interpretation of the flags field is as follows. + + //Bits[0..5] contain an index to the "known tag" table, + //which represents tags likely to appear in fonts.If the tag is not present in this table, + //then the value of this bit field is 63. + + //interprete flags + int knowTable = flags & 0x1F; //5 bits => known table or not + + table.Name = (knowTable < 63) ? s_knownTableTags[knowTable] : Utils.TagToString(reader.ReadUInt32()); //other tag + + //Bits 6 and 7 indicate the preprocessing transformation version number(0 - 3) that was applied to each table. + + //For all tables in a font, except for 'glyf' and 'loca' tables, + //transformation version 0 indicates the null transform where the original table data is passed directly + //to the Brotli compressor for inclusion in the compressed data stream. + + //For 'glyf' and 'loca' tables, + //transformation version 3 indicates the null transform where the original table data was passed directly + //to the Brotli compressor without applying any pre - processing defined in subclause 5.1 and subclause 5.3. + + //The transformed table formats and their associated transformation version numbers are + //described in details in clause 5 of this specification. + + table.PreprocessingTransformation = (byte)((flags >> 5) & 0x3); //2 bits, preprocessing transformation + + table.ExpectedStartAt = expectedTableStartAt; + // + if (!ReadUIntBase128(reader, out table.origLength)) + { + //can't read 128=> error + } + + switch (table.PreprocessingTransformation) + { + default: + break; + + case 0: + { + if (table.Name == Glyf._N) + { + if (!ReadUIntBase128(reader, out table.transformLength)) + { + //can't read 128=> error + } + expectedTableStartAt += table.transformLength;//*** + } + else if (table.Name == GlyphLocations._N) + { + //BUT by spec, transform 'loca' MUST has transformLength=0 + if (!ReadUIntBase128(reader, out table.transformLength)) + { + //can't read 128=> error + } + expectedTableStartAt += table.transformLength;//*** + } + else + { + expectedTableStartAt += table.origLength; + } + } + break; + + case 1: + { + expectedTableStartAt += table.origLength; + } + break; + + case 2: + { + expectedTableStartAt += table.origLength; + } + break; + + case 3: + { + expectedTableStartAt += table.origLength; + } + break; + } + tableDirs[i] = table; + } + + return tableDirs; + } + + static TableEntryCollection CreateTableEntryCollection(Woff2TableDirectory[] woffTableDirs) + { + TableEntryCollection tableEntryCollection = new TableEntryCollection(); + for (int i = 0; i < woffTableDirs.Length; ++i) + { + Woff2TableDirectory woffTableDir = woffTableDirs[i]; + UnreadTableEntry unreadTableEntry = null; + + if (woffTableDir.Name == Glyf._N && woffTableDir.PreprocessingTransformation == 0) + { + //this is transformed glyf table, + //we need another techqniue + TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0, + (uint)woffTableDir.ExpectedStartAt, + woffTableDir.transformLength); + unreadTableEntry = new TransformedGlyf(tableHeader, woffTableDir); + } + else if (woffTableDir.Name == GlyphLocations._N && woffTableDir.PreprocessingTransformation == 0) + { + //this is transformed glyf table, + //we need another techqniue + TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0, + (uint)woffTableDir.ExpectedStartAt, + woffTableDir.transformLength); + unreadTableEntry = new TransformedLoca(tableHeader, woffTableDir); + } + else + { + TableHeader tableHeader = new TableHeader(woffTableDir.Name, 0, + (uint)woffTableDir.ExpectedStartAt, + woffTableDir.origLength); + unreadTableEntry = new UnreadTableEntry(tableHeader); + } + tableEntryCollection.AddEntry(unreadTableEntry); + } + + return tableEntryCollection; + } + + private static readonly string[] s_knownTableTags = new string[] + { + //Known Table Tags + //Flag Tag Flag Tag Flag Tag Flag Tag + //0 => cmap, 16 =>EBLC, 32 =>CBDT, 48 =>gvar, + //1 => head, 17 =>gasp, 33 =>CBLC, 49 =>hsty, + //2 => hhea, 18 =>hdmx, 34 =>COLR, 50 =>just, + //3 => hmtx, 19 =>kern, 35 =>CPAL, 51 =>lcar, + //4 => maxp, 20 =>LTSH, 36 =>SVG , 52 =>mort, + //5 => name, 21 =>PCLT, 37 =>sbix, 53 =>morx, + //6 => OS/2, 22 =>VDMX, 38 =>acnt, 54 =>opbd, + //7 => post, 23 =>vhea, 39 =>avar, 55 =>prop, + //8 => cvt , 24 =>vmtx, 40 =>bdat, 56 =>trak, + //9 => fpgm, 25 =>BASE, 41 =>bloc, 57 =>Zapf, + //10 => glyf, 26 =>GDEF, 42 =>bsln, 58 =>Silf, + //11 => loca, 27 =>GPOS, 43 =>cvar, 59 =>Glat, + //12 => prep, 28 =>GSUB, 44 =>fdsc, 60 =>Gloc, + //13 => CFF , 29 =>EBSC, 45 =>feat, 61 =>Feat, + //14 => VORG, 30 =>JSTF, 46 =>fmtx, 62 =>Sill, + //15 => EBDT, 31 =>MATH, 47 =>fvar, 63 =>arbitrary tag follows,... + //------------------------------------------------------------------- + + //-- TODO:implement missing table too! + Cmap._N, //0 + Head._N, //1 + HorizontalHeader._N,//2 + HorizontalMetrics._N,//3 + MaxProfile._N,//4 + NameEntry._N,//5 + OS2Table._N, //6 + PostTable._N,//7 + CvtTable._N,//8 + FpgmTable._N,//9 + Glyf._N,//10 + GlyphLocations._N,//11 + PrepTable._N,//12 + CFFTable._N,//13 + "VORG",//14 + EBDT._N,//15, + + //--------------- + EBLC._N,//16 + Gasp._N,//17 + HorizontalDeviceMetrics._N,//18 + Kern._N,//19 + "LTSH",//20 + "PCLT",//21 + VerticalDeviceMetrics._N,//22 + VerticalHeader._N,//23 + VerticalMetrics._N,//24 + BASE._N,//25 + GDEF._N,//26 + GPOS._N,//27 + GSUB._N,//28 + EBSC._N, //29 + "JSTF", //30 + MathTable._N,//31 + //--------------- + + //Known Table Tags (copy,same as above) + //Flag Tag Flag Tag Flag Tag Flag Tag + //0 => cmap, 16 =>EBLC, 32 =>CBDT, 48 =>gvar, + //1 => head, 17 =>gasp, 33 =>CBLC, 49 =>hsty, + //2 => hhea, 18 =>hdmx, 34 =>COLR, 50 =>just, + //3 => hmtx, 19 =>kern, 35 =>CPAL, 51 =>lcar, + //4 => maxp, 20 =>LTSH, 36 =>SVG , 52 =>mort, + //5 => name, 21 =>PCLT, 37 =>sbix, 53 =>morx, + //6 => OS/2, 22 =>VDMX, 38 =>acnt, 54 =>opbd, + //7 => post, 23 =>vhea, 39 =>avar, 55 =>prop, + //8 => cvt , 24 =>vmtx, 40 =>bdat, 56 =>trak, + //9 => fpgm, 25 =>BASE, 41 =>bloc, 57 =>Zapf, + //10 => glyf, 26 =>GDEF, 42 =>bsln, 58 =>Silf, + //11 => loca, 27 =>GPOS, 43 =>cvar, 59 =>Glat, + //12 => prep, 28 =>GSUB, 44 =>fdsc, 60 =>Gloc, + //13 => CFF , 29 =>EBSC, 45 =>feat, 61 =>Feat, + //14 => VORG, 30 =>JSTF, 46 =>fmtx, 62 =>Sill, + //15 => EBDT, 31 =>MATH, 47 =>fvar, 63 =>arbitrary tag follows,... + //------------------------------------------------------------------- + + CBDT._N, //32 + CBLC._N,//33 + COLR._N,//34 + CPAL._N,//35, + SvgTable._N,//36 + "sbix",//37 + "acnt",//38 + "avar",//39 + "bdat",//40 + "bloc",//41 + "bsln",//42 + "cvar",//43 + "fdsc",//44 + "feat",//45 + "fmtx",//46 + "fvar",//47 + //--------------- + + "gvar",//48 + "hsty",//49 + "just",//50 + "lcar",//51 + "mort",//52 + "morx",//53 + "opbd",//54 + "prop",//55 + "trak",//56 + "Zapf",//57 + "Silf",//58 + "Glat",//59 + "Gloc",//60 + "Feat",//61 + "Sill",//62 + "...." //63 arbitrary tag follows + }; + + static bool ReadUIntBase128(BinaryReader reader, out uint result) + { + //UIntBase128 Data Type + + //UIntBase128 is a different variable length encoding of unsigned integers, + //suitable for values up to 2^(32) - 1. + + //A UIntBase128 encoded number is a sequence of bytes for which the most significant bit + //is set for all but the last byte, + //and clear for the last byte. + + //The number itself is base 128 encoded in the lower 7 bits of each byte. + //Thus, a decoding procedure for a UIntBase128 is: + //start with value = 0. + //Consume a byte, setting value = old value times 128 + (byte bitwise - and 127). + //Repeat last step until the most significant bit of byte is false. + + //UIntBase128 encoding format allows a possibility of sub-optimal encoding, + //where e.g.the same numerical value can be represented with variable number of bytes(utilizing leading 'zeros'). + //For example, the value 63 could be encoded as either one byte 0x3F or two(or more) bytes: [0x80, 0x3f]. + //An encoder must not allow this to happen and must produce shortest possible encoding. + //A decoder MUST reject the font file if it encounters a UintBase128 - encoded value with leading zeros(a value that starts with the byte 0x80), + //if UintBase128 - encoded sequence is longer than 5 bytes, + //or if a UintBase128 - encoded value exceeds 232 - 1. + + //The "C-like" pseudo - code describing how to read the UIntBase128 format is presented below: + //bool ReadUIntBase128(data, * result) + // { + // UInt32 accum = 0; + + // for (i = 0; i < 5; i++) + // { + // UInt8 data_byte = data.getNextUInt8(); + + // // No leading 0's + // if (i == 0 && data_byte = 0x80) return false; + + // // If any of top 7 bits are set then << 7 would overflow + // if (accum & 0xFE000000) return false; + + // *accum = (accum << 7) | (data_byte & 0x7F); + + // // Spin until most significant bit of data byte is false + // if ((data_byte & 0x80) == 0) + // { + // *result = accum; + // return true; + // } + // } + // // UIntBase128 sequence exceeds 5 bytes + // return false; + // } + + uint accum = 0; + result = 0; + for (int i = 0; i < 5; ++i) + { + byte data_byte = reader.ReadByte(); + // No leading 0's + if (i == 0 && data_byte == 0x80) return false; + + // If any of top 7 bits are set then << 7 would overflow + if ((accum & 0xFE000000) != 0) return false; + // + accum = (uint)(accum << 7) | (uint)(data_byte & 0x7F); + // Spin until most significant bit of data byte is false + if ((data_byte & 0x80) == 0) + { + result = accum; + return true; + } + // + } + // UIntBase128 sequence exceeds 5 bytes + return false; + } + } + + class Woff2Utils + { + private const byte ONE_MORE_BYTE_CODE1 = 255; + private const byte ONE_MORE_BYTE_CODE2 = 254; + private const byte WORD_CODE = 253; + private const byte LOWEST_UCODE = 253; + + public static short[] ReadInt16Array(BinaryReader reader, int count) + { + short[] arr = new short[count]; + for (int i = 0; i < count; ++i) + { + arr[i] = reader.ReadInt16(); + } + return arr; + } + + public static ushort Read255UInt16(BinaryReader reader) + { + //255UInt16 Variable-length encoding of a 16-bit unsigned integer for optimized intermediate font data storage. + //255UInt16 Data Type + //255UInt16 is a variable-length encoding of an unsigned integer + //in the range 0 to 65535 inclusive. + //This data type is intended to be used as intermediate representation of various font values, + //which are typically expressed as UInt16 but represent relatively small values. + //Depending on the encoded value, the length of the data field may be one to three bytes, + //where the value of the first byte either represents the small value itself or is treated as a code that defines the format of the additional byte(s). + //The "C-like" pseudo-code describing how to read the 255UInt16 format is presented below: + // Read255UShort(data ) + // { + // UInt8 code; + // UInt16 value, value2; + + // const oneMoreByteCode1 = 255; + // const oneMoreByteCode2 = 254; + // const wordCode = 253; + // const lowestUCode = 253; + + // code = data.getNextUInt8(); + // if (code == wordCode) + // { + // /* Read two more bytes and concatenate them to form UInt16 value*/ + // value = data.getNextUInt8(); + // value <<= 8; + // value &= 0xff00; + // value2 = data.getNextUInt8(); + // value |= value2 & 0x00ff; + // } + // else if (code == oneMoreByteCode1) + // { + // value = data.getNextUInt8(); + // value = (value + lowestUCode); + // } + // else if (code == oneMoreByteCode2) + // { + // value = data.getNextUInt8(); + // value = (value + lowestUCode * 2); + // } + // else + // { + // value = code; + // } + // return value; + // } + //Note that the encoding is not unique.For example, + //the value 506 can be encoded as [255, 253], [254, 0], and[253, 1, 250]. + //An encoder may produce any of these, and a decoder MUST accept them all.An encoder should choose shorter encodings, + //and must be consistent in choice of encoding for the same value, as this will tend to compress better. + + byte code = reader.ReadByte(); + if (code == WORD_CODE) + { + /* Read two more bytes and concatenate them to form UInt16 value*/ + //int value = (reader.ReadByte() << 8) & 0xff00; + //int value2 = reader.ReadByte(); + //return (ushort)(value | (value2 & 0xff)); + int value = reader.ReadByte(); + value <<= 8; + value &= 0xff00; + int value2 = reader.ReadByte(); + value |= value2 & 0x00ff; + + return (ushort)value; + } + else if (code == ONE_MORE_BYTE_CODE1) + { + return (ushort)(reader.ReadByte() + LOWEST_UCODE); + } + else if (code == ONE_MORE_BYTE_CODE2) + { + return (ushort)(reader.ReadByte() + (LOWEST_UCODE * 2)); + } + else + { + return code; + } + } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/WoffReader.cs b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/WoffReader.cs new file mode 100644 index 00000000..029f5271 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.FontViewer/Typography.OpenFont/WebFont/WoffReader.cs @@ -0,0 +1,364 @@ +//MIT, 2019-present, WinterDev +//see https://www.w3.org/TR/2012/REC-WOFF-20121213/ + +using System; +using System.IO; +using Typography.OpenFont.Tables; +using Typography.OpenFont.Trimmable; + +namespace Typography.OpenFont.WebFont +{ + //NOTE: Web Font file structure is not part of 'Open Font Format'. + + class WoffHeader + { + //WOFFHeader + //UInt32 signature 0x774F4646 'wOFF' + //UInt32 flavor The "sfnt version" of the input font. + //UInt32 length Total size of the WOFF file. + //UInt16 numTables Number of entries in directory of font tables. + //UInt16 reserved Reserved; set to zero. + //UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables(including padding). + //UInt16 majorVersion Major version of the WOFF file. + //UInt16 minorVersion Minor version of the WOFF file. + //UInt32 metaOffset Offset to metadata block, from beginning of WOFF file. + //UInt32 metaLength Length of compressed metadata block. + //UInt32 metaOrigLength Uncompressed size of metadata block. + //UInt32 privOffset Offset to private data block, from beginning of WOFF file. + //UInt32 privLength Length of private data block. + + public uint flavor; + public uint length; + public uint numTables; + + //public ushort reserved; + public uint totalSfntSize; + + public ushort majorVersion; + public ushort minorVersion; + public uint metaOffset; + public uint metaLength; + public uint metaOriginalLength; + public uint privOffset; + public uint privLength; + } + + class WoffTableDirectory + { + //WOFF TableDirectoryEntry + //UInt32 tag 4-byte sfnt table identifier. + //UInt32 offset Offset to the data, from beginning of WOFF file. + //UInt32 compLength Length of the compressed data, excluding padding. + //UInt32 origLength Length of the uncompressed table, excluding padding. + //UInt32 origChecksum Checksum of the uncompressed table. + public uint tag; + + public uint offset; + public uint compLength; + public uint origLength; + public uint origChecksum; + + //translated values + //public UnreadTableEntry unreadTableEntry; //simulate + public string Name { get; set; } + + public long ExpectedStartAt { get; set; } +#if DEBUG + + public WoffTableDirectory() + { + } + + public override string ToString() + { + return Name; + } + +#endif + } + + public delegate bool ZlibDecompressStreamFunc(byte[] compressedInput, byte[] decompressOutput); + + public static class WoffDefaultZlibDecompressFunc + { + public static ZlibDecompressStreamFunc DecompressHandler; + } + + class WoffReader + { + private WoffHeader _header; + + public ZlibDecompressStreamFunc DecompressHandler; + + public PreviewFontInfo ReadPreview(BinaryReader reader) + { + //read preview only + //WOFF File + //WOFFHeader File header with basic font type and version, along with offsets to metadata and private data blocks. + //TableDirectory Directory of font tables, indicating the original size, compressed size and location of each table within the WOFF file. + //FontTables The font data tables from the input sfnt font, compressed to reduce bandwidth requirements. + //ExtendedMetadata An optional block of extended metadata, represented in XML format and compressed for storage in the WOFF file. + //PrivateData An optional block of private data for the font designer, foundry, or vendor to use. + + PreviewFontInfo fontPreviewInfo = null; + _header = ReadWOFFHeader(reader); + if (_header == null) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("can't read "); +#endif + return null; //notify user too + } + + // + WoffTableDirectory[] woffTableDirs = ReadTableDirectories(reader); + if (woffTableDirs == null) + { + return null; + } + //try read each compressed table + if (DecompressHandler == null) + { + if (WoffDefaultZlibDecompressFunc.DecompressHandler != null) + { + DecompressHandler = WoffDefaultZlibDecompressFunc.DecompressHandler; + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("no Zlib DecompressHandler "); +#endif + return null; //notify user too + } + } + + TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woffTableDirs); + //for font preview, we may not need to extract + using (MemoryStream decompressStream = new MemoryStream()) + { + if (Extract(reader, woffTableDirs, decompressStream)) + { + using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressStream)) + { + decompressStream.Position = 0; + OpenFontReader openFontReader = new OpenFontReader(); + PreviewFontInfo previewFontInfo = openFontReader.ReadPreviewFontInfo(tableEntryCollection, reader2); + if (previewFontInfo != null) + { + //add webfont info to this preview font + previewFontInfo.IsWebFont = true; + } + return previewFontInfo; + } + } + } + + return fontPreviewInfo; + } + + internal bool Read(Typeface typeface, BinaryReader reader, RestoreTicket ticket) + { + //WOFF File + //WOFFHeader File header with basic font type and version, along with offsets to metadata and private data blocks. + //TableDirectory Directory of font tables, indicating the original size, compressed size and location of each table within the WOFF file. + //FontTables The font data tables from the input sfnt font, compressed to reduce bandwidth requirements. + //ExtendedMetadata An optional block of extended metadata, represented in XML format and compressed for storage in the WOFF file. + //PrivateData An optional block of private data for the font designer, foundry, or vendor to use. + _header = ReadWOFFHeader(reader); + if (_header == null) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("can't read "); +#endif + return false; + } + + // + WoffTableDirectory[] woffTableDirs = ReadTableDirectories(reader); + if (woffTableDirs == null) + { + return false; + } + // + //try read each compressed table + if (DecompressHandler == null) + { + if (WoffDefaultZlibDecompressFunc.DecompressHandler != null) + { + DecompressHandler = WoffDefaultZlibDecompressFunc.DecompressHandler; + } + else + { +#if DEBUG + System.Diagnostics.Debug.WriteLine("no Zlib DecompressHandler "); +#endif + return false; + } + } + + TableEntryCollection tableEntryCollection = CreateTableEntryCollection(woffTableDirs); + + using (MemoryStream decompressStream = new MemoryStream()) + { + if (Extract(reader, woffTableDirs, decompressStream)) + { + using (ByteOrderSwappingBinaryReader reader2 = new ByteOrderSwappingBinaryReader(decompressStream)) + { + decompressStream.Position = 0; + OpenFontReader openFontReader = new OpenFontReader(); + return openFontReader.ReadTableEntryCollection(typeface, ticket, tableEntryCollection, reader2); + } + } + } + return false; + } + + static TableEntryCollection CreateTableEntryCollection(WoffTableDirectory[] woffTableDirs) + { + TableEntryCollection tableEntryCollection = new TableEntryCollection(); + for (int i = 0; i < woffTableDirs.Length; ++i) + { + WoffTableDirectory woffTableDir = woffTableDirs[i]; + tableEntryCollection.AddEntry( + new UnreadTableEntry( + new TableHeader(woffTableDir.tag, + woffTableDir.origChecksum, + (uint)woffTableDir.ExpectedStartAt, + woffTableDir.origLength))); + } + + return tableEntryCollection; + } + + static WoffHeader ReadWOFFHeader(BinaryReader reader) + { + //WOFFHeader + //UInt32 signature 0x774F4646 'wOFF' + //UInt32 flavor The "sfnt version" of the input font. + //UInt32 length Total size of the WOFF file. + //UInt16 numTables Number of entries in directory of font tables. + //UInt16 reserved Reserved; set to zero. + //UInt32 totalSfntSize Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables(including padding). + //UInt16 majorVersion Major version of the WOFF file. + //UInt16 minorVersion Minor version of the WOFF file. + //UInt32 metaOffset Offset to metadata block, from beginning of WOFF file. + //UInt32 metaLength Length of compressed metadata block. + //UInt32 metaOrigLength Uncompressed size of metadata block. + //UInt32 privOffset Offset to private data block, from beginning of WOFF file. + //UInt32 privLength Length of private data block. + + //signature + byte b0 = reader.ReadByte(); + byte b1 = reader.ReadByte(); + byte b2 = reader.ReadByte(); + byte b3 = reader.ReadByte(); + if (!(b0 == 0x77 && b1 == 0x4f && b2 == 0x46 && b3 == 0x46)) + { + return null; + } + WoffHeader header = new WoffHeader(); + header.flavor = reader.ReadUInt32BE(); + header.length = reader.ReadUInt32BE(); + header.numTables = reader.ReadUInt16BE(); + ushort reserved = reader.ReadUInt16BE(); + header.totalSfntSize = reader.ReadUInt32BE(); + + header.majorVersion = reader.ReadUInt16BE(); + header.minorVersion = reader.ReadUInt16BE(); + + header.metaOffset = reader.ReadUInt32BE(); + header.metaLength = reader.ReadUInt32BE(); + header.metaOriginalLength = reader.ReadUInt32BE(); + + header.privOffset = reader.ReadUInt32BE(); + header.privLength = reader.ReadUInt32BE(); + + return header; + } + + WoffTableDirectory[] ReadTableDirectories(BinaryReader reader) + { + //The table directory is an array of WOFF table directory entries, as defined below. + //The directory follows immediately after the WOFF file header; + //therefore, there is no explicit offset in the header pointing to this block. + //Its size is calculated by multiplying the numTables value in the WOFF header times the size of a single WOFF table directory. + //Each table directory entry specifies the size and location of a single font data table. + + uint tableCount = (uint)_header.numTables; //? + //tableDirs = new WoffTableDirectory[tableCount]; + long expectedStartAt = 0; + + //simulate table entry collection + //var tableEntryCollection = new TableEntryCollection((int)tableCount); + WoffTableDirectory[] tableDirs = new WoffTableDirectory[tableCount]; + + for (int i = 0; i < tableCount; ++i) + { + //UInt32 tag 4-byte sfnt table identifier. + //UInt32 offset Offset to the data, from beginning of WOFF file. + //UInt32 compLength Length of the compressed data, excluding padding. + //UInt32 origLength Length of the uncompressed table, excluding padding. + //UInt32 origChecksum Checksum of the uncompressed table. + + WoffTableDirectory table = new WoffTableDirectory(); + table.tag = reader.ReadUInt32(); + table.offset = reader.ReadUInt32(); + table.compLength = reader.ReadUInt32(); + table.origLength = reader.ReadUInt32(); + table.origChecksum = reader.ReadUInt32(); + + table.ExpectedStartAt = expectedStartAt; + table.Name = Utils.TagToString(table.tag); + //var tableHeader = new TableHeader(tag, origChecksum, (uint)expectedStartAt, origLength); + //var unreadTable = new UnreadTableEntry(tableHeader); + //tableEntryCollection.AddEntry(unreadTable); + //table.unreadTableEntry = unreadTable; + + tableDirs[i] = table; + expectedStartAt += table.origLength; + } + + return tableDirs; + } + + bool Extract(BinaryReader reader, WoffTableDirectory[] tables, Stream newDecompressedStream) + { + for (int i = 0; i < tables.Length; ++i) + { + //UInt32 tag 4-byte sfnt table identifier. + //UInt32 offset Offset to the data, from beginning of WOFF file. + //UInt32 compLength Length of the compressed data, excluding padding. + //UInt32 origLength Length of the uncompressed table, excluding padding. + //UInt32 origChecksum Checksum of the uncompressed table. + + WoffTableDirectory table = tables[i]; + reader.BaseStream.Seek(table.offset, SeekOrigin.Begin); + + //indeed, table may be compress or not=> check length of before and after ... + byte[] compressedBuffer = reader.ReadBytes((int)table.compLength); + + if (compressedBuffer.Length == table.origLength) + { + //not a compress buffer + newDecompressedStream.Write(compressedBuffer, 0, compressedBuffer.Length); + } + else + { + var decompressedBuffer = new byte[table.origLength]; + if (!DecompressHandler(compressedBuffer, decompressedBuffer)) + { + //if not pass set to null + decompressedBuffer = null; + } + else + { + //pass + newDecompressedStream.Write(decompressedBuffer, 0, decompressedBuffer.Length); + } + } + } + newDecompressedStream.Flush(); + return true; + } + } +}