commit 72061260e1dae23fa31918b5d6cbf48271740414 Author: gered Date: Sun Aug 11 09:54:26 2013 -0400 Initial commit Based on version 1.0.5 from http://www.zer7.com/software/truetypesharp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29a3e97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.suo +*.user +*.userprefs +*.sln.docstates +[Dd]ebug/ +[Rr]elease/ +[Bb]in/ +[Oo]bj/ + diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..c9884a8 --- /dev/null +++ b/CHANGES @@ -0,0 +1,5 @@ +1.0.5 May 14, 2012: + Ported over stb_truetype 0.5, adding kerning and subpixel functions. + +1.0.2 August 25, 2010: + Initial public release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fb90e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +TrueTypeSharp +Copyright (c) 2010, 2012 Illusory Studios LLC + +TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's +C library stb_truetype, which was placed in the public domain and is +available at nothings.org. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/TrueTypeSharp.Demo/Program.cs b/TrueTypeSharp.Demo/Program.cs new file mode 100644 index 0000000..fbb47c9 --- /dev/null +++ b/TrueTypeSharp.Demo/Program.cs @@ -0,0 +1,102 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; +using System.Drawing; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace TrueTypeSharp.Demo +{ + class Program + { + static void SaveBitmap(byte[] data, int x0, int y0, int x1, int y1, + int stride, string filename) + { + var bitmap = new Bitmap(x1 - x0, y1 - y0); + for (int y = y0; y < y1; y++) + { + for (int x = x0; x < x1; x++) + { + byte opacity = data[y * stride + x]; + bitmap.SetPixel(x - x0, y - y0, Color.FromArgb(opacity, 0x00, 0x7f, 0xff)); + } + } + bitmap.Save(filename); + } + + static void Main(string[] args) + { + var font = new TrueTypeFont(@"Anonymous/Anonymous Pro.ttf"); + + // Render some characters... + for (char ch = 'A'; ch <= 'Z'; ch++) + { + int width, height, xOffset, yOffset; + float scale = font.GetScaleForPixelHeight(80); + byte[] data = font.GetCodepointBitmap(ch, scale, scale, + out width, out height, out xOffset, out yOffset); + + SaveBitmap(data, 0, 0, width, height, width, "Char-" + ch.ToString() + ".png"); + } + + // Let's try baking. Tasty tasty. + BakedCharCollection characters; float pixelHeight = 18; + var bitmap = font.BakeFontBitmap(pixelHeight, out characters, true); + + SaveBitmap(bitmap.Buffer, 0, 0, bitmap.Width, bitmap.Height, bitmap.Width, "BakeResult1.png"); + + // Now, let's give serialization a go. + using (var file = File.OpenWrite("BakeResult2.temp")) + { + var bitmapSaver = new BinaryFormatter(); + bitmapSaver.Serialize(file, bitmap); + bitmapSaver.Serialize(file, characters); + + int ascent, descent, lineGap; + float scale = font.GetScaleForPixelHeight(pixelHeight); + font.GetFontVMetrics(out ascent, out descent, out lineGap); + bitmapSaver.Serialize(file, (float)ascent * scale); + bitmapSaver.Serialize(file, (float)descent * scale); + bitmapSaver.Serialize(file, (float)lineGap * scale); + } + + using (var file = File.OpenRead("BakeResult2.temp")) + { + var bitmapLoader = new BinaryFormatter(); + var bitmapAgain = (FontBitmap)bitmapLoader.Deserialize(file); + var charactersAgain = (BakedCharCollection)bitmapLoader.Deserialize(file); + + SaveBitmap(bitmapAgain.Buffer, 0, 0, bitmapAgain.Width, bitmapAgain.Height, bitmap.Width, "BakeResult2.png"); + for (char ch = 'A'; ch <= 'Z'; ch++) + { + BakedChar bakedChar = charactersAgain[ch]; + if (bakedChar.IsEmpty) { continue; } + SaveBitmap(bitmapAgain.Buffer, + bakedChar.X0, bakedChar.Y0, bakedChar.X1, bakedChar.Y1, + bitmapAgain.Stride, "SmallChar-" + ch.ToString() + ".png"); + } + } + } + } +} diff --git a/TrueTypeSharp.Demo/Properties/AssemblyInfo.cs b/TrueTypeSharp.Demo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b51c80d --- /dev/null +++ b/TrueTypeSharp.Demo/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +[assembly: AssemblyTitle("TrueTypeSharp.Demo")] +[assembly: AssemblyDescription("TrueTypeSharp demo application")] +[assembly: AssemblyProduct("TrueTypeSharp")] +[assembly: AssemblyCopyright("TrueTypeSharp: Copyright © 2010 Illusory Studios LLC. stb_truetype: Public domain, Sean Barrett.")] +[assembly: AssemblyVersion("1.0.0.0")] + diff --git a/TrueTypeSharp.Demo/TrueTypeSharp.Demo.csproj b/TrueTypeSharp.Demo/TrueTypeSharp.Demo.csproj new file mode 100644 index 0000000..2773c12 --- /dev/null +++ b/TrueTypeSharp.Demo/TrueTypeSharp.Demo.csproj @@ -0,0 +1,92 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {B067AB58-D823-4A46-97E1-43BC75F7FF35} + Exe + Properties + TrueTypeSharp.Demo + TrueTypeSharp.Demo + v2.0 + 512 + + + + + 3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + + + + + + + + + + + + {B722113F-1252-4BE1-9D43-6BC82B3E37D1} + TrueTypeSharp + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/TrueTypeSharp.sln b/TrueTypeSharp.sln new file mode 100644 index 0000000..842eea5 --- /dev/null +++ b/TrueTypeSharp.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueTypeSharp", "TrueTypeSharp\TrueTypeSharp.csproj", "{B722113F-1252-4BE1-9D43-6BC82B3E37D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueTypeSharp.Demo", "TrueTypeSharp.Demo\TrueTypeSharp.Demo.csproj", "{B067AB58-D823-4A46-97E1-43BC75F7FF35}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B722113F-1252-4BE1-9D43-6BC82B3E37D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B722113F-1252-4BE1-9D43-6BC82B3E37D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B722113F-1252-4BE1-9D43-6BC82B3E37D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B722113F-1252-4BE1-9D43-6BC82B3E37D1}.Release|Any CPU.Build.0 = Release|Any CPU + {B067AB58-D823-4A46-97E1-43BC75F7FF35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B067AB58-D823-4A46-97E1-43BC75F7FF35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B067AB58-D823-4A46-97E1-43BC75F7FF35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B067AB58-D823-4A46-97E1-43BC75F7FF35}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TrueTypeSharp/BakedChar.cs b/TrueTypeSharp/BakedChar.cs new file mode 100644 index 0000000..0d45dd4 --- /dev/null +++ b/TrueTypeSharp/BakedChar.cs @@ -0,0 +1,68 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; + +namespace TrueTypeSharp +{ + [Serializable] + public struct BakedChar + { + public static BakedChar Empty + { + get { return default(BakedChar); } + } + + public ushort X0, Y0, X1, Y1; + public float XOffset, YOffset, XAdvance; + + public BakedQuad GetBakedQuad(int bakeWidth, int bakeHeight, + ref float xPosition, ref float yPosition) + { + return GetBakedQuad(bakeWidth, bakeHeight, ref xPosition, ref yPosition, false); + } + + public BakedQuad GetBakedQuad(int bakeWidth, int bakeHeight, + ref float xPosition, ref float yPosition, bool putTexCoordsAtTexelCenters) + { + BakedQuad quad; + stb_truetype.stbtt_GetBakedQuad(ref this, bakeWidth, bakeHeight, + ref xPosition, ref yPosition, out quad, putTexCoordsAtTexelCenters ? 0 : 1); + return quad; + } + + public bool IsEmpty + { + get { return Width == 0 && Height == 0 && XAdvance == 0; } + } + + public int Width + { + get { return X1 - X0; } + } + + public int Height + { + get { return Y1 - Y0; } + } + } +} diff --git a/TrueTypeSharp/BakedCharCollection.cs b/TrueTypeSharp/BakedCharCollection.cs new file mode 100644 index 0000000..1b07186 --- /dev/null +++ b/TrueTypeSharp/BakedCharCollection.cs @@ -0,0 +1,224 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace TrueTypeSharp +{ + [Serializable] + public class BakedCharCollection : ISerializable + { + Dictionary _characters; + int _bakeWidth, _bakeHeight; + + public BakedCharCollection(char firstCodepoint, BakedChar[] characters, + int bakeWidth, int bakeHeight) + { + if (characters == null) { throw new ArgumentNullException("characters"); } + + Dictionary dictionary = new Dictionary(); + for (int i = 0; i < characters.Length; i++) + { + char codepoint = (char)(firstCodepoint + i); + if (char.IsSurrogate(codepoint)) { continue; } + + BakedChar character = characters[i]; + if (character.IsEmpty) { continue; } + + dictionary[codepoint] = character; + } + Create(dictionary, bakeWidth, bakeHeight); + } + + protected BakedCharCollection(SerializationInfo info, StreamingContext context) + { + Create((Dictionary) + info.GetValue("Characters", typeof(Dictionary)), + info.GetInt32("BakeWidth"), info.GetInt32("BakeHeight")); + } + + void Create(Dictionary characters, int bakeWidth, int bakeHeight) + { + if (characters == null) { throw new ArgumentNullException("characters"); } + if (bakeWidth < 0 || bakeHeight < 0) { throw new ArgumentOutOfRangeException(); } + + BakeWidth = bakeWidth; BakeHeight = bakeHeight; + _characters = characters; + + } + + public IEnumerable GetTextQuads(string str, + float lineAscender, float lineDescender, float lineGap, + int hAlign, int vAlign) + { + return GetTextQuads(new string[] { str }, + lineAscender, lineDescender, lineGap, hAlign, vAlign); + } + + public IEnumerable GetTextQuads(IEnumerable strs, + float lineAscender, float lineDescender, float lineGap, + int hAlign, int vAlign) + { + if (strs == null) { throw new ArgumentNullException("strs"); } + + float y0 = 0, y1, yPosition = 0; + y1 = BakedCharCollection.GetVerticalTextHeight(strs, lineAscender, lineDescender, lineGap); + BakedCharCollection.OffsetTextPositionForAlignment(y0, y1, ref yPosition, vAlign); + + foreach (var str in strs) + { + yPosition += lineAscender; + + float sx0, sy0, sx1, sy1, sxPosition = 0, syPosition = yPosition; + if (GetTextBounds(str, out sx0, out sy0, out sx1, out sy1)) + { + BakedCharCollection.OffsetTextPositionForAlignment + (sx0, sx1, ref sxPosition, hAlign); + + foreach (var ch in str) + { + var quad = GetBakedQuad(ch, ref sxPosition, ref syPosition); + if (quad.IsEmpty) { continue; } + + yield return quad; + } + } + + yPosition += lineGap - lineDescender; + } + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Characters", Characters); + info.AddValue("BakeWidth", BakeWidth); + info.AddValue("BakeHeight", BakeHeight); + } + + public BakedQuad GetBakedQuad(char character, + ref float xPosition, ref float yPosition) + { + return GetBakedQuad(character, ref xPosition, ref yPosition, false); + } + + public BakedQuad GetBakedQuad(char character, + ref float xPosition, ref float yPosition, bool putTexCoordsAtTexelCenters) + { + var bakedChar = this[character]; if (bakedChar.IsEmpty) { return BakedQuad.Empty; } + return bakedChar.GetBakedQuad(BakeWidth, BakeHeight, + ref xPosition, ref yPosition, putTexCoordsAtTexelCenters); + } + + static int ElementCount(IEnumerable strs) + { + var list = strs as ICollection; if (list != null) { return list.Count; } + var e = strs.GetEnumerator(); int count = 0; while (e.MoveNext()) { count++; } + return count; + } + + public static float GetVerticalTextHeight(IEnumerable strs, + float lineAscender, float lineDescender, float lineGap) + { + if (strs == null) { throw new ArgumentNullException("strs"); } + return GetVerticalTextHeight(ElementCount(strs), + lineAscender, lineDescender, lineGap); + } + + public static float GetVerticalTextHeight(int lineCount, + float lineAscender, float lineDescender, float lineGap) + { + return Math.Max(0, lineGap * (lineCount - 1) + + (lineAscender - lineDescender) * lineCount); + } + + public bool GetTextBounds(string str, out float x0, out float y0, out float x1, out float y1) + { + if (str == null) { throw new ArgumentNullException("str"); } + x0 = y0 = x1 = y1 = 0; bool isSet = false; float xPosition = 0, yPosition = 0; + + foreach (var ch in str) + { + var quad = GetBakedQuad(ch, ref xPosition, ref yPosition); + if (quad.IsEmpty) { continue; } + + if (isSet) + { + if (quad.X0 < x0) { x0 = quad.X0; } if (quad.Y0 < y0) { y0 = quad.Y0; } + if (quad.X1 > x1) { x1 = quad.X1; } if (quad.Y1 > y1) { y1 = quad.Y1; } + } + else + { + x0 = quad.X0; y0 = quad.Y0; x1 = quad.X1; y1 = quad.Y1; + isSet = true; + } + } + + return isSet; + } + + public void OffsetTextPositionForAlignment(string str, + ref float xPosition, ref float yPosition, int hAlign, int vAlign) + { + float x0, y0, x1, y1; + GetTextBounds(str, out x0, out y0, out x1, out y1); + OffsetTextPositionForAlignment(x0, x1, ref xPosition, hAlign); + OffsetTextPositionForAlignment(y0, y1, ref yPosition, vAlign); + } + + public static void OffsetTextPositionForAlignment(float xOrY0, float xOrY1, + ref float position, int align) + { + if (align < 0) { position -= xOrY0; } + else if (align == 0) { position -= (xOrY0 + xOrY1) * 0.5f; } + else { position -= xOrY1; } + } + + public int BakeWidth + { + get { return _bakeWidth; } + set { if (value < 0) { throw new ArgumentOutOfRangeException(); } _bakeWidth = value; } + } + + public int BakeHeight + { + get { return _bakeHeight; } + set { if (value < 0) { throw new ArgumentOutOfRangeException(); } _bakeHeight = value; } + } + + public IDictionary Characters + { + get { return _characters; } + } + + public BakedChar this[char character] + { + get + { + BakedChar bakedChar; + _characters.TryGetValue(character, out bakedChar); + return bakedChar; + } + } + } +} diff --git a/TrueTypeSharp/BakedQuad.cs b/TrueTypeSharp/BakedQuad.cs new file mode 100644 index 0000000..982a23d --- /dev/null +++ b/TrueTypeSharp/BakedQuad.cs @@ -0,0 +1,43 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; + +namespace TrueTypeSharp +{ + [Serializable] + public struct BakedQuad + { + public static BakedQuad Empty + { + get { return default(BakedQuad); } + } + + public float X0, Y0, S0, T0; + public float X1, Y1, S1, T1; + + public bool IsEmpty + { + get { return X0 == X1 && Y0 == Y1; } + } + } +} diff --git a/TrueTypeSharp/ContourPoint.cs b/TrueTypeSharp/ContourPoint.cs new file mode 100644 index 0000000..9f06b78 --- /dev/null +++ b/TrueTypeSharp/ContourPoint.cs @@ -0,0 +1,32 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; + +namespace TrueTypeSharp +{ + [Serializable] + public struct ContourPoint + { + public float X, Y; + } +} diff --git a/TrueTypeSharp/FakePtr.cs b/TrueTypeSharp/FakePtr.cs new file mode 100644 index 0000000..6060026 --- /dev/null +++ b/TrueTypeSharp/FakePtr.cs @@ -0,0 +1,98 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; + +namespace TrueTypeSharp +{ + #pragma warning disable 0660, 0661 // This never goes into a collection... + struct FakePtr + { + public T[] Array; public int Offset; + + public T[] GetData(int length) + { + var t = new T[length]; + if (Array != null) { global::System.Array.Copy(Array, Offset, t, 0, length); } + return t; + } + + public void MakeNull() + { + Array = null; Offset = 0; + } + + public T this[int index] + { + get + { + try { return Array[Offset + index]; } + catch (IndexOutOfRangeException) { return default(T); } // Sometimes accesses are made out of range, it appears. + // In particular, to get all the way to char.MaxValue, this was needed. + // Probably bad data in the font. Also, is bounds checking done? + // I don't see it... Either way, it's not a problem for us here. + } + set { Array[Offset + index] = value; } + } + + public T Value + { + get { return this[0]; } + set { this[0] = value; } + } + + public bool IsNull + { + get { return Array == null; } + } + + public static FakePtr operator +(FakePtr p, int offset) + { + return new FakePtr() { Array = p.Array, Offset = p.Offset + offset }; + } + + public static FakePtr operator -(FakePtr p, int offset) + { + return p + -offset; + } + + public static FakePtr operator +(FakePtr p, uint offset) + { + return p + (int)offset; + } + + public static FakePtr operator ++(FakePtr p) + { + return p + 1; + } + + public static bool operator ==(FakePtr p1, FakePtr p2) + { + return p1.Array == p2.Array && p1.Offset == p2.Offset; + } + + public static bool operator !=(FakePtr p1, FakePtr p2) + { + return !(p1 == p2); + } + } +} diff --git a/TrueTypeSharp/FontBitmap.cs b/TrueTypeSharp/FontBitmap.cs new file mode 100644 index 0000000..3f901d7 --- /dev/null +++ b/TrueTypeSharp/FontBitmap.cs @@ -0,0 +1,182 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; +using System.Runtime.Serialization; + +namespace TrueTypeSharp +{ + [Serializable] + public struct FontBitmap : ISerializable + { + public delegate T PixelConversionFunc(byte opacity); + + public byte[] Buffer; public int BufferOffset; + public int XOffset, YOffset, Width, Height, Stride; + + public FontBitmap(int width, int height) : this() + { + if (width < 0 || height < 0 || width * height < 0) + { throw new ArgumentOutOfRangeException(); } + + Buffer = new byte[width * height]; + Stride = Width = width; Height = height; + } + + FontBitmap(SerializationInfo info, StreamingContext context) : this() + { + Buffer = (byte[])info.GetValue("Buffer", typeof(byte[])); + BufferOffset = info.GetInt32("BufferOffset"); + XOffset = info.GetInt32("XOffset"); + YOffset = info.GetInt32("YOffset"); + Width = info.GetInt32("Width"); + Height = info.GetInt32("Height"); + Stride = info.GetInt32("Stride"); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Buffer", Buffer); + info.AddValue("BufferOffset", BufferOffset); + info.AddValue("XOffset", XOffset); + info.AddValue("YOffset", YOffset); + info.AddValue("Width", Width); + info.AddValue("Height", Height); + info.AddValue("Stride", Stride); + } + + public FontBitmap GetResizedBitmap(int width, int height) + { + var bitmap = new FontBitmap(width, height); + int w = Math.Min(width, Width), h = Math.Min(height, Height); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) { bitmap[x, y] = this[x, y]; } + } + return bitmap; + } + + public FontBitmap GetResizedBitmap(int width, int height, BakedCharCollection bakedChars) + { + var bitmap = GetResizedBitmap(width, height); + if (bakedChars != null) { bakedChars.BakeWidth = bitmap.Width; bakedChars.BakeHeight = bitmap.Height; } + return bitmap; + } + + static int RoundUpToPow2(int value) + { + int rounded = 1; + while (rounded < value) { rounded <<= 1; } + return rounded; + } + + public FontBitmap GetResizedBitmapPow2() + { + return GetResizedBitmap(RoundUpToPow2(Width), RoundUpToPow2(Height)); + } + + public FontBitmap GetResizedBitmapPow2(BakedCharCollection bakedChars) + { + return GetResizedBitmap(RoundUpToPow2(Width), RoundUpToPow2(Height), bakedChars); + } + + public FontBitmap GetTrimmedBitmap() + { + return GetResizedBitmap(Width, Height); + } + + public byte[,] To2DBitmap() + { + return To2DBitmap(false); + } + + public byte[,] To2DBitmap(bool yMajor) + { + return To2DBitmap(yMajor, x => x); + } + + public T[,] To2DBitmap(bool yMajor, PixelConversionFunc pixelConversionFunc) + { + if (pixelConversionFunc == null) { throw new ArgumentNullException("pixelConversionFunc"); } + + if (yMajor) + { + var bitmap = new T[Height, Width]; + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + bitmap[y, x] = pixelConversionFunc(this[x, y]); + } + } + return bitmap; + } + else + { + var bitmap = new T[Width, Height]; + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + bitmap[x, y] = pixelConversionFunc(this[x, y]); + } + } + return bitmap; + } + } + + public int StartOffset + { + get { return YOffset * Stride + XOffset + BufferOffset; } + } + + public bool IsValid + { + get + { + return Buffer != null && Width >= 0 && Height >= 0 && StartOffset >= 0 + && Width * Height >= 0 && Width * Height <= Buffer.Length + && StartOffset + Height * Stride >= 0 + && StartOffset + Height * Stride <= Buffer.Length; + } + } + + internal FakePtr StartPointer + { + get { return new FakePtr() { Array = Buffer, Offset = StartOffset }; } + } + + public byte this[int x, int y] + { + get + { + try { return Buffer[StartOffset + y * Stride + x]; } + catch (NullReferenceException) { throw new InvalidOperationException(); } + } + set + { + try { Buffer[StartOffset + y * Stride + x] = value; } + catch (NullReferenceException) { throw new InvalidOperationException(); } + } + } + } +} diff --git a/TrueTypeSharp/GlyphVertex.cs b/TrueTypeSharp/GlyphVertex.cs new file mode 100644 index 0000000..49d2287 --- /dev/null +++ b/TrueTypeSharp/GlyphVertex.cs @@ -0,0 +1,33 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; + +namespace TrueTypeSharp +{ + [Serializable] + public struct GlyphVertex + { + public short X, Y, CX, CY; + public GlyphVertexType Type; + } +} \ No newline at end of file diff --git a/TrueTypeSharp/GlyphVertexType.cs b/TrueTypeSharp/GlyphVertexType.cs new file mode 100644 index 0000000..5fce136 --- /dev/null +++ b/TrueTypeSharp/GlyphVertexType.cs @@ -0,0 +1,31 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +namespace TrueTypeSharp +{ + public enum GlyphVertexType : ushort + { + Move = 1, + Line, + Curve + } +} diff --git a/TrueTypeSharp/Properties/AssemblyInfo.cs b/TrueTypeSharp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..464de03 --- /dev/null +++ b/TrueTypeSharp/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +[assembly: AssemblyTitle("TrueTypeSharp")] +[assembly: AssemblyDescription("C# TrueType font renderer")] +[assembly: AssemblyProduct("TrueTypeSharp")] +[assembly: AssemblyCopyright("TrueTypeSharp: Copyright © 2010, 2012 Illusory Studios LLC. stb_truetype: Public domain, Sean Barrett.")] +[assembly: AssemblyVersion("1.0.5.0")] + diff --git a/TrueTypeSharp/TrueTypeFont.Codepoints.cs b/TrueTypeSharp/TrueTypeFont.Codepoints.cs new file mode 100644 index 0000000..53e4639 --- /dev/null +++ b/TrueTypeSharp/TrueTypeFont.Codepoints.cs @@ -0,0 +1,85 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010, 2012 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +namespace TrueTypeSharp +{ + public partial class TrueTypeFont + { + public byte[] GetCodepointBitmapSubpixel(char codepoint, + float xScale, float yScale, float xShift, float yShift, + out int width, out int height, out int xOffset, out int yOffset) + { + return GetGlyphBitmapSubpixel(FindGlyphIndex(codepoint), + xScale, yScale, xShift, yShift, + out width, out height, out xOffset, out yOffset); + } + + public byte[] GetCodepointBitmap(char codepoint, float xScale, float yScale, + out int width, out int height, out int xOffset, out int yOffset) + { + return GetCodepointBitmapSubpixel(codepoint, xScale, yScale, 0, 0, + out width, out height, out xOffset, out yOffset); + } + + public void GetCodepointBitmapBoxSubpixel(char codepoint, + float xScale, float yScale, float xShift, float yShift, + out int x0, out int y0, out int x1, out int y1) + { + GetGlyphBitmapBoxSubpixel(FindGlyphIndex(codepoint), + xScale, yScale, xShift, yShift, + out x0, out y0, out x1, out y1); + } + + public void GetCodepointBitmapBox(char codepoint, float xScale, float yScale, + out int x0, out int y0, out int x1, out int y1) + { + GetCodepointBitmapBoxSubpixel(codepoint, xScale, yScale, 0, 0, out x0, out y0, out x1, out y1); + } + + public void GetCodepointBox(char codepoint, + out int x0, out int y0, out int x1, out int y1) + { + GetGlyphBox(FindGlyphIndex(codepoint), out x0, out y0, out x1, out y1); + } + + public int GetCodepointKernAdvance(char codepoint1, char codepoint2) + { + return GetGlyphKernAdvance(FindGlyphIndex(codepoint1), FindGlyphIndex(codepoint2)); + } + + public void GetCodepointHMetrics(char codepoint, out int advanceWidth, out int leftSideBearing) + { + GetGlyphHMetrics(FindGlyphIndex(codepoint), out advanceWidth, out leftSideBearing); + } + + public GlyphVertex[] GetCodepointShape(char codepoint) + { + return GetGlyphShape(FindGlyphIndex(codepoint)); + } + + public void MakeCodepointBitmap(char codepoint, float xScale, float yScale, + FontBitmap bitmap) + { + MakeGlyphBitmap(FindGlyphIndex(codepoint), xScale, yScale, bitmap); + } + } +} diff --git a/TrueTypeSharp/TrueTypeFont.cs b/TrueTypeSharp/TrueTypeFont.cs new file mode 100644 index 0000000..d089cfa --- /dev/null +++ b/TrueTypeSharp/TrueTypeFont.cs @@ -0,0 +1,256 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010, 2012 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +using System; +using System.Diagnostics; +using System.IO; + +namespace TrueTypeSharp +{ + public partial class TrueTypeFont + { + stb_truetype.stbtt_fontinfo _info; + + public TrueTypeFont(byte[] data, int offset) + { + CheckFontData(data, offset); + + if (0 == stb_truetype.stbtt_InitFont(ref _info, + new FakePtr() { Array = data }, offset)) + { + throw new BadImageFormatException("Couldn't load TrueType file."); + } + } + + public TrueTypeFont(string filename) + : this(File.ReadAllBytes(filename), 0) + { + + } + + static void CheckFontData(byte[] data, int offset) + { + if (data == null) { throw new ArgumentNullException("data"); } + if (offset < 0 || offset > data.Length) { throw new ArgumentOutOfRangeException("offset"); } + } + + public static bool TryGetFontOffsetForIndex(byte[] data, int index, out int offset) + { + offset = stb_truetype.stbtt_GetFontOffsetForIndex(new FakePtr() { Array = data }, index); + if (offset < 0) { offset = 0; return false; } else { return true; } + } + + public int BakeFontBitmap(float pixelHeight, char firstCodepoint, + BakedChar[] characters, FontBitmap bitmap) + { + float scale = GetScaleForPixelHeight(pixelHeight); + return BakeFontBitmap(scale, scale, firstCodepoint, characters, bitmap); + } + + public int BakeFontBitmap(float xScale, float yScale, char firstCodepoint, + BakedChar[] characters, FontBitmap bitmap) + { + if (!bitmap.IsValid) { throw new ArgumentException("bitmap"); } + if (characters == null) { throw new ArgumentNullException("characters"); } + + return stb_truetype.stbtt_BakeFontBitmap(ref _info, xScale, yScale, + bitmap.StartPointer, bitmap.Width, bitmap.Height, bitmap.Stride, + (int)firstCodepoint, characters.Length, + new FakePtr() { Array = characters }); + } + + // generates square textures ... minimalHeight can be used to crop if desired + public FontBitmap BakeFontBitmap(float pixelHeight, char firstCodepoint, + BakedChar[] characters, out int minimalHeight) + { + int size = 16; + if (characters.Length == 0) { minimalHeight = 0; return new FontBitmap(0, 0); } + + while (true) + { + var bitmap = new FontBitmap(size, size); + int result = BakeFontBitmap(pixelHeight, firstCodepoint, characters, bitmap); + if (result > 0) { minimalHeight = result; return bitmap; } + + size *= 2; + } + } + + public FontBitmap BakeFontBitmap(float pixelHeight, out BakedCharCollection characters) + { + return BakeFontBitmap(pixelHeight, out characters, false); + } + + public FontBitmap BakeFontBitmap(float pixelHeight, out BakedCharCollection characters, + bool shrinkToMinimalHeight) + { + return BakeFontBitmap(pixelHeight, char.MinValue, char.MaxValue, out characters, + shrinkToMinimalHeight); + } + + public FontBitmap BakeFontBitmap(float pixelHeight, char firstCodepoint, + char lastCodepoint, out BakedCharCollection characters) + { + if (lastCodepoint < firstCodepoint) + { + var tmp = lastCodepoint; + lastCodepoint = firstCodepoint; + firstCodepoint = tmp; + } + + return BakeFontBitmap(pixelHeight, firstCodepoint, + lastCodepoint - firstCodepoint + 1, out characters); + } + + public FontBitmap BakeFontBitmap(float pixelHeight, char firstCodepoint, + int characterCount, out BakedCharCollection characters) + { + return BakeFontBitmap(pixelHeight, firstCodepoint, characterCount, + out characters, false); + } + + public FontBitmap BakeFontBitmap(float pixelHeight, char firstCodepoint, + int characterCount, out BakedCharCollection characters, bool shrinkToMinimalHeight) + { + if (characterCount < 0) { throw new ArgumentOutOfRangeException("characterCount"); } + + var charArray = new BakedChar[characterCount]; int minimalHeight; + var bitmap = BakeFontBitmap(pixelHeight, firstCodepoint, charArray, out minimalHeight); + if (shrinkToMinimalHeight) { bitmap.Height = minimalHeight; bitmap = bitmap.GetTrimmedBitmap(); } + + characters = new BakedCharCollection(firstCodepoint, charArray, bitmap.Width, bitmap.Height); + return bitmap; + } + + public uint FindGlyphIndex(char codepoint) + { + return stb_truetype.stbtt_FindGlyphIndex(ref _info, (uint)codepoint); + } + + public void FlattenCurves(GlyphVertex[] vertices, float flatness, + out ContourPoint[] points, out int[] contourLengths) + { + if (vertices == null) { throw new ArgumentNullException("vertices"); } + + FakePtr contourLengthsFP; int contourCount; + var contours = stb_truetype.stbtt_FlattenCurves + (new FakePtr() { Array = vertices }, vertices.Length, + flatness, out contourLengthsFP, out contourCount); + contourLengths = contourLengthsFP.GetData(contourCount); + + int pointCount = 0; + foreach (var length in contourLengths) { pointCount += length; } + points = contours.GetData(pointCount); + } + + public byte[] GetGlyphBitmapSubpixel(uint glyphIndex, float xScale, float yScale, float xShift, float yShift, + out int width, out int height, out int xOffset, out int yOffset) + { + var data = stb_truetype.stbtt_GetGlyphBitmapSubpixel(ref _info, xScale, yScale, xShift, yShift, glyphIndex, + out width, out height, out xOffset, out yOffset); + if (data.IsNull) { width = 0; height = 0; xOffset = 0; yOffset = 0; return data.GetData(0); } + return data.GetData(width * height); + } + + public byte[] GetGlyphBitmap(uint glyphIndex, float xScale, float yScale, + out int width, out int height, out int xOffset, out int yOffset) + { + return GetGlyphBitmapSubpixel(glyphIndex, xScale, yScale, 0, 0, + out width, out height, out xOffset, out yOffset); + } + + public void MakeGlyphBitmapSubpixel(uint glyphIndex, + float xScale, float yScale, float xShift, float yShift, + FontBitmap bitmap) + { + if (bitmap.Buffer == null) { throw new ArgumentNullException("bitmap.Buffer"); } + stb_truetype.stbtt_MakeGlyphBitmapSubpixel(ref _info, + bitmap.StartPointer, bitmap.Width, bitmap.Height, bitmap.Stride, + xScale, yScale, xShift, yShift, glyphIndex); + } + + public void MakeGlyphBitmap(uint glyphIndex, float xScale, float yScale, FontBitmap bitmap) + { + MakeGlyphBitmapSubpixel(glyphIndex, xScale, yScale, 0, 0, bitmap); + } + + public void GetGlyphBitmapBoxSubpixel(uint glyphIndex, + float xScale, float yScale, float xShift, float yShift, + out int x0, out int y0, out int x1, out int y1) + { + stb_truetype.stbtt_GetGlyphBitmapBoxSubpixel(ref _info, glyphIndex, + xScale, yScale, xShift, yShift, out x0, out y0, out x1, out y1); + } + + public void GetGlyphBitmapBox(uint glyphIndex, float xScale, float yScale, + out int x0, out int y0, out int x1, out int y1) + { + GetGlyphBitmapBoxSubpixel(glyphIndex, xScale, yScale, 0, 0, out x0, out y0, out x1, out y1); + } + + public void GetGlyphBox(uint glyphIndex, + out int x0, out int y0, out int x1, out int y1) + { + stb_truetype.stbtt_GetGlyphBox(ref _info, glyphIndex, + out x0, out y0, out x1, out y1); + } + + public void GetGlyphHMetrics(uint glyphIndex, out int advanceWidth, out int leftSideBearing) + { + stb_truetype.stbtt_GetGlyphHMetrics(ref _info, glyphIndex, + out advanceWidth, out leftSideBearing); + } + + public GlyphVertex[] GetGlyphShape(uint glyphIndex) + { + FakePtr vertices; + int n = stb_truetype.stbtt_GetGlyphShape(ref _info, glyphIndex, out vertices); + return vertices.GetData(n); + } + + public int GetGlyphKernAdvance(uint glyph1Index, uint glyph2Index) + { + return stb_truetype.stbtt_GetGlyphKernAdvance(ref _info, glyph1Index, glyph2Index); + } + + public void GetFontVMetrics(out int lineAscender, out int lineDescender, out int lineGap) + { + stb_truetype.stbtt_GetFontVMetrics(ref _info, out lineAscender, out lineDescender, out lineGap); + } + + public void GetFontVMetricsAtScale(float pixelHeight, out float lineAscender, out float lineDescender, out float lineGap) + { + int lineAscenderI, lineDescenderI, lineGapI; + GetFontVMetrics(out lineAscenderI, out lineDescenderI, out lineGapI); + + float scale = GetScaleForPixelHeight(pixelHeight); + lineAscender = (float)lineAscenderI * scale; + lineDescender = (float)lineDescenderI * scale; + lineGap = (float)lineGapI * scale; + } + + public float GetScaleForPixelHeight(float pixelHeight) + { + return stb_truetype.stbtt_ScaleForPixelHeight(ref _info, pixelHeight); + } + } +} diff --git a/TrueTypeSharp/TrueTypeSharp.csproj b/TrueTypeSharp/TrueTypeSharp.csproj new file mode 100644 index 0000000..61c2d42 --- /dev/null +++ b/TrueTypeSharp/TrueTypeSharp.csproj @@ -0,0 +1,98 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {B722113F-1252-4BE1-9D43-6BC82B3E37D1} + Library + Properties + TrueTypeSharp + TrueTypeSharp + v2.0 + 512 + + + + + + + 3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/TrueTypeSharp/stb_truetype.cs b/TrueTypeSharp/stb_truetype.cs new file mode 100644 index 0000000..7779487 --- /dev/null +++ b/TrueTypeSharp/stb_truetype.cs @@ -0,0 +1,1479 @@ +#region License +/* TrueTypeSharp + Copyright (c) 2010, 2012 Illusory Studios LLC + + TrueTypeSharp is available at zer7.com. It is a C# port of Sean Barrett's + C library stb_truetype, which was placed in the public domain and is + available at nothings.org. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#endregion + +// ---------------------------------------------------------------------------- +// stb_truetype, ported to C# by James Bellinger Aug 2010, May 2012 +// This file kept rather similar to the original, to make merging fixes easier. +// To avoid exposing the greatness of FakePtr and the ugliness of my port, +// the public API is separate. +// ---------------------------------------------------------------------------- +#pragma warning disable 0660, 0661 +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace TrueTypeSharp +{ + static class stb_truetype + { +// stb_truetype.h - v0.5 - public domain - 2009 Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// +// VERSION HISTORY +// +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b(2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (STB) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Look at the header-file sections below for the API, but here's a quick skim: +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start, +// and you can cut and paste from it to move to more advanced) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to "glyphs" and operate on the glyphs; if +// you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.5, 1980 LOC) +// +// Documentation & header file 450 LOC \___ 550 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 590 LOC ---- 600 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +////////////////////////////////////////////////////////////////////////////// + +/* // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif*/ + + static int STBTT_ifloor(float x) { return (int)Math.Floor(x); } + static int STBTT_iceil(float x) { return (int)Math.Ceiling(x); } +/* + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) malloc(x) + #define STBTT_free(x,u) free(x) + #endif*/ + + class DelegateComparer : IComparer + { + Comparison _comparison; + + public DelegateComparer(Comparison comparison) + { + _comparison = comparison; + } + + public int Compare(T item1, T item2) + { + return _comparison(item1, item2); + } + } + static void STBTT_sort(ref FakePtr p, int n, Comparison comparer) + { + Array.Sort(p.Array, p.Offset, n, new DelegateComparer(comparer)); + } + + static void STBTT_memcpy(FakePtr target, FakePtr source, int count) + { + for (int i = 0; i < count; i ++) { target[i] = source[i]; } + } + + static void STBTT_memset(FakePtr target, int count) + { + for (int i = 0; i < count; i ++) { target[i] = default(T); } + } + + static T STBTT_malloc() where T : class, new() + { + try { return new T(); } + catch (OutOfMemoryException) { return null; } + } + static FakePtr STBTT_malloc(int count) + { + try { return new FakePtr() { Array = new T[count] }; } + catch (OutOfMemoryException) { return new FakePtr(); } + } + static void STBTT_free(FakePtr p) { } // no-op + + static void STBTT_assert(bool condition) { Debug.Assert(condition); } + static void STBTT_assert(int condition) { STBTT_assert(condition != 0); } + + /*#ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + */ + +public struct stbtt_fontinfo +{ + public FakePtr data; // pointer to .ttf file + public int fontstart; // offset of start of font + + public uint numGlyphs; // number of glyphs, needed for range checking + + public uint loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + public uint index_map; // a cmap mapping for our chosen character encoding + public int indexToLocFormat; // format needed to map from glyph index to glyph +} + +// @TODO: don't expose this structure +public struct stbtt__bitmap +{ + public int w,h,stride; + public FakePtr pixels; +} + enum STBTT_MACSTYLE { +STBTT_MACSTYLE_DONTCARE = 0, +STBTT_MACSTYLE_BOLD = 1, +STBTT_MACSTYLE_ITALIC = 2, +STBTT_MACSTYLE_UNDERSCORE = 4, +STBTT_MACSTYLE_NONE = 8 }; // <= not same as 0, this makes us check the bitfield is 0 + + enum STBTT_PLATFORM_ID { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +} + +enum STBTT_UNICODE_EID { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +} + +enum STBTT_MS_EID { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +} + +enum STBTT_MAC_EID { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +} + +enum STBTT_MS_LANG { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +} + +enum STBTT_MAC_LANG { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +} + + static byte ttBYTE(FakePtr p) { return p.Value; } + static sbyte ttCHAR(FakePtr p) { return (sbyte)p.Value; } + static int ttFixed(FakePtr p) { return ttLONG(p); } + + static ushort ttUSHORT(FakePtr p) { return (ushort)(p[0]*256 + p[1]); } + static short ttSHORT(FakePtr p) { return (short)(p[0]*256 + p[1]); } + static uint ttULONG(FakePtr p) { return ((uint)p[0]<<24) + ((uint)p[1]<<16) + ((uint)p[2]<<8) + p[3]; } + static int ttLONG(FakePtr p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + + static bool stbtt_tag4(FakePtr p, byte c0, byte c1, byte c2, byte c3) + { + return ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)); + } + + static bool stbtt_tag(FakePtr p, string str) + { + return stbtt_tag4(p, + (byte)(str.Length >= 1 ? str[0] : 0), + (byte)(str.Length >= 2 ? str[1] : 0), + (byte)(str.Length >= 3 ? str[2] : 0), + (byte)(str.Length >= 4 ? str[3] : 0)); + } + + static int stbtt__isfont(FakePtr font) + { + // check the version number + if (stbtt_tag4(font, (byte)'1', 0, 0, 0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; + } + + // @OPTIMIZE: binary search + static uint stbtt__find_table(FakePtr data, int fontstart, string tag) + { + int num_tables = ttUSHORT(data+fontstart+4); + int tabledir = fontstart + 12; + int i; + for (i=0; i < num_tables; ++i) { + int loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; + } + +public static int stbtt_GetFontOffsetForIndex(FakePtr font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection) != 0) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + int n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return (int)ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +public static int stbtt_InitFont(ref stbtt_fontinfo info, FakePtr data2, int fontstart) +{ + FakePtr data = (FakePtr ) data2; + uint cmap, t; + uint i,numTables; + + info.data = data; + info.fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info.loca = stbtt__find_table(data, fontstart, "loca"); // required + info.head = stbtt__find_table(data, fontstart, "head"); // required + info.glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info.hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info.hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info.kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (cmap == 0 || info.loca == 0 || info.head == 0 || info.glyf == 0|| info.hhea == 0 || info.hmtx == 0) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t != 0) + info.numGlyphs = ttUSHORT(data+t+4); + else + info.numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info.index_map = 0; + for (i=0; i < numTables; ++i) { + uint encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch((STBTT_PLATFORM_ID)ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID.STBTT_PLATFORM_ID_MICROSOFT: + switch ((STBTT_MS_EID)ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID.STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID.STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info.index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + } + } + if (info.index_map == 0) + return 0; + + info.indexToLocFormat = ttUSHORT(data+info.head + 50); + return 1; +} + +public static uint stbtt_FindGlyphIndex(ref stbtt_fontinfo info, uint unicode_codepoint) +{ + return stbtt_FindGlyphIndexOrNull(ref info, unicode_codepoint) ?? 0; +} + +public static uint? stbtt_FindGlyphIndexOrNull(ref stbtt_fontinfo info, uint unicode_codepoint) +{ + FakePtr data = info.data; + uint index_map = info.index_map; + + ushort format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + int bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return null; + } else if (format == 6) { + uint first = ttUSHORT(data + index_map + 6); + uint count = ttUSHORT(data + index_map + 8); + if ((uint) unicode_codepoint >= first && (uint) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + ((uint)unicode_codepoint - first)*2); + return null; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return null; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + ushort segcount = (ushort)(ttUSHORT(data+index_map+6) >> 1); + ushort searchRange = (ushort)(ttUSHORT(data+index_map+8) >> 1); + ushort entrySelector = ttUSHORT(data+index_map+10); + ushort rangeShift = (ushort)(ttUSHORT(data+index_map+12) >> 1); + ushort item, offset, start, end; + + // do a binary search of the segments + uint endCount = index_map + 14; + uint search = endCount; + + if (unicode_codepoint > 0xffff) + return null; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += (uint)rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector != 0) { + ushort start_, end_; + searchRange >>= 1; + start_ = ttUSHORT(data + search + 2 + segcount*2 + 2); + end_ = ttUSHORT(data + search + 2); + start_ = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end_ = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end_) + search += (uint)searchRange*2; + --entrySelector; + } + search += 2; + + item = (ushort) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return null; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (ushort)(unicode_codepoint + ttSHORT(data + index_map + 14 + segcount * 4 + 2 + 2 * item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } else if (format == 12 || format == 13) { + uint ngroups = ttULONG(data+index_map+12); + int low,high; + low = 0; high = (int)ngroups; + // Binary search the right group. + while (low < high) { + int mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + uint start_char = ttULONG(data+index_map+16+mid*12); + uint end_char = ttULONG(data+index_map+16+mid*12+4); + if (unicode_codepoint < start_char) + high = mid; + else if (unicode_codepoint > end_char) + low = mid+1; + else { + uint start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint - start_char; + else // format == 13 + return start_glyph; + } + } + return null; // not found + } + // @TODO + STBTT_assert(0); + return null; +} + +static void stbtt__setvertex(FakePtr v, GlyphVertexType type, int x, int y, int cx, int cy) +{ + var _ = v.Value; + _.Type = type; + _.X = (short)x; + _.Y = (short)y; + _.CX = (short)cx; + _.CY = (short)cy; + v.Value = _; +} + +static int stbtt__GetGlyfOffset(ref stbtt_fontinfo info, uint glyph_index) +{ + int g1,g2; + + if (glyph_index >= info.numGlyphs) return -1; // glyph index out of range + if (info.indexToLocFormat >= 2) return -1; // unknown index.glyph map format + + if (info.indexToLocFormat == 0) { + g1 = (int)(info.glyf + ttUSHORT(info.data + info.loca + glyph_index * 2) * 2); + g2 = (int)(info.glyf + ttUSHORT(info.data + info.loca + glyph_index * 2 + 2) * 2); + } else { + g1 = (int)(info.glyf + ttULONG (info.data + info.loca + glyph_index * 4)); + g2 = (int)(info.glyf + ttULONG (info.data + info.loca + glyph_index * 4 + 4)); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +public static int stbtt_GetGlyphBox(ref stbtt_fontinfo info, uint glyph_index, + out int x0, out int y0, out int x1, out int y1) +{ + int g = stbtt__GetGlyfOffset(ref info, glyph_index); + if (g < 0) { x0 = y0 = x1 = y1 = 0; return 0; } + + x0 = ttSHORT(info.data + g + 2); + y0 = ttSHORT(info.data + g + 4); + x1 = ttSHORT(info.data + g + 6); + y1 = ttSHORT(info.data + g + 8); + return 1; +} + +public static int stbtt_GetCodepointBox(ref stbtt_fontinfo info, uint codepoint, out int x0, out int y0, out int x1, out int y1) +{ + return stbtt_GetGlyphBox(ref info, stbtt_FindGlyphIndex(ref info, codepoint), out x0, out y0, out x1, out y1); +} + +static int stbtt__close_shape(FakePtr vertices, int num_vertices, int was_off, int start_off, + int sx, int sy, int scx, int scy, int cx, int cy) +{ + if (start_off != 0) + { + if (was_off != 0) + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Curve, (cx + scx) >> 1, (cy + scy) >> 1, cx, cy); + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Curve, sx, sy, scx, scy); + } + else + { + if (was_off != 0) + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Curve, sx, sy, cx, cy); + else + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Move, sx, sy, 0, 0); + } + return num_vertices; +} + +public static int stbtt_GetGlyphShape(ref stbtt_fontinfo info, uint glyph_index, out FakePtr pvertices) +{ + short numberOfContours; + FakePtr endPtsOfContours; + FakePtr data = info.data; + FakePtr vertices = new FakePtr(); + int num_vertices=0; + int g = stbtt__GetGlyfOffset(ref info, glyph_index); + + pvertices = new FakePtr(); + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + byte flags=0,flagcount; + int ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + int x,y,cx,cy,sx,sy,scx=0,scy=0; + FakePtr points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = STBTT_malloc(m); + if (vertices.IsNull) return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = (points++).Value; + if ((flags & 8) != 0) + flagcount = (points++).Value; + } else + --flagcount; + + var _ = vertices[off+i]; _.Type = (GlyphVertexType)flags; vertices[off+i] = _; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = (byte)vertices[off+i].Type; + if ((flags & 2) != 0) { + short dx = (points++).Value; + x += (short)((flags & 16) != 0 ? dx : -dx); // ??? + } else { + if ((flags & 16) == 0) { + x += (short)(points[0]*256 + points[1]); + points += 2; + } + } + var _ = vertices[off+i]; _.X = (short)x; vertices[off+i] = _; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = (byte)vertices[off+i].Type; + if ((flags & 4) != 0) { + short dy = (points++).Value; + y += (short)((flags & 32) != 0 ? dy : -dy); // ??? + } else { + if ((flags & 32) == 0) { + y += (short)(points[0]*256 + points[1]); + points += 2; + } + } + var _ = vertices[off+i]; _.Y = (short)y; vertices[off+i] = _; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = 0; + for (i=0; i < n; ++i) { + flags = (byte)vertices[off+i].Type; + x = (short) vertices[off+i].X; + y = (short) vertices[off+i].Y; + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = 0 == (flags & 1) ? 1 : 0; + if (start_off != 0) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (0 == ((int)vertices[off+i+1].Type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (int) vertices[off+i+1].X) >> 1; + sy = (y + (int) vertices[off+i+1].Y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (int) vertices[off+i+1].X; + sy = (int) vertices[off+i+1].Y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Move,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (0 == (flags & 1)) { // if it's a curve + if (was_off != 0) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Curve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off != 0) + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Curve, x,y, cx, cy); + else + stbtt__setvertex(vertices + num_vertices++, GlyphVertexType.Line, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + FakePtr comp = data + g + 10; + num_vertices = 0; + vertices.MakeNull(); + while (more != 0) { + ushort flags, gidx; + int comp_num_verts = 0, i; + FakePtr comp_verts = new FakePtr(), tmp = new FakePtr(); + float mtx0 = 1, mtx1 = 0, mtx2 = 0, mtx3 = 1, mtx4 = 0, mtx5 = 0, m, n; + + flags = ttUSHORT(comp); comp+=2; + gidx = ttUSHORT(comp); comp+=2; + + if (0 != (flags & 2)) { // XY values + if (0 != (flags & 1)) { // shorts + mtx4 = ttSHORT(comp); comp+=2; + mtx5 = ttSHORT(comp); comp+=2; + } else { + mtx4 = ttCHAR(comp); comp+=1; + mtx5 = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (0 != (flags & (1<<3))) { // WE_HAVE_A_SCALE + mtx0 = mtx3 = ttSHORT(comp)/16384.0f; comp+=2; + mtx1 = mtx2 = 0; + } else if (0 != (flags & (1<<6))) { // WE_HAVE_AN_X_AND_YSCALE + mtx0 = ttSHORT(comp)/16384.0f; comp+=2; + mtx1 = mtx2 = 0; + mtx3 = ttSHORT(comp)/16384.0f; comp+=2; + } else if (0 != (flags & (1<<7))) { // WE_HAVE_A_TWO_BY_TWO + mtx0 = ttSHORT(comp)/16384.0f; comp+=2; + mtx1 = ttSHORT(comp)/16384.0f; comp+=2; + mtx2 = ttSHORT(comp)/16384.0f; comp+=2; + mtx3 = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) Math.Sqrt(mtx0*mtx0 + mtx1*mtx1); + n = (float) Math.Sqrt(mtx2*mtx2 + mtx3*mtx3); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(ref info, gidx, out comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + FakePtr v = comp_verts + i; + short x,y; + + var _ = v.Value; + x=_.X; y=_.Y; + _.X = (short)(m * (mtx0*x + mtx2*y + mtx4)); + _.Y = (short)(n * (mtx1*x + mtx3*y + mtx5)); + x=_.CX; y=_.CY; + _.CX = (short)(m * (mtx0*x + mtx2*y + mtx4)); + _.CY = (short)(n * (mtx1*x + mtx3*y + mtx5)); + v.Value = _; + } + // Append vertices. + tmp = STBTT_malloc(num_vertices+comp_num_verts); + if (tmp.IsNull) { + if (!vertices.IsNull) STBTT_free(vertices); + if (!comp_verts.IsNull) STBTT_free(comp_verts); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts); + if (!vertices.IsNull) STBTT_free(vertices); + vertices = tmp; + STBTT_free(comp_verts); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + pvertices = vertices; + return num_vertices; +} + +public static void stbtt_GetGlyphHMetrics(ref stbtt_fontinfo info, uint glyph_index, + out int advanceWidth, out int leftSideBearing) +{ + ushort numOfLongHorMetrics = ttUSHORT(info.data+info.hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + advanceWidth = ttSHORT(info.data + info.hmtx + 4*glyph_index); + leftSideBearing = ttSHORT(info.data + info.hmtx + 4*glyph_index + 2); + } else { + advanceWidth = ttSHORT(info.data + info.hmtx + 4*(numOfLongHorMetrics-1)); + leftSideBearing = ttSHORT(info.data + info.hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +public static int stbtt_GetGlyphKernAdvance(ref stbtt_fontinfo info, uint glyph1, uint glyph2) +{ + FakePtr data = info.data + info.kern; + uint needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (0 == info.kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +public static int stbtt_GetCodepointKernAdvance(ref stbtt_fontinfo info, uint ch1, uint ch2) +{ + if (0 == info.kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(ref info, stbtt_FindGlyphIndex(ref info, ch1), stbtt_FindGlyphIndex(ref info, ch2)); +} + +public static void stbtt_GetCodepointHMetrics(ref stbtt_fontinfo info, uint codepoint, + out int advanceWidth, out int leftSideBearing) +{ + stbtt_GetGlyphHMetrics(ref info, stbtt_FindGlyphIndex(ref info, codepoint), out advanceWidth, out leftSideBearing); +} + +public static void stbtt_GetFontVMetrics(ref stbtt_fontinfo info, + out int ascent, out int descent, out int lineGap) +{ + ascent = ttSHORT(info.data+info.hhea + 4); + descent = ttSHORT(info.data+info.hhea + 6); + lineGap = ttSHORT(info.data+info.hhea + 8); +} + +public static float stbtt_ScaleForPixelHeight(ref stbtt_fontinfo info, float height) +{ + int fheight = ttSHORT(info.data + info.hhea + 4) - ttSHORT(info.data + info.hhea + 6); + return (float) height / fheight; +} + +public static void stbtt_FreeShape(ref stbtt_fontinfo info, FakePtr v) +{ + STBTT_free(v); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +public static void stbtt_GetGlyphBitmapBoxSubpixel(ref stbtt_fontinfo font, uint glyph, + float scale_x, float scale_y, float shift_x, float shift_y, + out int ix0, out int iy0, out int ix1, out int iy1) +{ + int x0, y0, x1, y1; + if (0 == stbtt_GetGlyphBox(ref font, glyph, out x0, out y0, out x1, out y1)) + x0=y0=x1=y1=0; // e.g. space character + // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? + ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + iy0 = -STBTT_iceil (y1 * scale_y + shift_y); + ix1 = STBTT_iceil (x1 * scale_x + shift_x); + iy1 = -STBTT_ifloor(y0 * scale_y + shift_y); +} + +public static void stbtt_GetGlyphBitmapBox(ref stbtt_fontinfo font, uint glyph, + float scale_x, float scale_y, + out int ix0, out int iy0, out int ix1, out int iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(ref font, glyph, scale_x, scale_y, 0, 0, out ix0, out iy0, out ix1, out iy1); +} + +public static void stbtt_GetCodepointBitmapBoxSubpixel(ref stbtt_fontinfo font, uint codepoint, + float scale_x, float scale_y, float shift_x, float shift_y, + out int ix0, out int iy0, out int ix1, out int iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(ref font, stbtt_FindGlyphIndex(ref font, codepoint), + scale_x, scale_y, shift_x, shift_y, out ix0, out iy0, out ix1, out iy1); +} + +public static void stbtt_GetCodepointBitmapBox(ref stbtt_fontinfo font, uint codepoint, + float scale_x, float scale_y, out int ix0, out int iy0, out int ix1, out int iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(ref font, codepoint, scale_x, scale_y, 0, 0, out ix0, out iy0, out ix1, out iy1); +} + +struct stbtt__edge { + public float x0,y0, x1,y1; + public int invert; +} + +class stbtt__active_edge +{ + public int x,dx; + public float ey; + public stbtt__active_edge next; + public int valid; +} + +const int FIXSHIFT = 10; +const int FIX = (1 << FIXSHIFT); +const int FIXMASK = (FIX-1); + +static stbtt__active_edge new_active(stbtt__edge e, int off_x, float start_point) +{ + stbtt__active_edge z = STBTT_malloc(); // @TODO: make a pool of these!!! + float dxdy = (e.x1 - e.x0) / (e.y1 - e.y0); + STBTT_assert(e.y0 <= start_point); + if (null == z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z.dx = -STBTT_ifloor(FIX * -dxdy); + else + z.dx = STBTT_ifloor(FIX * dxdy); + z.x = STBTT_ifloor(FIX * (e.x0 + dxdy * (start_point - e.y0))); + z.x -= off_x * FIX; + z.ey = e.y1; + z.next = null; + z.valid = e.invert != 0 ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(FakePtr scanline, int len, stbtt__active_edge e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e != null) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e.x; w += e.valid; + } else { + int x1 = e.x; w += e.valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (byte)(scanline[i] + (byte) ((x1 - x0) * max_weight >> FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (byte)(scanline[i] + (byte) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (byte)(scanline[j] + (byte) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (byte)(scanline[i] + (byte) max_weight); + } + } + } + } + + e = e.next; + } +} + +static void stbtt__rasterize_sorted_edges(ref stbtt__bitmap result, FakePtr e, int n, int vsubsample, int off_x, int off_y) +{ + stbtt__active_edge activeIsNext = new stbtt__active_edge(); // jfb: If only I could do pointers to stack...*sigh* Oh C#, why so arbitrarily limited... + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + FakePtr scanline_data = STBTT_malloc(512), scanline; + + if (result.w > 512) + scanline = STBTT_malloc(result.w); + else + scanline = scanline_data; + + y = off_y * vsubsample; + { var _ = e[n]; _.y0 = (off_y + result.h) * (float) vsubsample + 1; e[n] = _; } + + while (j < result.h) { + STBTT_memset(scanline, result.w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge stepIsNext = activeIsNext; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (stepIsNext.next != null) { + stbtt__active_edge z = stepIsNext.next; + if (z.ey <= scan_y) { + stepIsNext.next = z.next; // delete from list + STBTT_assert(z.valid); + z.valid = 0; + //STBTT_free(z); + } else { + z.x += z.dx; // advance to position for current scanline + stepIsNext = stepIsNext.next; // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + stepIsNext = activeIsNext; + while (stepIsNext.next != null && stepIsNext.next.next != null) { + if (stepIsNext.next.x > stepIsNext.next.next.x) { + stbtt__active_edge t = stepIsNext.next; + stbtt__active_edge q = t.next; + + t.next = q.next; + q.next = t; + stepIsNext.next = q; + changed = 1; + } + stepIsNext = stepIsNext.next; + } + if (0 == changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e[0].y0 <= scan_y) { + if (e[0].y1 > scan_y) { + stbtt__active_edge z = new_active(e[0], off_x, scan_y); + // find insertion point + if (activeIsNext.next == null) + activeIsNext.next = z; + else if (z.x < activeIsNext.next.x) { + // insert at front + z.next = activeIsNext.next; + activeIsNext.next = z; + } else { + // find thing to insert AFTER + stbtt__active_edge p = activeIsNext.next; + while (p.next != null && p.next.x < z.x) + p = p.next; + // at this point, p->next->x is NOT < z->x + z.next = p.next; + p.next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (activeIsNext.next != null) + stbtt__fill_active_edges(scanline, result.w, activeIsNext.next, max_weight); + + ++y; + } + STBTT_memcpy(result.pixels + j * result.stride, scanline, result.w); + ++j; + } + + //while (active != null) { + // stbtt__active_edge z = active; + // active = active.next; + // STBTT_free(z); + //} + + if (scanline != scanline_data) + STBTT_free(scanline); +} + +static int stbtt__edge_compare(stbtt__edge a, stbtt__edge b) +{ + if (a.y0 < b.y0) return -1; + if (a.y0 > b.y0) return 1; + return 0; +} + +static void stbtt__rasterize(ref stbtt__bitmap result, FakePtr pts, + FakePtr wcount, int windings, float scale_x, float scale_y, + float shift_x, float shift_y, int off_x, int off_y, int invert) +{ + float y_scale_inv = invert != 0 ? -scale_y : scale_y; + FakePtr e; + int n,i,j,k,m; + int vsubsample = result.h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = STBTT_malloc(n+1); // add an extra one as a sentinel + if (e.IsNull) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + FakePtr p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].Y == p[k].Y) + continue; + // add edge from j to k to the list + { + var _ = e[n]; + _.invert = 0; + if (invert != 0 ? p[j].Y > p[k].Y : p[j].Y < p[k].Y) { + _.invert = 1; + a=j; b=k; + } + _.x0 = p[a].X * scale_x + shift_x; + _.y0 = p[a].Y * y_scale_inv * vsubsample + shift_y; + _.x1 = p[b].X * scale_x + shift_x; + _.y1 = p[b].Y * y_scale_inv * vsubsample + shift_y; + e[n] = _; + } + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(ref e, n, stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(ref result, e, n, vsubsample, off_x, off_y); + + STBTT_free(e); +} + +static void stbtt__add_point(FakePtr points, int n, float x, float y) +{ + if (points.IsNull) return; // during first pass, it's unallocated + ContourPoint p; p.X = x; p.Y = y; points[n] = p; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(FakePtr points, ref int num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, ref num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, ref num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, num_points,x2,y2); + num_points = num_points+1; + } + return 1; +} + +// returns number of contours +public static FakePtr stbtt_FlattenCurves(FakePtr vertices, int num_verts, + float objspace_flatness, out FakePtr contour_lengths, out int num_contours) +{ + FakePtr points = new FakePtr(); + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].Type == GlyphVertexType.Move) + ++n; + + num_contours = n; + if (n == 0) { contour_lengths = new FakePtr(); return new FakePtr(); } + + contour_lengths = STBTT_malloc(n); + + if (contour_lengths.IsNull) { + num_contours = 0; + return new FakePtr(); + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = STBTT_malloc(num_points); + if (points.IsNull) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].Type) { + case GlyphVertexType.Move: + // start the next contour + if (n >= 0) + contour_lengths[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].X; y = vertices[i].Y; + stbtt__add_point(points, num_points++, x,y); + break; + case GlyphVertexType.Line: + x = vertices[i].X; y = vertices[i].Y; + stbtt__add_point(points, num_points++, x, y); + break; + case GlyphVertexType.Curve: + stbtt__tesselate_curve(points, ref num_points, x,y, + vertices[i].CX, vertices[i].CY, + vertices[i].X, vertices[i].Y, + objspace_flatness_squared, 0); + x = vertices[i].X; y = vertices[i].Y; + break; + } + } + contour_lengths[n] = num_points - start; + } + + return points; +error: + STBTT_free(points); + STBTT_free(contour_lengths); + contour_lengths.MakeNull(); + num_contours = 0; + return new FakePtr(); +} + +public static void stbtt_Rasterize(ref stbtt__bitmap result, float flatness_in_pixels, + FakePtr vertices, int num_verts, + float scale_x, float scale_y, float shift_x, float shift_y, + int x_off, int y_off, int invert) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count; FakePtr winding_lengths; + FakePtr windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, out winding_lengths, out winding_count); + if (!windings.IsNull) { + stbtt__rasterize(ref result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert); + STBTT_free(winding_lengths); + STBTT_free(windings); + } +} + +public static void stbtt_FreeBitmap(FakePtr bitmap) +{ + STBTT_free(bitmap); +} + +public static FakePtr stbtt_GetGlyphBitmapSubpixel(ref stbtt_fontinfo info, + float scale_x, float scale_y, float shift_x, float shift_y, + uint glyph, out int width, out int height, out int xoff, out int yoff) +{ + int ix0=0,iy0=0,ix1=0,iy1=0; + stbtt__bitmap gbm; + FakePtr vertices; + int num_verts = stbtt_GetGlyphShape(ref info, glyph, out vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { width = 0; height = 0; xoff = 0; yoff = 0; return new FakePtr(); } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBox(ref info, glyph, scale_x, scale_y, out ix0,out iy0,out ix1,out iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = new FakePtr(); // in case we error + + width = gbm.w; + height = gbm.h; + xoff = ix0; + yoff = iy0; + + if (gbm.w > 0 && gbm.h > 0) { + gbm.pixels = STBTT_malloc(gbm.w * gbm.h); + if (!gbm.pixels.IsNull) { + gbm.stride = gbm.w; + + stbtt_Rasterize(ref gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1); + } + } + STBTT_free(vertices); + return gbm.pixels; +} + +public static FakePtr stbtt_GetGlyphBitmap(ref stbtt_fontinfo info, + float scale_x, float scale_y, + uint glyph, out int width, out int height, out int xoff, out int yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(ref info, scale_x, scale_y, 0, 0, glyph, + out width, out height, out xoff, out yoff); +} + +public static void stbtt_MakeGlyphBitmapSubpixel(ref stbtt_fontinfo info, FakePtr output, + int out_w, int out_h, int out_stride, + float scale_x, float scale_y, float shift_x, float shift_y, uint glyph) +{ + int ix0=0,iy0=0; + FakePtr vertices; + int num_verts = stbtt_GetGlyphShape(ref info, glyph, out vertices); + stbtt__bitmap gbm; + + int dontCare1, dontCare2; + stbtt_GetGlyphBitmapBox(ref info, glyph, scale_x, scale_y, out ix0,out iy0, out dontCare1, out dontCare2); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w > 0 && gbm.h > 0) + stbtt_Rasterize(ref gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1); + + STBTT_free(vertices); +} + +public static void stbtt_MakeGlyphBitmap(ref stbtt_fontinfo info, FakePtr output, + int out_w, int out_h, int out_stride, float scale_x, float scale_y, uint glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(ref info, output, out_w, out_h, out_stride, + scale_x, scale_y, 0, 0, glyph); +} + +public static FakePtr stbtt_GetCodepointBitmapSubpixel(ref stbtt_fontinfo info, + float scale_x, float scale_y, float shift_x, float shift_y, uint codepoint, + out int width, out int height, out int xoff, out int yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(ref info, scale_x, scale_y,shift_x,shift_y, + stbtt_FindGlyphIndex(ref info,codepoint), out width, out height, out xoff, out yoff); +} + +public static void stbtt_MakeCodepointBitmapSubpixel(ref stbtt_fontinfo info, + FakePtr output, int out_w, int out_h, int out_stride, + float scale_x, float scale_y, float shift_x, float shift_y, uint codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(ref info, output, out_w, out_h, out_stride, + scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(ref info, codepoint)); +} + +public static FakePtr stbtt_GetCodepointBitmap(ref stbtt_fontinfo info, + float scale_x, float scale_y, uint codepoint, + out int width, out int height, out int xoff, out int yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(ref info, scale_x, scale_y, 0, 0, codepoint, + out width, out height, out xoff, out yoff); +} + +public static void stbtt_MakeCodepointBitmap(ref stbtt_fontinfo info, + FakePtr output, int out_w, int out_h, int out_stride, + float scale_x, float scale_y, uint codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(ref info, output, out_w, out_h, out_stride, + scale_x, scale_y, 0, 0, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-SHITTY packing to keep source code small + +public static int stbtt_BakeFontBitmap(ref stbtt_fontinfo f, + float xScale, float yScale, // height of font in pixels + FakePtr pixels, int pw, int ph, int pstride, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + FakePtr chardata) +{ + int x,y,bottom_y, i; + STBTT_memset(pixels, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + for (i=0; i < num_chars; ++i) { + uint? g = stbtt_FindGlyphIndexOrNull(ref f, (uint)(first_char + i)); + if (g == null) { chardata[i] = new BakedChar() { X0 = (ushort)x, X1 = (ushort)x, Y0 = (ushort)y, Y1 = (ushort)y }; continue; } + + int advance = 0, lsb = 0, x0 = 0, y0 = 0, x1 = 0, y1 = 0, gw, gh; + stbtt_GetGlyphHMetrics(ref f, (uint)g, out advance, out lsb); + stbtt_GetGlyphBitmapBox(ref f, (uint)g, xScale, yScale, out x0, out y0, out x1, out y1); + gw = x1 - x0; + gh = y1 - y0; + if (x + gw + 1 >= pw) + { y = bottom_y; x = 1; } // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x + gw < pw); + STBTT_assert(y + gh < ph); + stbtt_MakeGlyphBitmap(ref f, pixels + x + y * pw, gw, gh, pstride, xScale, yScale, (uint)g); + + { + BakedChar _; + _.X0 = (ushort) x; + _.Y0 = (ushort) y; + _.X1 = (ushort) (x + gw); + _.Y1 = (ushort) (y + gh); + _.XAdvance = xScale * advance; + _.XOffset = (float) x0; + _.YOffset = (float) y0; + chardata[i] = _; + } + + x = x + gw + 2; + if (y + gh + 2 > bottom_y) + bottom_y = y + gh + 2; + } + return bottom_y; +} + +public static void stbtt_GetBakedQuad(ref BakedChar b, int pw, int ph, + ref float xpos, ref float ypos, out BakedQuad q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule != 0 ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + + int round_x = STBTT_ifloor((xpos + b.XOffset) + 0.5f); + int round_y = STBTT_ifloor((ypos + b.YOffset) + 0.5f); + + q.X0 = round_x + d3d_bias; + q.Y0 = round_y + d3d_bias; + q.X1 = round_x + b.X1 - b.X0 + d3d_bias; + q.Y1 = round_y + b.Y1 - b.Y0 + d3d_bias; + + q.S0 = b.X0 * ipw; + q.T0 = b.Y0 * iph; + q.S1 = b.X1 * ipw; + q.T1 = b.Y1 * iph; + + xpos += b.XAdvance; +} + } +} \ No newline at end of file diff --git a/TrueTypeSharp/stb_truetype.h b/TrueTypeSharp/stb_truetype.h new file mode 100644 index 0000000..d8025d1 --- /dev/null +++ b/TrueTypeSharp/stb_truetype.h @@ -0,0 +1,1958 @@ +// stb_truetype.h - v0.5 - public domain - 2009 Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// +// VERSION HISTORY +// +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b(2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (STB) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Look at the header-file sections below for the API, but here's a quick skim: +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start, +// and you can cut and paste from it to move to more advanced) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to "glyphs" and operate on the glyphs; if +// you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.5, 1980 LOC) +// +// Documentation & header file 450 LOC \___ 550 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 590 LOC ---- 600 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLstbtt_uint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl,0=old d3d + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=0; + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to do this, + // you need to render the bitmap to a temp buffer, then "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH RUNTIME LIBRARIES +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) malloc(x) + #define STBTT_free(x,u) free(x) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// It's inefficient; you might want to c&p it and optimize it. + + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf file may have more than one font. Each has a sequential index +// number starting from 0. Call this function to get the font offset for a +// given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc. +typedef struct +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are a pure +// cache with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates + +extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 +// @TODO; for now always returns 0! + +extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates + +extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +extern void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + stbtt_uint16 item, offset, start, end; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 start, end; + searchRange >>= 1; + start = ttUSHORT(data + search + 2 + segcount*2 + 2); + end = ttUSHORT(data + search + 2); + start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + stbtt_uint16 g = 0; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int16 x, stbtt_int16 y, stbtt_int16 cx, stbtt_int16 cy) +{ + v->type = type; + v->x = x; + v->y = y; + v->cx = cx; + v->cy = cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0, curve_end=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) + x0=y0=x1=y1=0; // e.g. space character + // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + if (iy0) *iy0 = -STBTT_iceil (y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil (x1 * scale_x + shift_x); + if (iy1) *iy1 = -STBTT_ifloor(y0 * scale_y + shift_y); +} +void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = p[a].y * y_scale_inv * vsubsample + shift_y; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = p[b].y * y_scale_inv * vsubsample + shift_y; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBox(info, glyph, scale_x, scale_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + stbtt_InitFont(&f, data, offset); + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 2; + if (y+gh+2 > bottom_y) + bottom_y = y+gh+2; + } + return bottom_y; +} + +void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8), off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + stbtt_int32 slen = ttUSHORT(fc+loc+12+8), off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION