├── .gitignore ├── Fonts ├── Apache License.txt ├── OpenSans-Regular.ttf ├── SIL Open Font License.txt └── SourceSansPro-Regular.otf ├── GpuExample ├── GpuExample.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TextBuffer.cs ├── TextureAtlas.cs └── Window.cs ├── LICENSE ├── README.md ├── SharpFont.sln ├── SharpFont ├── FontCollection.cs ├── FontFace.cs ├── Internal │ ├── BinPacker.cs │ ├── CharacterMap.cs │ ├── DataReader.cs │ ├── Geometry.cs │ ├── Interpreter.cs │ ├── KerningTable.cs │ ├── Renderer.cs │ ├── SbitTable.cs │ └── SfntTables.cs ├── InvalidFontException.cs ├── Metrics.cs ├── Properties │ └── AssemblyInfo.cs ├── SharpFont.csproj ├── TextAnalyzer.cs ├── TextFormat.cs └── TextLayout.cs ├── Test ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── Test.csproj └── external ├── System.Numerics.Vectors ├── System.Numerics.Vectors.dll ├── System.Numerics.Vectors.pdb └── System.Numerics.Vectors.xml └── bgfx ├── LICENSE ├── SharpBgfx.cs ├── bgfx.dll └── bgfx_debug.dll /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | *.obj 3 | *.user 4 | *.aps 5 | *.pch 6 | *.vspscc 7 | *_i.c 8 | *_p.c 9 | *.ncb 10 | *.suo 11 | *.sln.docstates 12 | *.tlb 13 | *.tlh 14 | *.bak 15 | *.cache 16 | *.ilk 17 | *.log 18 | [Bb]in 19 | [Dd]ebug*/ 20 | *.lib 21 | *.sbr 22 | *.opensdf 23 | *.sdf 24 | *.vsp 25 | *.sublime-workspace 26 | obj/ 27 | ipch/ 28 | [Rr]elease*/ 29 | _ReSharper*/ 30 | [Tt]est[Rr]esult* 31 | *.sln.ide/ 32 | .vs/ 33 | *.vspx -------------------------------------------------------------------------------- /Fonts/Apache License.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /Fonts/SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 5 | 6 | ----------------------------------------------------------- 7 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 8 | ----------------------------------------------------------- 9 | 10 | PREAMBLE 11 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 12 | 13 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 14 | 15 | DEFINITIONS 16 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 17 | 18 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 19 | 20 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 21 | 22 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 23 | 24 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 25 | 26 | PERMISSION & CONDITIONS 27 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 28 | 29 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 30 | 31 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 32 | 33 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 34 | 35 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 36 | 37 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 38 | 39 | TERMINATION 40 | This license becomes null and void if any of the above conditions are not met. 41 | 42 | DISCLAIMER 43 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /Fonts/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/Fonts/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /GpuExample/GpuExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA} 8 | WinExe 9 | Properties 10 | GpuExample 11 | GpuExample 12 | v4.6 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | true 27 | 3021 28 | true 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | false 39 | true 40 | 3021 41 | 42 | 43 | 44 | 45 | 46 | 47 | False 48 | ..\external\System.Numerics.Vectors\System.Numerics.Vectors.dll 49 | 50 | 51 | 52 | 53 | 54 | SharpBgfx.cs 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | bgfx.dll 65 | PreserveNewest 66 | 67 | 68 | bgfx_debug.dll 69 | PreserveNewest 70 | 71 | 72 | 73 | 74 | {8492da27-3077-43a2-80b0-d51e7da83bd2} 75 | SharpFont 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /GpuExample/Program.cs: -------------------------------------------------------------------------------- 1 | using SharpBgfx; 2 | using SharpFont; 3 | using System.IO; 4 | using System.Numerics; 5 | 6 | namespace GpuExample { 7 | static class EntryPoint { 8 | static void Main () { 9 | // create a platform window and kick off a separate render thread 10 | var window = new Window("Text Rendering Example", 1280, 720); 11 | window.Run(RenderThread); 12 | } 13 | 14 | unsafe static void RenderThread (Window window) { 15 | // initialize the renderer 16 | Bgfx.Init(); 17 | Bgfx.Reset(window.Width, window.Height, ResetFlags.Vsync); 18 | Bgfx.SetDebugFeatures(DebugFeatures.DisplayText); 19 | Bgfx.SetViewClear(0, ClearTargets.Color | ClearTargets.Depth, unchecked((int)0xffffffff)); 20 | 21 | var fontProgram = new Program( 22 | new Shader(MemoryBlock.FromArray(Shaders.FontVS)), 23 | new Shader(MemoryBlock.FromArray(Shaders.FontFS)), 24 | destroyShaders: true 25 | ); 26 | 27 | var u_texColor = new Uniform("u_texColor", UniformType.Int1); 28 | var atlas = new TextureAtlas(4096); 29 | 30 | var font = FontCollection.SystemFonts.Load("Verdana"); 31 | var analyzer = new TextAnalyzer(atlas); 32 | var buffer = new TextBuffer(12800); 33 | //buffer.Append(analyzer, font, "m"); 34 | //buffer.Append(analyzer, font, "Hello, World! (¼)"); 35 | 36 | 37 | buffer.Append(analyzer, font, 38 | @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus congue vitae augue sit amet laoreet. Etiam eros ligula, vestibulum non nisi a, convallis sodales odio. Integer dapibus ipsum eros, sit amet euismod ligula fringilla quis. Pellentesque placerat, dui vitae venenatis aliquam, augue eros porttitor erat, sit amet imperdiet dolor nulla nec justo. Etiam at elit vel diam consectetur venenatis vel et eros. In leo ante, vestibulum eu volutpat a, facilisis et justo. Sed semper arcu id lectus faucibus, ac pretium nunc sagittis. Praesent faucibus eu nisl non lacinia. Suspendisse suscipit vulputate velit, non sodales augue. In ante nulla, tempus vitae nisi tincidunt, dignissim venenatis elit. Phasellus fermentum turpis sed sapien dapibus, quis varius leo mattis. Nam nisl nibh, eleifend in maximus ac, ultricies ut eros. 39 | 40 | Phasellus auctor magna erat, iaculis interdum purus porta vulputate. Etiam vel neque at justo dictum tincidunt. Etiam enim nibh, dapibus accumsan blandit suscipit, dignissim vitae quam. Duis at metus et nulla posuere malesuada ut at urna. Quisque eget arcu venenatis, gravida tellus volutpat, vehicula tellus. Pellentesque rutrum purus vel ante sodales sollicitudin. Phasellus ut elit blandit, maximus ipsum ut, varius nisi. 41 | 42 | Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque volutpat in lectus sit amet pharetra. In mi justo, maximus sit amet viverra nec, posuere vel leo. Phasellus in justo quis ex semper suscipit. Pellentesque ut ornare purus.Donec et pretium metus. Integer dapibus est a nibh convallis pulvinar. Mauris metus diam, congue eget arcu volutpat, varius vehicula sapien. Donec posuere, massa et fringilla tincidunt, lacus ante fermentum sapien, ut dictum tellus erat id quam.Donec tempus urna velit, ac congue nibh egestas luctus. Praesent vel leo at enim imperdiet maximus non nec massa. 43 | 44 | Pellentesque auctor ultricies accumsan. Vestibulum ipsum mi, tincidunt ut lacinia et, lobortis in lacus. Donec eu velit imperdiet, viverra mi ut, congue tortor. Vestibulum ac dui pretium, interdum tortor a, varius nulla. Nunc consequat neque sed sodales sagittis. Nullam condimentum metus sit amet sapien elementum, a pulvinar eros cursus. Sed eget elit tellus. Fusce ac pellentesque orci. Nullam sagittis malesuada elit, sed dignissim nibh sollicitudin malesuada. Mauris ac hendrerit enim. Aliquam ex urna, sagittis varius ex eu, commodo consequat tortor. 45 | 46 | Praesent varius mauris sed lacus congue sollicitudin. Nulla lectus nunc, euismod id nibh quis, sollicitudin imperdiet neque. Maecenas suscipit quam sit amet venenatis cursus. Donec lacinia interdum rutrum. Phasellus quis odio justo. Duis sed justo quis quam rutrum consequat. Praesent et suscipit magna, eu condimentum mauris.Mauris in ligula odio. Integer tincidunt nisi et ipsum efficitur rutrum."); 47 | 48 | Bgfx.SetViewTransform(0, Matrix4x4.Identity, Matrix4x4.CreateOrthographicOffCenter(0, 1280, 720, 0, -1.0f, 1.0f)); 49 | 50 | // main loop 51 | while (window.ProcessEvents(ResetFlags.Vsync)) { 52 | Bgfx.SetViewRect(0, 0, 0, window.Width, window.Height); 53 | 54 | Bgfx.SetTexture(0, u_texColor, atlas.Texture); 55 | Bgfx.SetProgram(fontProgram); 56 | buffer.Submit(); 57 | 58 | Bgfx.Frame(); 59 | } 60 | 61 | // cleanup 62 | Bgfx.Shutdown(); 63 | } 64 | } 65 | 66 | struct PosColorTexture { 67 | public Vector2 Position; 68 | public Vector2 TexCoords; 69 | public int Color; 70 | 71 | public PosColorTexture (Vector2 position, Vector2 texcoords, int color) { 72 | Position = position; 73 | TexCoords = texcoords; 74 | Color = color; 75 | } 76 | 77 | public static readonly VertexLayout Layout = new VertexLayout() 78 | .Begin() 79 | .Add(VertexAttributeUsage.Position, 2, VertexAttributeType.Float) 80 | .Add(VertexAttributeUsage.TexCoord0, 2, VertexAttributeType.Float) 81 | .Add(VertexAttributeUsage.Color0, 4, VertexAttributeType.UInt8, normalized: true) 82 | .End(); 83 | } 84 | 85 | static class Shaders { 86 | public static readonly byte[] FontVS = { 87 | 0x56, 0x53, 0x48, 0x04, 0x01, 0x83, 0xf2, 0xe1, 0x01, 0x00, 0x0f, 0x75, 0x5f, 0x6d, 0x6f, 0x64, // VSH........u_mod 88 | 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x50, 0x72, 0x6f, 0x6a, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, // elViewProj...... 89 | 0xd8, 0x02, 0x44, 0x58, 0x42, 0x43, 0x03, 0x3f, 0x85, 0xee, 0x20, 0x01, 0x00, 0xea, 0x08, 0x06, // ..DXBC.?.. ..... 90 | 0xb6, 0x7d, 0x8d, 0xf2, 0x28, 0xef, 0x01, 0x00, 0x00, 0x00, 0xd8, 0x02, 0x00, 0x00, 0x03, 0x00, // .}..(........... 91 | 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x49, 0x53, // ..,...........IS 92 | 0x47, 0x4e, 0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, // GNh...........P. 93 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 94 | 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ......V......... 95 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x5f, 0x00, // .............._. 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, // ................ 97 | 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x00, 0x50, 0x4f, 0x53, 0x49, // ......COLOR.POSI 98 | 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x4f, 0x53, // TION.TEXCOORD.OS 99 | 0x47, 0x4e, 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, // GNl...........P. 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 101 | 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 102 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x62, 0x00, // ..............b. 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, // ................ 104 | 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, // ......SV_POSITIO 105 | 0x4e, 0x00, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, // N.COLOR.TEXCOORD 106 | 0x00, 0xab, 0x53, 0x48, 0x44, 0x52, 0xc0, 0x01, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x70, 0x00, // ..SHDR....@...p. 107 | 0x00, 0x00, 0x59, 0x00, 0x00, 0x04, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, // ..Y...F. ....... 108 | 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0xf2, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, // .._..........._. 109 | 0x00, 0x03, 0x32, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0x32, 0x10, // ..2......._...2. 110 | 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, // ......g.... .... 111 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xf2, 0x20, 0x10, 0x00, 0x01, 0x00, // ......e.... .... 112 | 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0x32, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x68, 0x00, // ..e...2 ......h. 113 | 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, // ......8......... 114 | 0x00, 0x00, 0x06, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, // ..........F. ... 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, // ......8......... 116 | 0x00, 0x00, 0x56, 0x15, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, // ..V.......F. ... 117 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, // ................ 118 | 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, // ..F.......F..... 119 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x0b, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, // ..8...........F. 120 | 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, // ..........@.... 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 122 | 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, // ..........F..... 123 | 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x0b, 0xf2, 0x00, // ..F.......8..... 124 | 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, // ......F. ....... 125 | 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, // ...@.....?...?.. 126 | 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, // .?...?.......... 127 | 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, // ..F.......F..... 128 | 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x10, // ..6...2.......F. 129 | 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0xf2, 0x00, 0x10, 0x00, 0x02, 0x00, // ......6......... 130 | 0x00, 0x00, 0x46, 0x1e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0xf2, 0x20, // ..F.......6.... 131 | 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, // ......F.......6. 132 | 0x00, 0x05, 0xf2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x02, 0x00, // ... ......F..... 133 | 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x00, // ..6...2 ......F. 134 | 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x03, 0x05, 0x00, 0x01, 0x00, // ......>......... 135 | 0x10, 0x00, 0x40, 0x00, // ..@. 136 | }; 137 | 138 | public static readonly byte[] FontFS = { 139 | 0x46, 0x53, 0x48, 0x04, 0x01, 0x83, 0xf2, 0xe1, 0x00, 0x00, 0xac, 0x01, 0x44, 0x58, 0x42, 0x43, // FSH.........DXBC 140 | 0xa4, 0x23, 0x5a, 0xc4, 0xcc, 0x18, 0x6f, 0x23, 0xe6, 0x28, 0xa6, 0x2c, 0x8d, 0x0d, 0xd5, 0x26, // .#Z...o#.(.,...& 141 | 0x01, 0x00, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, // ............,... 142 | 0xa0, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x4e, 0x6c, 0x00, 0x00, 0x00, // ........ISGNl... 143 | 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........P....... 144 | 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, // ................ 145 | 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // ................ 146 | 0x01, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........b....... 147 | 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, // ................ 148 | 0x53, 0x56, 0x5f, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x43, 0x4f, 0x4c, 0x4f, // SV_POSITION.COLO 149 | 0x52, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0xab, 0x4f, 0x53, 0x47, 0x4e, // R.TEXCOORD..OSGN 150 | 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // ,........... ... 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 152 | 0x0f, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x00, 0xab, 0xab, // ....SV_TARGET... 153 | 0x53, 0x48, 0x44, 0x52, 0xd0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, // SHDR....@...4... 154 | 0x5a, 0x00, 0x00, 0x03, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x18, 0x00, 0x04, // Z....`......X... 155 | 0x00, 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x62, 0x10, 0x00, 0x03, // .p......UU..b... 156 | 0xf2, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x62, 0x10, 0x00, 0x03, 0x32, 0x10, 0x10, 0x00, // ........b...2... 157 | 0x02, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // ....e.... ...... 158 | 0x68, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x09, 0xf2, 0x00, 0x10, 0x00, // h.......E....... 159 | 0x00, 0x00, 0x00, 0x00, 0x46, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, 0x00, // ....F.......F~.. 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, // .....`......6... 161 | 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 162 | 0x36, 0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, // 6............... 163 | 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x82, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // ....8.... ...... 164 | 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, // ........:....... 165 | 0x36, 0x00, 0x00, 0x05, 0x72, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x12, 0x10, 0x00, // 6...r ......F... 166 | 0x01, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // ....>....... 167 | }; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /GpuExample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("GpuExample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("GpuExample")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5203b9cc-ae0c-4da2-be63-e61f7dc71dea")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /GpuExample/TextBuffer.cs: -------------------------------------------------------------------------------- 1 | using SharpBgfx; 2 | using SharpFont; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Numerics; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace GpuExample { 12 | unsafe class TextBuffer { 13 | IndexBuffer indexBuffer; 14 | DynamicVertexBuffer vertexBuffer; 15 | int count; 16 | 17 | public TextBuffer (int capacity) { 18 | var indexMem = new MemoryBlock(sizeof(ushort) * capacity * 6); 19 | var indices = (ushort*)indexMem.Data; 20 | for (int i = 0, v = 0; i < capacity; i++, v += 4) { 21 | *indices++ = (ushort)(v + 0); 22 | *indices++ = (ushort)(v + 1); 23 | *indices++ = (ushort)(v + 2); 24 | *indices++ = (ushort)(v + 2); 25 | *indices++ = (ushort)(v + 3); 26 | *indices++ = (ushort)(v + 0); 27 | } 28 | 29 | indexBuffer = new IndexBuffer(indexMem); 30 | } 31 | 32 | public unsafe void Append (TextAnalyzer analyzer, FontFace font, string text) { 33 | var layout = new TextLayout(); 34 | var format = new TextFormat { 35 | Font = font, 36 | Size = 8.0f 37 | }; 38 | 39 | analyzer.AppendText(text, format); 40 | analyzer.PerformLayout(32, 64, 1000, 1000, layout); 41 | 42 | var memBlock = new MemoryBlock(text.Length * 6 * PosColorTexture.Layout.Stride); 43 | var mem = (PosColorTexture*)memBlock.Data; 44 | foreach (var thing in layout.Stuff) { 45 | var width = thing.Width; 46 | var height = thing.Height; 47 | var region = new Vector4(thing.SourceX, thing.SourceY, width, height) / 4096; 48 | var origin = new Vector2(thing.DestX, thing.DestY); 49 | *mem++ = new PosColorTexture(origin + new Vector2(0, height), new Vector2(region.X, region.Y + region.W), unchecked((int)0xff000000)); 50 | *mem++ = new PosColorTexture(origin + new Vector2(width, height), new Vector2(region.X + region.Z, region.Y + region.W), unchecked((int)0xff000000)); 51 | *mem++ = new PosColorTexture(origin + new Vector2(width, 0), new Vector2(region.X + region.Z, region.Y), unchecked((int)0xff000000)); 52 | *mem++ = new PosColorTexture(origin, new Vector2(region.X, region.Y), unchecked((int)0xff000000)); 53 | count++; 54 | } 55 | 56 | vertexBuffer = new DynamicVertexBuffer(memBlock, PosColorTexture.Layout); 57 | } 58 | 59 | public void Submit () { 60 | Bgfx.SetVertexBuffer(vertexBuffer, count * 4); 61 | Bgfx.SetIndexBuffer(indexBuffer, 0, count * 6); 62 | Bgfx.SetRenderState(RenderState.ColorWrite | RenderState.BlendFunction(RenderState.BlendSourceAlpha, RenderState.BlendInverseSourceAlpha)); 63 | Bgfx.Submit(0); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /GpuExample/TextureAtlas.cs: -------------------------------------------------------------------------------- 1 | using SharpBgfx; 2 | using SharpFont; 3 | using System; 4 | 5 | namespace GpuExample { 6 | class TextureAtlas : IGlyphAtlas, IDisposable { 7 | Texture texture; 8 | int size; 9 | 10 | public int Width => size; 11 | public int Height => size; 12 | public Texture Texture => texture; 13 | 14 | public TextureAtlas (int size) { 15 | this.size = size; 16 | texture = Texture.Create2D(size, size, 1, TextureFormat.R8); 17 | } 18 | 19 | public void Dispose () => texture.Dispose(); 20 | 21 | public void Insert (int page, int x, int y, int width, int height, IntPtr data) { 22 | if (page > 0) 23 | throw new NotImplementedException(); 24 | 25 | texture.Update2D(0, x, y, width, height, new MemoryBlock(data, width * height), width); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /GpuExample/Window.cs: -------------------------------------------------------------------------------- 1 | using SharpBgfx; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Drawing; 5 | using System.Threading; 6 | using System.Windows.Forms; 7 | 8 | namespace GpuExample { 9 | public class Window { 10 | EventQueue eventQueue = new EventQueue(); 11 | Form form; 12 | Thread thread; 13 | 14 | public int Width { 15 | get; 16 | private set; 17 | } 18 | 19 | public int Height { 20 | get; 21 | private set; 22 | } 23 | 24 | public Window (string name, int width, int height) { 25 | Width = width; 26 | Height = height; 27 | 28 | form = new Form { 29 | Text = name, 30 | ClientSize = new Size(width, height) 31 | }; 32 | 33 | form.ClientSizeChanged += (o, e) => eventQueue.Post(new SizeEvent(width, height)); 34 | form.FormClosing += OnFormClosing; 35 | form.FormClosed += (o, e) => eventQueue.Post(new Event(EventType.Exit)); 36 | 37 | Bgfx.SetWindowHandle(form.Handle); 38 | } 39 | 40 | public void Run (Action renderThread) { 41 | thread = new Thread(() => renderThread(this)); 42 | thread.Start(); 43 | 44 | Application.Run(form); 45 | } 46 | 47 | public bool ProcessEvents (ResetFlags resetFlags) { 48 | Event ev; 49 | bool resizeRequired = false; 50 | 51 | while ((ev = eventQueue.Poll()) != null) { 52 | switch (ev.Type) { 53 | case EventType.Exit: 54 | return false; 55 | 56 | case EventType.Size: 57 | var size = (SizeEvent)ev; 58 | Width = size.Width; 59 | Height = size.Height; 60 | resizeRequired = true; 61 | break; 62 | } 63 | } 64 | 65 | if (resizeRequired) 66 | Bgfx.Reset(Width, Height, resetFlags); 67 | 68 | return true; 69 | } 70 | 71 | void OnFormClosing (object sender, FormClosingEventArgs e) { 72 | // kill all rendering and shutdown before closing the 73 | // window, or we'll get errors from the graphics driver 74 | eventQueue.Post(new Event(EventType.Exit)); 75 | thread.Join(); 76 | } 77 | 78 | class EventQueue { 79 | ConcurrentQueue queue = new ConcurrentQueue(); 80 | 81 | public void Post (Event ev) => queue.Enqueue(ev); 82 | 83 | public Event Poll () { 84 | Event ev; 85 | if (queue.TryDequeue(out ev)) 86 | return ev; 87 | 88 | return null; 89 | } 90 | } 91 | 92 | enum EventType { 93 | Exit, 94 | Key, 95 | Mouse, 96 | Size 97 | } 98 | 99 | class Event { 100 | public readonly EventType Type; 101 | 102 | public Event (EventType type) { 103 | Type = type; 104 | } 105 | } 106 | 107 | class SizeEvent : Event { 108 | public readonly int Width; 109 | public readonly int Height; 110 | 111 | public SizeEvent (int width, int height) 112 | : base(EventType.Size) { 113 | 114 | Width = width; 115 | Height = height; 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michael Popoloski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SharpFont 2 | 3 | Implements a parser and renderer for TTF / OTF font files. -------------------------------------------------------------------------------- /SharpFont.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.22823.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpFont", "SharpFont\SharpFont.csproj", "{8492DA27-3077-43A2-80B0-D51E7DA83BD2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{B7428745-AD58-411B-B8BE-75B1393D50B5}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuExample", "GpuExample\GpuExample.csproj", "{5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {8492DA27-3077-43A2-80B0-D51E7DA83BD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {8492DA27-3077-43A2-80B0-D51E7DA83BD2}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {8492DA27-3077-43A2-80B0-D51E7DA83BD2}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {8492DA27-3077-43A2-80B0-D51E7DA83BD2}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B7428745-AD58-411B-B8BE-75B1393D50B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B7428745-AD58-411B-B8BE-75B1393D50B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B7428745-AD58-411B-B8BE-75B1393D50B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B7428745-AD58-411B-B8BE-75B1393D50B5}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5203B9CC-AE0C-4DA2-BE63-E61F7DC71DEA}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /SharpFont/FontCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace SharpFont { 6 | /// 7 | /// Maintains a collection of fonts. 8 | /// 9 | public sealed class FontCollection { 10 | static FontCollection systemFonts; 11 | readonly Dictionary> fontTable = new Dictionary>(); 12 | 13 | /// 14 | /// The system font collection. 15 | /// 16 | public static FontCollection SystemFonts { 17 | get { 18 | if (systemFonts == null) 19 | systemFonts = LoadSystemFonts(); 20 | return systemFonts; 21 | } 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public FontCollection () { 28 | } 29 | 30 | /// 31 | /// Adds a font to the collection. 32 | /// 33 | /// A stream pointing to the font file. 34 | public void AddFontFile (Stream stream) { 35 | var metadata = LoadMetadata(stream); 36 | metadata.Stream = stream; 37 | AddFile(metadata, throwOnError: true); 38 | } 39 | 40 | /// 41 | /// Adds a font to the collection. 42 | /// 43 | /// The path to the font file. 44 | public void AddFontFile (string fileName) => AddFontFile(fileName, throwOnError: true); 45 | 46 | /// 47 | /// Finds a font in the collection that matches the given parameters. 48 | /// 49 | /// The font family name. 50 | /// The font weight. 51 | /// The font stretch setting. 52 | /// The font style. 53 | /// The loaded font if it exists in the collection; otherwise, null. 54 | public FontFace Load (string family, FontWeight weight = FontWeight.Normal, FontStretch stretch = FontStretch.Normal, FontStyle style = FontStyle.Regular) { 55 | List sublist; 56 | if (!fontTable.TryGetValue(family.ToLowerInvariant(), out sublist)) 57 | return null; 58 | 59 | foreach (var file in sublist) { 60 | if (file.Weight == weight && file.Stretch == stretch && file.Style == style) { 61 | if (file.Stream != null) 62 | return new FontFace(file.Stream); 63 | else { 64 | using (var stream = File.OpenRead(file.FileName)) 65 | return new FontFace(stream); 66 | } 67 | } 68 | } 69 | 70 | return null; 71 | } 72 | 73 | void AddFontFile (string fileName, bool throwOnError) { 74 | using (var stream = File.OpenRead(fileName)) { 75 | var metadata = LoadMetadata(stream); 76 | metadata.FileName = fileName; 77 | AddFile(metadata, throwOnError); 78 | } 79 | } 80 | 81 | void AddFile (Metadata metadata, bool throwOnError) { 82 | // specifically ignore fonts with no family name 83 | if (string.IsNullOrEmpty(metadata.Family)) { 84 | if (throwOnError) 85 | throw new InvalidFontException("Font does not contain any name metadata."); 86 | else 87 | return; 88 | } 89 | 90 | List sublist; 91 | var key = metadata.Family.ToLowerInvariant(); 92 | if (fontTable.TryGetValue(key, out sublist)) 93 | sublist.Add(metadata); 94 | else 95 | fontTable.Add(key, new List { metadata }); 96 | } 97 | 98 | static FontCollection LoadSystemFonts () { 99 | // TODO: currently only supports Windows 100 | var collection = new FontCollection(); 101 | foreach (var file in Directory.EnumerateFiles(Environment.GetFolderPath(Environment.SpecialFolder.Fonts))) { 102 | if (SupportedExtensions.Contains(Path.GetExtension(file).ToLowerInvariant())) 103 | collection.AddFontFile(file, throwOnError: false); 104 | } 105 | 106 | return collection; 107 | } 108 | 109 | static Metadata LoadMetadata (Stream stream) { 110 | using (var reader = new DataReader(stream)) { 111 | var tables = SfntTables.ReadFaceHeader(reader); 112 | var names = SfntTables.ReadNames(reader, tables); 113 | var os2Data = SfntTables.ReadOS2(reader, tables); 114 | 115 | return new Metadata { 116 | Family = names.TypographicFamilyName ?? names.FamilyName, 117 | Weight = os2Data.Weight, 118 | Stretch = os2Data.Stretch, 119 | Style = os2Data.Style 120 | }; 121 | } 122 | } 123 | 124 | class Metadata { 125 | public string Family; 126 | public FontWeight Weight; 127 | public FontStretch Stretch; 128 | public FontStyle Style; 129 | public Stream Stream; 130 | public string FileName; 131 | } 132 | 133 | static readonly HashSet SupportedExtensions = new HashSet { 134 | ".ttf", ".otf" 135 | }; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /SharpFont/FontFace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Numerics; 5 | using System.Threading; 6 | 7 | namespace SharpFont { 8 | /// 9 | /// Represents a single font face, maintaining all font data in memory. 10 | /// 11 | public sealed class FontFace { 12 | readonly Renderer renderer = new Renderer(); 13 | readonly Interpreter interpreter; 14 | readonly BaseGlyph[] glyphs; 15 | readonly MetricsEntry[] hmetrics; 16 | readonly MetricsEntry[] vmetrics; 17 | readonly CharacterMap charMap; 18 | readonly KerningTable kernTable; 19 | readonly MetricsEntry verticalSynthesized; 20 | readonly FUnit[] controlValueTable; 21 | readonly byte[] prepProgram; 22 | readonly int cellAscent; 23 | readonly int cellDescent; 24 | readonly int lineHeight; 25 | readonly int xHeight; 26 | readonly int capHeight; 27 | readonly int underlineSize; 28 | readonly int underlinePosition; 29 | readonly int strikeoutSize; 30 | readonly int strikeoutPosition; 31 | readonly int unitsPerEm; 32 | readonly bool integerPpems; 33 | 34 | static int currentId; 35 | internal int Id; // unique ID for cache lookups 36 | 37 | /// 38 | /// Indicates whether the font's glyphs share a fixed width. 39 | /// 40 | public readonly bool IsFixedWidth; 41 | 42 | /// 43 | /// The visual weight of the font. 44 | /// 45 | public readonly FontWeight Weight; 46 | 47 | /// 48 | /// The visual stretching appearance of the font's glyphs. 49 | /// 50 | public readonly FontStretch Stretch; 51 | 52 | /// 53 | /// The font's style. 54 | /// 55 | public readonly FontStyle Style; 56 | 57 | /// 58 | /// The name of the family to which the font belongs (e.g. "Arial"). 59 | /// 60 | public readonly string Family; 61 | 62 | /// 63 | /// The subname within the family (e.g. "Regular"). 64 | /// 65 | public readonly string Subfamily; 66 | 67 | /// 68 | /// The full human-friendly name of the font (e.g. "Arial Regular"). 69 | /// 70 | public readonly string FullName; 71 | 72 | /// 73 | /// A unique identifier for the font (this is self assigned by the font designer so it may not actually be unique). 74 | /// 75 | public readonly string UniqueID; 76 | 77 | /// 78 | /// The font's version string. 79 | /// 80 | public readonly string Version; 81 | 82 | /// 83 | /// An optional description string for the font. 84 | /// 85 | public readonly string Description; 86 | 87 | /// 88 | /// Initializes a new instance of the class. 89 | /// 90 | /// A stream pointing to the font file. 91 | /// 92 | /// All relevant font data is loaded into memory and retained by the FontFace object. 93 | /// Once the constructor finishes you are free to close the stream. 94 | /// 95 | public FontFace (Stream stream) { 96 | // read the face header and table records 97 | using (var reader = new DataReader(stream)) { 98 | var tables = SfntTables.ReadFaceHeader(reader); 99 | 100 | // read head and maxp tables for font metadata and limits 101 | FaceHeader head; 102 | SfntTables.ReadHead(reader, tables, out head); 103 | SfntTables.ReadMaxp(reader, tables, ref head); 104 | unitsPerEm = head.UnitsPerEm; 105 | integerPpems = (head.Flags & HeadFlags.IntegerPpem) != 0; 106 | 107 | // horizontal metrics header and data 108 | SfntTables.SeekToTable(reader, tables, FourCC.Hhea, required: true); 109 | var hMetricsHeader = SfntTables.ReadMetricsHeader(reader); 110 | SfntTables.SeekToTable(reader, tables, FourCC.Hmtx, required: true); 111 | hmetrics = SfntTables.ReadMetricsTable(reader, head.GlyphCount, hMetricsHeader.MetricCount); 112 | 113 | // font might optionally have vertical metrics 114 | if (SfntTables.SeekToTable(reader, tables, FourCC.Vhea)) { 115 | var vMetricsHeader = SfntTables.ReadMetricsHeader(reader); 116 | 117 | SfntTables.SeekToTable(reader, tables, FourCC.Vmtx, required: true); 118 | vmetrics = SfntTables.ReadMetricsTable(reader, head.GlyphCount, vMetricsHeader.MetricCount); 119 | } 120 | 121 | // OS/2 table has even more metrics 122 | var os2Data = SfntTables.ReadOS2(reader, tables); 123 | xHeight = os2Data.XHeight; 124 | capHeight = os2Data.CapHeight; 125 | Weight = os2Data.Weight; 126 | Stretch = os2Data.Stretch; 127 | Style = os2Data.Style; 128 | 129 | // optional PostScript table has random junk in it 130 | SfntTables.ReadPost(reader, tables, ref head); 131 | IsFixedWidth = head.IsFixedPitch; 132 | 133 | // read character-to-glyph mapping tables and kerning table 134 | charMap = CharacterMap.ReadCmap(reader, tables); 135 | kernTable = KerningTable.ReadKern(reader, tables); 136 | 137 | // name data 138 | var names = SfntTables.ReadNames(reader, tables); 139 | Family = names.TypographicFamilyName ?? names.FamilyName; 140 | Subfamily = names.TypographicSubfamilyName ?? names.SubfamilyName; 141 | FullName = names.FullName; 142 | UniqueID = names.UniqueID; 143 | Version = names.Version; 144 | Description = names.Description; 145 | 146 | // load glyphs if we have them 147 | if (SfntTables.SeekToTable(reader, tables, FourCC.Glyf)) { 148 | unsafe 149 | { 150 | // read in the loca table, which tells us the byte offset of each glyph 151 | var loca = stackalloc uint[head.GlyphCount]; 152 | SfntTables.ReadLoca(reader, tables, head.IndexFormat, loca, head.GlyphCount); 153 | 154 | // we need to know the length of the glyf table because of some weirdness in the loca table: 155 | // if a glyph is "missing" (like a space character), then its loca[n] entry is equal to loca[n+1] 156 | // if the last glyph in the set is missing, then loca[n] == glyf table length 157 | SfntTables.SeekToTable(reader, tables, FourCC.Glyf); 158 | var glyfOffset = reader.Position; 159 | var glyfLength = tables[SfntTables.FindTable(tables, FourCC.Glyf)].Length; 160 | 161 | // read in all glyphs 162 | glyphs = new BaseGlyph[head.GlyphCount]; 163 | for (int i = 0; i < glyphs.Length; i++) 164 | SfntTables.ReadGlyph(reader, i, 0, glyphs, glyfOffset, glyfLength, loca); 165 | } 166 | } 167 | 168 | // embedded bitmaps 169 | SbitTable.Read(reader, tables); 170 | 171 | // metrics calculations: if the UseTypographicMetrics flag is set, then 172 | // we should use the sTypo*** data for line height calculation 173 | if (os2Data.UseTypographicMetrics) { 174 | // include the line gap in the ascent so that 175 | // white space is distributed above the line 176 | cellAscent = os2Data.TypographicAscender + os2Data.TypographicLineGap; 177 | cellDescent = -os2Data.TypographicDescender; 178 | lineHeight = os2Data.TypographicAscender + os2Data.TypographicLineGap - os2Data.TypographicDescender; 179 | } 180 | else { 181 | // otherwise, we need to guess at whether hhea data or os/2 data has better line spacing 182 | // this is the recommended procedure based on the OS/2 spec extra notes 183 | cellAscent = os2Data.WinAscent; 184 | cellDescent = Math.Abs(os2Data.WinDescent); 185 | lineHeight = Math.Max( 186 | Math.Max(0, hMetricsHeader.LineGap) + hMetricsHeader.Ascender + Math.Abs(hMetricsHeader.Descender), 187 | cellAscent + cellDescent 188 | ); 189 | } 190 | 191 | // give sane defaults for underline and strikeout data if missing 192 | underlineSize = head.UnderlineThickness != 0 ? 193 | head.UnderlineThickness : (head.UnitsPerEm + 7) / 14; 194 | underlinePosition = head.UnderlinePosition != 0 ? 195 | head.UnderlinePosition : -((head.UnitsPerEm + 5) / 10); 196 | strikeoutSize = os2Data.StrikeoutSize != 0 ? 197 | os2Data.StrikeoutSize : underlineSize; 198 | strikeoutPosition = os2Data.StrikeoutPosition != 0 ? 199 | os2Data.StrikeoutPosition : head.UnitsPerEm / 3; 200 | 201 | // create some vertical metrics in case we haven't loaded any 202 | verticalSynthesized = new MetricsEntry { 203 | FrontSideBearing = os2Data.TypographicAscender, 204 | Advance = os2Data.TypographicAscender - os2Data.TypographicDescender 205 | }; 206 | 207 | // read in global font program data 208 | controlValueTable = SfntTables.ReadCvt(reader, tables); 209 | prepProgram = SfntTables.ReadProgram(reader, tables, FourCC.Prep); 210 | interpreter = new Interpreter( 211 | head.MaxStackSize, 212 | head.MaxStorageLocations, 213 | head.MaxFunctionDefs, 214 | head.MaxInstructionDefs, 215 | head.MaxTwilightPoints 216 | ); 217 | 218 | // the fpgm table optionally contains a program to run at initialization time 219 | var fpgm = SfntTables.ReadProgram(reader, tables, FourCC.Fpgm); 220 | if (fpgm != null) 221 | interpreter.InitializeFunctionDefs(fpgm); 222 | } 223 | 224 | Id = Interlocked.Increment(ref currentId); 225 | } 226 | 227 | /// 228 | /// Computes a pixel size given a point size and screen DPI. 229 | /// 230 | /// The font point size. 231 | /// The DPI of the screen. 232 | /// The pixel size at the given resolution. 233 | public static float ComputePixelSize (float pointSize, int dpi) => pointSize * dpi / 72; 234 | 235 | /// 236 | /// Gets metrics for the font as a whole at a particular pixel size. 237 | /// 238 | /// The size of the font, in pixels. 239 | /// The font's face metrics. 240 | public FaceMetrics GetFaceMetrics (float pixelSize) { 241 | var scale = ComputeScale(pixelSize); 242 | return new FaceMetrics( 243 | cellAscent * scale, 244 | cellDescent * scale, 245 | lineHeight * scale, 246 | xHeight * scale, 247 | capHeight * scale, 248 | underlineSize * scale, 249 | underlinePosition * scale, 250 | strikeoutSize * scale, 251 | strikeoutPosition * scale 252 | ); 253 | } 254 | 255 | /// 256 | /// Gets glyph data for a specific character. 257 | /// 258 | /// The Unicode codepoint for which to retrieve glyph data. 259 | /// The desired size of the font, in pixels. 260 | /// The glyph data if the font supports the given character; otherwise, null. 261 | public Glyph GetGlyph (CodePoint codePoint, float pixelSize) { 262 | var glyphIndex = charMap.Lookup(codePoint); 263 | if (glyphIndex < 0) 264 | return null; 265 | 266 | // set up the control value table 267 | var scale = ComputeScale(pixelSize); 268 | interpreter.SetControlValueTable(controlValueTable, scale, pixelSize, prepProgram); 269 | 270 | // get metrics 271 | var glyph = glyphs[glyphIndex]; 272 | var horizontal = hmetrics[glyphIndex]; 273 | var vtemp = vmetrics?[glyphIndex]; 274 | if (vtemp == null) { 275 | var synth = verticalSynthesized; 276 | synth.FrontSideBearing -= glyph.MaxY; 277 | vtemp = synth; 278 | } 279 | var vertical = vtemp.GetValueOrDefault(); 280 | 281 | // build and transform the glyph 282 | var points = new List(32); 283 | var contours = new List(32); 284 | var transform = Matrix3x2.CreateScale(scale); 285 | Geometry.ComposeGlyphs(glyphIndex, 0, ref transform, points, contours, glyphs); 286 | 287 | // add phantom points; these are used to define the extents of the glyph, 288 | // and can be modified by hinting instructions 289 | var pp1 = new Point((FUnit)(glyph.MinX - horizontal.FrontSideBearing), (FUnit)0); 290 | var pp2 = new Point(pp1.X + (FUnit)horizontal.Advance, (FUnit)0); 291 | var pp3 = new Point((FUnit)0, (FUnit)(glyph.MaxY + vertical.FrontSideBearing)); 292 | var pp4 = new Point((FUnit)0, pp3.Y - (FUnit)vertical.Advance); 293 | points.Add(pp1 * scale); 294 | points.Add(pp2 * scale); 295 | points.Add(pp3 * scale); 296 | points.Add(pp4 * scale); 297 | 298 | // hint the glyph's points 299 | var pointArray = points.ToArray(); 300 | var contourArray = contours.ToArray(); 301 | interpreter.HintGlyph(pointArray, contourArray, glyphs[glyphIndex].Instructions); 302 | 303 | return new Glyph(renderer, pointArray, contourArray, horizontal.Advance * scale); 304 | } 305 | 306 | /// 307 | /// Gets kerning information for a pair of characters. 308 | /// 309 | /// The left character. 310 | /// The right character. 311 | /// The size of the font, in pixels. 312 | /// The amount of kerning to apply, if any. 313 | public float GetKerning (CodePoint left, CodePoint right, float pixelSize) { 314 | if (kernTable == null) 315 | return 0.0f; 316 | 317 | var leftIndex = charMap.Lookup(left); 318 | var rightIndex = charMap.Lookup(right); 319 | if (leftIndex < 0 || rightIndex < 0) 320 | return 0.0f; 321 | 322 | var kern = kernTable.Lookup(leftIndex, rightIndex); 323 | return kern * ComputeScale(pixelSize); 324 | } 325 | 326 | /// 327 | /// Returns a string representation of the font. 328 | /// 329 | /// The full name of the font. 330 | public override string ToString () { 331 | return FullName; 332 | } 333 | 334 | float ComputeScale (float pixelSize) { 335 | if (integerPpems) 336 | pixelSize = (float)Math.Round(pixelSize); 337 | return pixelSize / unitsPerEm; 338 | } 339 | } 340 | 341 | /// 342 | /// Represents a single glyph of a font. 343 | /// 344 | public sealed class Glyph { 345 | readonly Renderer renderer; 346 | readonly PointF[] points; 347 | readonly int[] contours; 348 | 349 | /// 350 | /// The width of the glyph. 351 | /// 352 | public readonly float Width; 353 | 354 | /// 355 | /// The height of the glyph. 356 | /// 357 | public readonly float Height; 358 | 359 | /// 360 | /// The integer width of the glyph, as it will be rendered. 361 | /// 362 | public readonly int RenderWidth; 363 | 364 | /// 365 | /// The integer height of the glyph, as it will be rendered. 366 | /// 367 | public readonly int RenderHeight; 368 | 369 | /// 370 | /// The metrics to use when the glyph is laid out horizontally. 371 | /// 372 | public readonly GlyphMetrics HorizontalMetrics; 373 | 374 | /// 375 | /// The metrics to use when the glyph is laid out vertically. 376 | /// 377 | public readonly GlyphMetrics VerticalMetrics; 378 | 379 | internal Glyph (Renderer renderer, PointF[] points, int[] contours, float linearHorizontalAdvance) { 380 | this.renderer = renderer; 381 | this.points = points; 382 | this.contours = contours; 383 | 384 | // find the bounding box 385 | var min = new Vector2(float.MaxValue, float.MaxValue); 386 | var max = new Vector2(float.MinValue, float.MinValue); 387 | var pointCount = points.Length - 4; 388 | for (int i = 0; i < pointCount; i++) { 389 | min = Vector2.Min(min, points[i].P); 390 | max = Vector2.Max(max, points[i].P); 391 | } 392 | 393 | // save the "pure" size of the glyph, in fractional pixels 394 | var size = max - min; 395 | Width = size.X; 396 | Height = size.Y; 397 | 398 | // find the "render" size of the glyph, in whole pixels 399 | var shiftX = (int)Math.Floor(min.X); 400 | var shiftY = (int)Math.Floor(min.Y); 401 | RenderWidth = (int)Math.Ceiling(max.X) - shiftX; 402 | RenderHeight = (int)Math.Ceiling(max.Y) - shiftY; 403 | 404 | // translate the points so that 0,0 is at the bottom left corner 405 | var offset = new Vector2(-shiftX, -shiftY); 406 | for (int i = 0; i < pointCount; i++) 407 | points[i] = points[i].Offset(offset); 408 | 409 | HorizontalMetrics = new GlyphMetrics(new Vector2(min.X, max.Y), points[pointCount + 1].P.X - points[pointCount].P.X, linearHorizontalAdvance); 410 | 411 | // TODO: vertical metrics 412 | } 413 | 414 | /// 415 | /// Renders the glyph to the given surface. 416 | /// 417 | /// The target surface. 418 | /// 419 | /// If the surface is not large enough, the glyph will be clipped to fit. 420 | /// 421 | public void RenderTo (Surface surface) { 422 | // check for an empty outline, which obviously results in an empty render 423 | if (points.Length <= 0 || contours.Length <= 0) 424 | return; 425 | 426 | // clip against the bounds of the target surface 427 | var width = Math.Min(RenderWidth, surface.Width); 428 | var height = Math.Min(RenderHeight, surface.Height); 429 | if (width <= 0 || height <= 0) 430 | return; 431 | 432 | // walk each contour of the outline and render it 433 | var firstIndex = 0; 434 | renderer.Start(width, height); 435 | for (int i = 0; i < contours.Length; i++) { 436 | // decompose the contour into drawing commands 437 | var lastIndex = contours[i]; 438 | Geometry.DecomposeContour(renderer, firstIndex, lastIndex, points); 439 | 440 | // next contour starts where this one left off 441 | firstIndex = lastIndex + 1; 442 | } 443 | 444 | // blit the result to the target surface 445 | renderer.BlitTo(surface); 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /SharpFont/Internal/BinPacker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpFont { 4 | struct Rect { 5 | public int X, Y, Width, Height; 6 | 7 | public int Right => X + Width; 8 | public int Bottom => Y + Height; 9 | 10 | public Rect (int x, int y, int width, int height) { 11 | X = x; 12 | Y = y; 13 | Width = width; 14 | Height = height; 15 | } 16 | 17 | public bool Contains (Rect rect) { 18 | return rect.X >= X && rect.Y >= Y && 19 | rect.Right <= Right && rect.Bottom <= Bottom; 20 | } 21 | 22 | public override string ToString () => $"{X}, {Y}, {Width}, {Height}"; 23 | } 24 | 25 | struct ResizableArray { 26 | public T[] Data; 27 | public int Count; 28 | 29 | public T this[int index] => Data[index]; 30 | 31 | public ResizableArray (int capacity) { 32 | Data = new T[capacity]; 33 | Count = 0; 34 | } 35 | 36 | public void Clear () => Count = 0; 37 | 38 | public void Add (T value) { 39 | if (Count == Data.Length) 40 | Array.Resize(ref Data, (int)(Data.Length * 1.5)); 41 | Data[Count++] = value; 42 | } 43 | 44 | public void RemoveAt (int index) { 45 | Count--; 46 | if (index < Count) 47 | Array.Copy(Data, index + 1, Data, index, Count - index); 48 | } 49 | } 50 | 51 | // based on the "MAXRECTS" method developed by Jukka Jylänki: http://clb.demon.fi/files/RectangleBinPack.pdf 52 | struct BinPacker { 53 | ResizableArray freeList; 54 | 55 | public BinPacker (int width, int height) { 56 | freeList = new ResizableArray(16); 57 | freeList.Add(new Rect(0, 0, width, height)); 58 | } 59 | 60 | public void Clear (int width, int height) { 61 | freeList.Clear(); 62 | freeList.Add(new Rect(0, 0, width, height)); 63 | } 64 | 65 | public Rect Insert (int width, int height) { 66 | var bestNode = new Rect(); 67 | var bestShortFit = int.MaxValue; 68 | var bestLongFit = int.MaxValue; 69 | 70 | var count = freeList.Count; 71 | for (int i = 0; i < count; i++) { 72 | // try to place the rect 73 | var rect = freeList[i]; 74 | if (rect.Width < width || rect.Height < height) 75 | continue; 76 | 77 | var leftoverX = Math.Abs(rect.Width - width); 78 | var leftoverY = Math.Abs(rect.Height - height); 79 | var shortFit = Math.Min(leftoverX, leftoverY); 80 | var longFit = Math.Max(leftoverX, leftoverY); 81 | 82 | if (shortFit < bestShortFit || (shortFit == bestShortFit && longFit < bestLongFit)) { 83 | bestNode = new Rect(rect.X, rect.Y, width, height); 84 | bestShortFit = shortFit; 85 | bestLongFit = longFit; 86 | } 87 | } 88 | 89 | if (bestNode.Height == 0) 90 | return bestNode; 91 | 92 | // split out free areas into smaller ones 93 | for (int i = 0; i < count; i++) { 94 | if (SplitFreeNode(freeList[i], bestNode)) { 95 | freeList.RemoveAt(i); 96 | i--; 97 | count--; 98 | } 99 | } 100 | 101 | // prune the freelist 102 | for (int i = 0; i < freeList.Count; i++) { 103 | for (int j = i + 1; j < freeList.Count; j++) { 104 | var idata = freeList[i]; 105 | var jdata = freeList[j]; 106 | if (jdata.Contains(idata)) { 107 | freeList.RemoveAt(i); 108 | i--; 109 | break; 110 | } 111 | 112 | if (idata.Contains(jdata)) { 113 | freeList.RemoveAt(j); 114 | j--; 115 | } 116 | } 117 | } 118 | 119 | return bestNode; 120 | } 121 | 122 | bool SplitFreeNode (Rect freeNode, Rect usedNode) { 123 | // test if the rects even intersect 124 | var insideX = usedNode.X < freeNode.Right && usedNode.Right > freeNode.X; 125 | var insideY = usedNode.Y < freeNode.Bottom && usedNode.Bottom > freeNode.Y; 126 | if (!insideX || !insideY) 127 | return false; 128 | 129 | if (insideX) { 130 | // new node at the top side of the used node 131 | if (usedNode.Y > freeNode.Y && usedNode.Y < freeNode.Bottom) { 132 | var newNode = freeNode; 133 | newNode.Height = usedNode.Y - newNode.Y; 134 | freeList.Add(newNode); 135 | } 136 | 137 | // new node at the bottom side of the used node 138 | if (usedNode.Bottom < freeNode.Bottom) { 139 | var newNode = freeNode; 140 | newNode.Y = usedNode.Bottom; 141 | newNode.Height = freeNode.Bottom - usedNode.Bottom; 142 | freeList.Add(newNode); 143 | } 144 | } 145 | 146 | if (insideY) { 147 | // new node at the left side of the used node 148 | if (usedNode.X > freeNode.X && usedNode.X < freeNode.Right) { 149 | var newNode = freeNode; 150 | newNode.Width = usedNode.X - newNode.X; 151 | freeList.Add(newNode); 152 | } 153 | 154 | // new node at the right side of the used node 155 | if (usedNode.Right < freeNode.Right) { 156 | var newNode = freeNode; 157 | newNode.X = usedNode.Right; 158 | newNode.Width = freeNode.Right - usedNode.Right; 159 | freeList.Add(newNode); 160 | } 161 | } 162 | 163 | return true; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /SharpFont/Internal/CharacterMap.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SharpFont { 4 | class CharacterMap { 5 | Dictionary table; 6 | 7 | CharacterMap (Dictionary table) { 8 | this.table = table; 9 | } 10 | 11 | public int Lookup (CodePoint codePoint) { 12 | int index; 13 | if (table.TryGetValue(codePoint, out index)) 14 | return index; 15 | return -1; 16 | } 17 | 18 | public static CharacterMap ReadCmap (DataReader reader, TableRecord[] tables) { 19 | SfntTables.SeekToTable(reader, tables, FourCC.Cmap, required: true); 20 | 21 | // skip version 22 | var cmapOffset = reader.Position; 23 | reader.Skip(sizeof(short)); 24 | 25 | // read all of the subtable headers 26 | var subtableCount = reader.ReadUInt16BE(); 27 | var subtableHeaders = new CmapSubtableHeader[subtableCount]; 28 | for (int i = 0; i < subtableHeaders.Length; i++) { 29 | subtableHeaders[i] = new CmapSubtableHeader { 30 | PlatformID = reader.ReadUInt16BE(), 31 | EncodingID = reader.ReadUInt16BE(), 32 | Offset = reader.ReadUInt32BE() 33 | }; 34 | } 35 | 36 | // search for a "full" Unicode table first 37 | var chosenSubtableOffset = 0u; 38 | for (int i = 0; i < subtableHeaders.Length; i++) { 39 | var platform = subtableHeaders[i].PlatformID; 40 | var encoding = subtableHeaders[i].EncodingID; 41 | if ((platform == PlatformID.Microsoft && encoding == WindowsEncoding.UnicodeFull) || 42 | (platform == PlatformID.Unicode && encoding == UnicodeEncoding.Unicode32)) { 43 | 44 | chosenSubtableOffset = subtableHeaders[i].Offset; 45 | break; 46 | } 47 | } 48 | 49 | // if no full unicode table, just grab the first 50 | // one that supports any flavor of Unicode 51 | if (chosenSubtableOffset == 0) { 52 | for (int i = 0; i < subtableHeaders.Length; i++) { 53 | var platform = subtableHeaders[i].PlatformID; 54 | var encoding = subtableHeaders[i].EncodingID; 55 | if ((platform == PlatformID.Microsoft && encoding == WindowsEncoding.UnicodeBmp) || 56 | platform == PlatformID.Unicode) { 57 | 58 | chosenSubtableOffset = subtableHeaders[i].Offset; 59 | break; 60 | } 61 | } 62 | } 63 | 64 | // no unicode support at all is an error 65 | if (chosenSubtableOffset == 0) 66 | throw new InvalidFontException("Font does not support Unicode."); 67 | 68 | // jump to our chosen table and find out what format it's in 69 | reader.Seek(cmapOffset + chosenSubtableOffset); 70 | var format = reader.ReadUInt16BE(); 71 | switch (format) { 72 | case 4: return ReadCmapFormat4(reader); 73 | default: throw new InvalidFontException("Unsupported cmap format."); 74 | } 75 | } 76 | 77 | unsafe static CharacterMap ReadCmapFormat4 (DataReader reader) { 78 | // skip over length and language 79 | reader.Skip(sizeof(short) * 2); 80 | 81 | // figure out how many segments we have 82 | var segmentCount = reader.ReadUInt16BE() / 2; 83 | if (segmentCount > MaxSegments) 84 | throw new InvalidFontException("Too many cmap segments."); 85 | 86 | // skip over searchRange, entrySelector, and rangeShift 87 | reader.Skip(sizeof(short) * 3); 88 | 89 | // read in segment ranges 90 | var endCount = stackalloc int[segmentCount]; 91 | for (int i = 0; i < segmentCount; i++) 92 | endCount[i] = reader.ReadUInt16BE(); 93 | 94 | reader.Skip(sizeof(short)); // padding 95 | 96 | var startCount = stackalloc int[segmentCount]; 97 | for (int i = 0; i < segmentCount; i++) 98 | startCount[i] = reader.ReadUInt16BE(); 99 | 100 | var idDelta = stackalloc int[segmentCount]; 101 | for (int i = 0; i < segmentCount; i++) 102 | idDelta[i] = reader.ReadInt16BE(); 103 | 104 | // build table from each segment 105 | var table = new Dictionary(); 106 | for (int i = 0; i < segmentCount; i++) { 107 | // read the "idRangeOffset" for the current segment 108 | // if nonzero, we need to jump into the glyphIdArray to figure out the mapping 109 | // the layout is bizarre; see the OpenType spec for details 110 | var idRangeOffset = reader.ReadUInt16BE(); 111 | if (idRangeOffset != 0) { 112 | var currentOffset = reader.Position; 113 | reader.Seek(currentOffset + idRangeOffset - sizeof(ushort)); 114 | 115 | var end = endCount[i]; 116 | var delta = idDelta[i]; 117 | for (var codepoint = startCount[i]; codepoint <= end; codepoint++) { 118 | var glyphId = reader.ReadUInt16BE(); 119 | if (glyphId != 0) { 120 | var glyphIndex = (glyphId + delta) & 0xFFFF; 121 | if (glyphIndex != 0) 122 | table.Add((CodePoint)codepoint, glyphIndex); 123 | } 124 | } 125 | 126 | reader.Seek(currentOffset); 127 | } 128 | else { 129 | // otherwise, do a straight iteration through the segment 130 | var end = endCount[i]; 131 | var delta = idDelta[i]; 132 | for (var codepoint = startCount[i]; codepoint <= end; codepoint++) { 133 | var glyphIndex = (codepoint + delta) & 0xFFFF; 134 | if (glyphIndex != 0) 135 | table.Add((CodePoint)codepoint, glyphIndex); 136 | } 137 | } 138 | } 139 | 140 | return new CharacterMap(table); 141 | } 142 | 143 | const int MaxSegments = 1024; 144 | 145 | struct CmapSubtableHeader { 146 | public int PlatformID; 147 | public int EncodingID; 148 | public uint Offset; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /SharpFont/Internal/DataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace SharpFont { 6 | unsafe sealed class DataReader : IDisposable { 7 | readonly Stream stream; 8 | readonly byte[] buffer; 9 | readonly GCHandle handle; 10 | readonly byte* start; 11 | readonly int maxReadLength; 12 | int readOffset; 13 | int writeOffset; 14 | 15 | public uint Position => (uint)(stream.Position - (writeOffset - readOffset)); 16 | 17 | public DataReader (Stream stream, int maxReadLength = 4096) { 18 | this.stream = stream; 19 | this.maxReadLength = maxReadLength; 20 | 21 | buffer = new byte[maxReadLength * 2]; 22 | handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); 23 | start = (byte*)handle.AddrOfPinnedObject(); 24 | } 25 | 26 | public void Dispose () { 27 | if (handle.IsAllocated) 28 | handle.Free(); 29 | } 30 | 31 | public byte ReadByte () => *Read(1); 32 | public sbyte ReadSByte () => *(sbyte*)Read(1); 33 | public short ReadInt16 () => *(short*)Read(sizeof(short)); 34 | public int ReadInt32 () => *(int*)Read(sizeof(int)); 35 | public ushort ReadUInt16 () => *(ushort*)Read(sizeof(ushort)); 36 | public uint ReadUInt32 () => *(uint*)Read(sizeof(uint)); 37 | public short ReadInt16BE () => (short)htons(ReadUInt16()); 38 | public int ReadInt32BE () => (int)htonl(ReadUInt32()); 39 | public ushort ReadUInt16BE () => htons(ReadUInt16()); 40 | public uint ReadUInt32BE () => htonl(ReadUInt32()); 41 | 42 | public byte[] ReadBytes (int count) { 43 | var result = new byte[count]; 44 | int index = 0; 45 | while (count > 0) { 46 | var readCount = Math.Min(count, maxReadLength); 47 | Marshal.Copy(new IntPtr(Read(readCount)), result, index, readCount); 48 | 49 | count -= readCount; 50 | index += readCount; 51 | } 52 | return result; 53 | } 54 | 55 | public void Seek (uint position) { 56 | // if the position is within our buffer we can reuse part of it 57 | // otherwise, just clear everything out and jump to the right spot 58 | var current = stream.Position; 59 | if (position < current - writeOffset || position >= current) { 60 | readOffset = 0; 61 | writeOffset = 0; 62 | stream.Position = position; 63 | } 64 | else { 65 | readOffset = (int)(position - current + writeOffset); 66 | CheckWrapAround(0); 67 | } 68 | } 69 | 70 | public void Skip (int count) { 71 | readOffset += count; 72 | if (readOffset < writeOffset) 73 | CheckWrapAround(0); 74 | else { 75 | // we've skipped everything in our buffer; clear it out 76 | // and then skip any remaining data by seeking the stream 77 | var seekCount = readOffset - writeOffset; 78 | if (seekCount > 0) 79 | stream.Position += seekCount; 80 | 81 | readOffset = 0; 82 | writeOffset = 0; 83 | } 84 | } 85 | 86 | byte* Read (int count) { 87 | // we'll be returning a pointer to a contiguous block of memory 88 | // at least count bytes large, starting at the current offset 89 | var result = start + readOffset; 90 | readOffset += count; 91 | 92 | if (readOffset >= writeOffset) { 93 | if (count > maxReadLength) 94 | throw new InvalidOperationException("Tried to read more data than the max read length."); 95 | 96 | // we need to read at least this many bytes, but we'll try for more (could be zero) 97 | var need = readOffset - writeOffset; 98 | while (need > 0) { 99 | // try to read in a chunk of maxReadLength bytes (unless that would push past the end of our space) 100 | int read = stream.Read(buffer, writeOffset, Math.Min(maxReadLength, buffer.Length - writeOffset)); 101 | if (read <= 0) 102 | throw new EndOfStreamException(); 103 | 104 | writeOffset += read; 105 | need -= read; 106 | } 107 | 108 | if (CheckWrapAround(count)) 109 | result = start; 110 | } 111 | 112 | // most of the time we'll have plenty of data in the buffer 113 | // so we'll fall through here and get the pointer quickly 114 | return result; 115 | } 116 | 117 | bool CheckWrapAround (int dataCount) { 118 | // if we've gone past the max read length, we can no longer ensure 119 | // that future read calls of maxReadLength size will be able to get a 120 | // contiguous buffer, so wrap back to the beginning 121 | if (readOffset >= maxReadLength) { 122 | // back copy any buffered data so that it doesn't get lost 123 | var copyCount = writeOffset - readOffset + dataCount; 124 | if (copyCount > 0) 125 | Buffer.BlockCopy(buffer, readOffset - dataCount, buffer, 0, copyCount); 126 | 127 | readOffset = dataCount; 128 | writeOffset = copyCount; 129 | return true; 130 | } 131 | 132 | return false; 133 | } 134 | 135 | static uint htonl (uint value) { 136 | // this branch is constant at JIT time and will be optimized out 137 | if (!BitConverter.IsLittleEndian) 138 | return value; 139 | 140 | var ptr = (byte*)&value; 141 | return (uint)(ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]); 142 | } 143 | 144 | static ushort htons (ushort value) { 145 | // this branch is constant at JIT time and will be optimized out 146 | if (!BitConverter.IsLittleEndian) 147 | return value; 148 | 149 | var ptr = (byte*)&value; 150 | return (ushort)(ptr[0] << 8 | ptr[1]); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SharpFont/Internal/Geometry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | 5 | namespace SharpFont { 6 | struct FUnit { 7 | int value; 8 | 9 | public static explicit operator int (FUnit v) => v.value; 10 | public static explicit operator FUnit (int v) => new FUnit { value = v }; 11 | 12 | public static FUnit operator -(FUnit lhs, FUnit rhs) => (FUnit)(lhs.value - rhs.value); 13 | public static FUnit operator +(FUnit lhs, FUnit rhs) => (FUnit)(lhs.value + rhs.value); 14 | public static float operator *(FUnit lhs, float rhs) => lhs.value * rhs; 15 | 16 | public static FUnit Max (FUnit a, FUnit b) => (FUnit)Math.Max(a.value, b.value); 17 | public static FUnit Min (FUnit a, FUnit b) => (FUnit)Math.Min(a.value, b.value); 18 | } 19 | 20 | struct Point { 21 | public FUnit X; 22 | public FUnit Y; 23 | public PointType Type; 24 | 25 | public Point (FUnit x, FUnit y) { 26 | X = x; 27 | Y = y; 28 | Type = PointType.OnCurve; 29 | } 30 | 31 | public static PointF operator *(Point lhs, float rhs) => new PointF(new Vector2(lhs.X * rhs, lhs.Y * rhs), lhs.Type); 32 | 33 | public static explicit operator Vector2 (Point p) => new Vector2((int)p.X, (int)p.Y); 34 | } 35 | 36 | struct PointF { 37 | public Vector2 P; 38 | public PointType Type; 39 | 40 | public PointF (Vector2 position, PointType type) { 41 | P = position; 42 | Type = type; 43 | } 44 | 45 | public PointF Offset (Vector2 offset) => new PointF(P + offset, Type); 46 | 47 | public override string ToString () => $"{P} ({Type})"; 48 | 49 | public static implicit operator Vector2 (PointF p) => p.P; 50 | } 51 | 52 | enum PointType { 53 | OnCurve, 54 | Quadratic, 55 | Cubic 56 | } 57 | 58 | static class Geometry { 59 | public static void ComposeGlyphs (int glyphIndex, int startPoint, ref Matrix3x2 transform, List basePoints, List baseContours, BaseGlyph[] glyphTable) { 60 | var glyph = glyphTable[glyphIndex]; 61 | var simple = glyph as SimpleGlyph; 62 | if (simple != null) { 63 | foreach (var endpoint in simple.ContourEndpoints) 64 | baseContours.Add(endpoint + startPoint); 65 | foreach (var point in simple.Points) 66 | basePoints.Add(new PointF(Vector2.TransformNormal((Vector2)point, transform), point.Type)); 67 | } 68 | else { 69 | // otherwise, we have a composite glyph 70 | var composite = (CompositeGlyph)glyph; 71 | foreach (var subglyph in composite.Subglyphs) { 72 | // if we have a scale, update the local transform 73 | var local = transform; 74 | bool haveScale = (subglyph.Flags & (CompositeGlyphFlags.HaveScale | CompositeGlyphFlags.HaveXYScale | CompositeGlyphFlags.HaveTransform)) != 0; 75 | if (haveScale) 76 | local = transform * subglyph.Transform; 77 | 78 | // recursively compose the subglyph into our lists 79 | int currentPoints = basePoints.Count; 80 | ComposeGlyphs(subglyph.Index, currentPoints, ref local, basePoints, baseContours, glyphTable); 81 | 82 | // calculate the offset for the subglyph. we have to do offsetting after composing all subglyphs, 83 | // because we might need to find the offset based on previously composed points by index 84 | Vector2 offset; 85 | if ((subglyph.Flags & CompositeGlyphFlags.ArgsAreXYValues) != 0) { 86 | offset = (Vector2)new Point((FUnit)subglyph.Arg1, (FUnit)subglyph.Arg2); 87 | if (haveScale && (subglyph.Flags & CompositeGlyphFlags.ScaledComponentOffset) != 0) 88 | offset = Vector2.TransformNormal(offset, local); 89 | else 90 | offset = Vector2.TransformNormal(offset, transform); 91 | 92 | // if the RoundXYToGrid flag is set, round the offset components 93 | if ((subglyph.Flags & CompositeGlyphFlags.RoundXYToGrid) != 0) 94 | offset = new Vector2((float)Math.Round(offset.X), (float)Math.Round(offset.Y)); 95 | } 96 | else { 97 | // if the offsets are not given in FUnits, then they are point indices 98 | // in the currently composed base glyph that we should match up 99 | var p1 = basePoints[(int)((uint)subglyph.Arg1 + startPoint)]; 100 | var p2 = basePoints[(int)((uint)subglyph.Arg2 + currentPoints)]; 101 | offset = p1.P - p2.P; 102 | } 103 | 104 | // translate all child points 105 | if (offset != Vector2.Zero) { 106 | for (int i = currentPoints; i < basePoints.Count; i++) 107 | basePoints[i] = basePoints[i].Offset(offset); 108 | } 109 | } 110 | } 111 | } 112 | 113 | public static void DecomposeContour (Renderer renderer, int firstIndex, int lastIndex, PointF[] points) { 114 | var pointIndex = firstIndex; 115 | var start = points[pointIndex]; 116 | var end = points[lastIndex]; 117 | var control = start; 118 | 119 | if (start.Type == PointType.Cubic) 120 | throw new InvalidFontException("Contours can't start with a cubic control point."); 121 | 122 | if (start.Type == PointType.Quadratic) { 123 | // if first point is a control point, try using the last point 124 | if (end.Type == PointType.OnCurve) { 125 | start = end; 126 | lastIndex--; 127 | } 128 | else { 129 | // if they're both control points, start at the middle 130 | start.P = (start.P + end.P) / 2; 131 | } 132 | pointIndex--; 133 | } 134 | 135 | // let's draw this contour 136 | renderer.MoveTo(start); 137 | 138 | var needClose = true; 139 | while (pointIndex < lastIndex) { 140 | var point = points[++pointIndex]; 141 | switch (point.Type) { 142 | case PointType.OnCurve: 143 | renderer.LineTo(point); 144 | break; 145 | 146 | case PointType.Quadratic: 147 | control = point; 148 | var done = false; 149 | while (pointIndex < lastIndex) { 150 | var next = points[++pointIndex]; 151 | if (next.Type == PointType.OnCurve) { 152 | renderer.QuadraticCurveTo(control, next); 153 | done = true; 154 | break; 155 | } 156 | 157 | if (next.Type != PointType.Quadratic) 158 | throw new InvalidFontException("Bad outline data."); 159 | 160 | renderer.QuadraticCurveTo(control, (control.P + next.P) / 2); 161 | control = next; 162 | } 163 | 164 | if (!done) { 165 | // if we hit this point, we're ready to close out the contour 166 | renderer.QuadraticCurveTo(control, start); 167 | needClose = false; 168 | } 169 | break; 170 | 171 | case PointType.Cubic: 172 | throw new NotSupportedException(); 173 | } 174 | } 175 | 176 | if (needClose) 177 | renderer.LineTo(start); 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /SharpFont/Internal/KerningTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpFont { 5 | class KerningTable { 6 | Dictionary table; 7 | 8 | KerningTable (Dictionary table) { 9 | this.table = table; 10 | } 11 | 12 | public FUnit Lookup (int left, int right) { 13 | var key = ((uint)left << 16) | (uint)right; 14 | int value; 15 | if (table.TryGetValue(key, out value)) 16 | return (FUnit)value; 17 | return (FUnit)0; 18 | } 19 | 20 | public static KerningTable ReadKern (DataReader reader, TableRecord[] tables) { 21 | // kern table is optional 22 | if (!SfntTables.SeekToTable(reader, tables, FourCC.Kern)) 23 | return null; 24 | 25 | // skip version 26 | reader.Skip(sizeof(short)); 27 | 28 | // read each subtable and accumulate kerning values 29 | var tableData = new Dictionary(); 30 | var subtableCount = reader.ReadUInt16BE(); 31 | for (int i = 0; i < subtableCount; i++) { 32 | // skip version 33 | var currentOffset = reader.Position; 34 | reader.Skip(sizeof(short)); 35 | 36 | var length = reader.ReadUInt16BE(); 37 | var coverage = reader.ReadUInt16BE(); 38 | 39 | // we (and Windows) only support Format 0 tables 40 | // only care about tables with horizontal kerning data 41 | var kc = (KernCoverage)coverage; 42 | if ((coverage & FormatMask) == 0 && (kc & KernCoverage.Horizontal) != 0 && (kc & KernCoverage.CrossStream) == 0) { 43 | // read the number of entries; skip over the rest of the header 44 | var entryCount = reader.ReadUInt16BE(); 45 | reader.Skip(sizeof(short) * 3); 46 | 47 | var isMin = (kc & KernCoverage.Minimum) != 0; 48 | var isOverride = (kc & KernCoverage.Override) != 0; 49 | 50 | // read in each entry and accumulate its kerning data 51 | for (int j = 0; j < entryCount; j++) { 52 | var left = reader.ReadUInt16BE(); 53 | var right = reader.ReadUInt16BE(); 54 | var value = reader.ReadInt16BE(); 55 | 56 | // look up the current value, if we have one; if not, start at zero 57 | int current = 0; 58 | var key = ((uint)left << 16) | right; 59 | tableData.TryGetValue(key, out current); 60 | 61 | if (isMin) { 62 | if (current < value) 63 | tableData[key] = value; 64 | } 65 | else if (isOverride) 66 | tableData[key] = value; 67 | else 68 | tableData[key] = current + value; 69 | } 70 | } 71 | 72 | // jump to the next subtable 73 | reader.Seek(currentOffset + length); 74 | } 75 | 76 | return new KerningTable(tableData); 77 | } 78 | 79 | const uint FormatMask = 0xFFFF0000; 80 | 81 | [Flags] 82 | enum KernCoverage { 83 | None = 0, 84 | Horizontal = 0x1, 85 | Minimum = 0x2, 86 | CrossStream = 0x4, 87 | Override = 0x8 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SharpFont/Internal/Renderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | 4 | namespace SharpFont { 5 | // handles rasterizing curves to a bitmap 6 | // the algorithm is heavily inspired by the FreeType2 renderer; thanks guys! 7 | unsafe class Renderer { 8 | Surface surface; // the surface we're currently rendering to 9 | int[] scanlines; // one scanline per Y, points into cell buffer 10 | int[] curveLevels; 11 | Vector2[] bezierArc; // points on a bezier arc 12 | Cell[] cells; 13 | Vector2 activePoint; // subpixel position of active point 14 | float activeArea; // running total of the active cell's area 15 | float activeCoverage; // ditto for coverage 16 | int cellX, cellY; // pixel position of the active cell 17 | int cellCount; // number of cells in active use 18 | int width, height; // bounds of the glyph surface, in plain old pixels 19 | bool cellActive; // whether the current cell has active data 20 | 21 | public void Start (int width, int height) { 22 | this.width = width; 23 | this.height = height; 24 | 25 | cellCount = 0; 26 | activeArea = 0.0f; 27 | activeCoverage = 0.0f; 28 | cellActive = false; 29 | 30 | if (cells == null) { 31 | cells = new Cell[1024]; 32 | curveLevels = new int[32]; 33 | bezierArc = new Vector2[curveLevels.Length * 3 + 1]; 34 | scanlines = new int[height]; 35 | } 36 | else if (height >= scanlines.Length) 37 | scanlines = new int[height]; 38 | 39 | for (int i = 0; i < height; i++) 40 | scanlines[i] = -1; 41 | } 42 | 43 | public void MoveTo (Vector2 point) { 44 | // record current cell, if any 45 | if (cellActive) 46 | RetireActiveCell(); 47 | 48 | // calculate cell coordinates 49 | activePoint = point; 50 | cellX = Math.Max(-1, Math.Min((int)activePoint.X, width)); 51 | cellY = (int)activePoint.Y; 52 | 53 | // activate if this is a valid cell location 54 | cellActive = cellX < width && cellY < height; 55 | activeArea = 0.0f; 56 | activeCoverage = 0.0f; 57 | } 58 | 59 | public void LineTo (Vector2 point) { 60 | // figure out which scanlines this line crosses 61 | var startScanline = (int)activePoint.Y; 62 | var endScanline = (int)point.Y; 63 | 64 | // vertical clipping 65 | if (Math.Min(startScanline, endScanline) >= height || 66 | Math.Max(startScanline, endScanline) < 0) { 67 | // just save this position since it's outside our bounds and continue 68 | activePoint = point; 69 | return; 70 | } 71 | 72 | // render the line 73 | var vector = point - activePoint; 74 | var fringeStart = activePoint.Y - startScanline; 75 | var fringeEnd = point.Y - endScanline; 76 | 77 | if (startScanline == endScanline) { 78 | // this is a horizontal line 79 | RenderScanline(startScanline, activePoint.X, fringeStart, point.X, fringeEnd); 80 | } 81 | else if (vector.X == 0) { 82 | // this is a vertical line 83 | var x = (int)activePoint.X; 84 | var xarea = (activePoint.X - x) * 2; 85 | 86 | // check if we're scanning up or down 87 | var first = 1.0f; 88 | var increment = 1; 89 | if (vector.Y < 0) { 90 | first = 0.0f; 91 | increment = -1; 92 | } 93 | 94 | // first cell fringe 95 | var deltaY = (first - fringeStart); 96 | activeArea += xarea * deltaY; 97 | activeCoverage += deltaY; 98 | startScanline += increment; 99 | SetCurrentCell(x, startScanline); 100 | 101 | // any other cells covered by the line 102 | deltaY = first + first - 1.0f; 103 | var area = xarea * deltaY; 104 | while (startScanline != endScanline) { 105 | activeArea += area; 106 | activeCoverage += deltaY; 107 | startScanline += increment; 108 | SetCurrentCell(x, startScanline); 109 | } 110 | 111 | // ending fringe 112 | deltaY = fringeEnd - 1.0f + first; 113 | activeArea += xarea * deltaY; 114 | activeCoverage += deltaY; 115 | } 116 | else { 117 | // diagonal line 118 | // check if we're scanning up or down 119 | var dist = (1.0f - fringeStart) * vector.X; 120 | var first = 1.0f; 121 | var increment = 1; 122 | if (vector.Y < 0) { 123 | dist = fringeStart * vector.X; 124 | first = 0.0f; 125 | increment = -1; 126 | vector.Y = -vector.Y; 127 | } 128 | 129 | // render the first scanline 130 | var delta = dist / vector.Y; 131 | var x = activePoint.X + delta; 132 | RenderScanline(startScanline, activePoint.X, fringeStart, x, first); 133 | startScanline += increment; 134 | SetCurrentCell((int)x, startScanline); 135 | 136 | // step along the line 137 | if (startScanline != endScanline) { 138 | delta = vector.X / vector.Y; 139 | while (startScanline != endScanline) { 140 | var x2 = x + delta; 141 | RenderScanline(startScanline, x, 1.0f - first, x2, first); 142 | x = x2; 143 | 144 | startScanline += increment; 145 | SetCurrentCell((int)x, startScanline); 146 | } 147 | } 148 | 149 | // last scanline 150 | RenderScanline(startScanline, x, 1.0f - first, point.X, fringeEnd); 151 | } 152 | 153 | activePoint = point; 154 | } 155 | 156 | public void QuadraticCurveTo (Vector2 control, Vector2 point) { 157 | var levels = curveLevels; 158 | var arc = bezierArc; 159 | arc[0] = point; 160 | arc[1] = control; 161 | arc[2] = activePoint; 162 | 163 | var delta = Vector2.Abs(arc[2] + arc[0] - 2 * arc[1]); 164 | var dx = delta.X; 165 | if (dx < delta.Y) 166 | dx = delta.Y; 167 | 168 | // short cut for small arcs 169 | if (dx < 0.25f) { 170 | LineTo(arc[0]); 171 | return; 172 | } 173 | 174 | int level = 0; 175 | do { 176 | dx /= 4.0f; 177 | level++; 178 | } while (dx > 0.25f); 179 | 180 | int top = 0; 181 | int arcIndex = 0; 182 | levels[0] = level; 183 | 184 | while (top >= 0) { 185 | level = levels[top]; 186 | if (level > 0) { 187 | // split the arc 188 | arc[arcIndex + 4] = arc[arcIndex + 2]; 189 | var b = arc[arcIndex + 1]; 190 | var a = arc[arcIndex + 3] = (arc[arcIndex + 2] + b) / 2; 191 | b = arc[arcIndex + 1] = (arc[arcIndex] + b) / 2; 192 | arc[arcIndex + 2] = (a + b) / 2; 193 | 194 | arcIndex += 2; 195 | top++; 196 | levels[top] = levels[top - 1] = level - 1; 197 | } 198 | else { 199 | LineTo(arc[arcIndex]); 200 | top--; 201 | arcIndex -= 2; 202 | } 203 | } 204 | } 205 | 206 | public void BlitTo (Surface surface) { 207 | if (cellActive) 208 | RetireActiveCell(); 209 | 210 | // if we rendered nothing, there's nothing to do 211 | if (cellCount == 0) 212 | return; 213 | 214 | this.surface = surface; 215 | for (int y = 0; y < height; y++) { 216 | var x = 0; 217 | var coverage = 0.0f; 218 | var index = scanlines[y]; 219 | 220 | while (index != -1) { 221 | // cap off the previous span, if we had one 222 | var cell = cells[index]; 223 | if (cell.X > x && coverage != 0.0f) 224 | FillHLine(x, y, coverage, cell.X - x); 225 | 226 | coverage += cell.Coverage; 227 | 228 | var area = coverage - (cell.Area / 2.0f); 229 | if (area != 0.0f && cell.X >= 0) 230 | FillHLine(cell.X, y, area, 1); 231 | 232 | x = cell.X + 1; 233 | index = cell.Next; 234 | } 235 | 236 | // finish off the trailing span 237 | if (coverage != 0.0f) 238 | FillHLine(x, y, coverage, width - x); 239 | } 240 | } 241 | 242 | void FillHLine (int x, int y, float coveragePercentage, int length) { 243 | var coverage = (int)Math.Round(coveragePercentage * 255, MidpointRounding.AwayFromZero); 244 | if (coverage == 0) 245 | return; 246 | 247 | coverage = Math.Min(Math.Abs(coverage), 255); 248 | var c = (byte)coverage; 249 | 250 | // find the scanline offset 251 | var bits = (byte*)surface.Bits - y * surface.Pitch; 252 | if (surface.Pitch >= 0) 253 | bits += (surface.Height - 1) * surface.Pitch; 254 | 255 | // finally fill pixels 256 | var p = bits + x; 257 | for (int i = 0; i < length; i++) 258 | *p++ = c; 259 | } 260 | 261 | void RenderScanline (int scanline, float x1, float y1, float x2, float y2) { 262 | var startCell = (int)x1; 263 | var endCell = (int)x2; 264 | var fringeStart = x1 - startCell; 265 | var fringeEnd = x2 - endCell; 266 | 267 | // trivial case; exact same Y, down to the subpixel 268 | if (y1 == y2) { 269 | SetCurrentCell(endCell, scanline); 270 | return; 271 | } 272 | 273 | // trivial case; within the same cell 274 | if (startCell == endCell) { 275 | var deltaY = y2 - y1; 276 | activeArea += (fringeStart + fringeEnd) * deltaY; 277 | activeCoverage += deltaY; 278 | return; 279 | } 280 | 281 | // long case: render a run of adjacent cells on the scanline 282 | var dx = x2 - x1; 283 | var dy = y2 - y1; 284 | 285 | // check if we're going left or right 286 | var dist = (1.0f - fringeStart) * dy; 287 | var first = 1.0f; 288 | var increment = 1; 289 | if (dx < 0) { 290 | dist = fringeStart * dy; 291 | first = 0.0f; 292 | increment = -1; 293 | dx = -dx; 294 | } 295 | 296 | // update the first cell 297 | var delta = dist / dx; 298 | activeArea += (fringeStart + first) * delta; 299 | activeCoverage += delta; 300 | 301 | startCell += increment; 302 | SetCurrentCell(startCell, scanline); 303 | y1 += delta; 304 | 305 | // update all covered cells 306 | if (startCell != endCell) { 307 | dist = y2 - y1 + delta; 308 | delta = dist / dx; 309 | 310 | while (startCell != endCell) { 311 | activeArea += delta; 312 | activeCoverage += delta; 313 | y1 += delta; 314 | startCell += increment; 315 | SetCurrentCell(startCell, scanline); 316 | } 317 | } 318 | 319 | // final cell 320 | delta = y2 - y1; 321 | activeArea += (fringeEnd + 1.0f - first) * delta; 322 | activeCoverage += delta; 323 | } 324 | 325 | void SetCurrentCell (int x, int y) { 326 | // all cells on the left of the clipping region go to the minX - 1 position 327 | x = Math.Min(x, width); 328 | x = Math.Max(x, -1); 329 | 330 | // moving to a new cell? 331 | if (x != cellX || y != cellY) { 332 | if (cellActive) 333 | RetireActiveCell(); 334 | 335 | activeArea = 0.0f; 336 | activeCoverage = 0.0f; 337 | cellX = x; 338 | cellY = y; 339 | } 340 | 341 | cellActive = cellX < width && cellY < height; 342 | } 343 | 344 | void RetireActiveCell () { 345 | // cells with no coverage have nothing to do 346 | if (activeArea == 0.0f && activeCoverage == 0.0f) 347 | return; 348 | 349 | // find the right spot to add or insert this cell 350 | var x = cellX; 351 | var y = cellY; 352 | var cell = scanlines[y]; 353 | if (cell == -1 || cells[cell].X > x) { 354 | // no cells at all on this scanline yet, or the first one 355 | // is already beyond our X value, so grab a new one 356 | cell = GetNewCell(x, cell); 357 | scanlines[y] = cell; 358 | return; 359 | } 360 | 361 | while (cells[cell].X != x) { 362 | var next = cells[cell].Next; 363 | if (next == -1 || cells[next].X > x) { 364 | // either we reached the end of the chain in this 365 | // scanline, or the next cell has a larger X 366 | next = GetNewCell(x, next); 367 | cells[cell].Next = next; 368 | return; 369 | } 370 | 371 | // move to next cell 372 | cell = next; 373 | } 374 | 375 | // we found a cell with identical coords, so adjust its coverage 376 | cells[cell].Area += activeArea; 377 | cells[cell].Coverage += activeCoverage; 378 | } 379 | 380 | int GetNewCell (int x, int next) { 381 | // resize our array if we've run out of room 382 | if (cellCount == cells.Length) 383 | Array.Resize(ref cells, (int)(cells.Length * 1.5)); 384 | 385 | var index = cellCount++; 386 | cells[index].X = x; 387 | cells[index].Next = next; 388 | cells[index].Area = activeArea; 389 | cells[index].Coverage = activeCoverage; 390 | 391 | return index; 392 | } 393 | 394 | struct Cell { 395 | public int X; 396 | public int Next; 397 | public float Coverage; 398 | public float Area; 399 | } 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /SharpFont/Internal/SbitTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpFont { 8 | class SbitTable { 9 | public unsafe static SbitTable Read (DataReader reader, TableRecord[] tables) { 10 | if (!SfntTables.SeekToTable(reader, tables, FourCC.Eblc)) 11 | return null; 12 | 13 | // skip version 14 | var baseOffset = reader.Position; 15 | reader.Skip(sizeof(int)); 16 | 17 | // load each strike table 18 | var count = reader.ReadInt32BE(); 19 | if (count > MaxBitmapStrikes) 20 | throw new InvalidFontException("Too many bitmap strikes in font."); 21 | 22 | var sizeTableHeaders = stackalloc BitmapSizeTable[count]; 23 | for (int i = 0; i < count; i++) { 24 | sizeTableHeaders[i].SubTableOffset = reader.ReadUInt32BE(); 25 | sizeTableHeaders[i].SubTableSize = reader.ReadUInt32BE(); 26 | sizeTableHeaders[i].SubTableCount = reader.ReadUInt32BE(); 27 | 28 | // skip colorRef, metrics entries, start and end glyph indices 29 | reader.Skip(sizeof(uint) + sizeof(ushort) * 2 + 12 * 2); 30 | 31 | sizeTableHeaders[i].PpemX = reader.ReadByte(); 32 | sizeTableHeaders[i].PpemY = reader.ReadByte(); 33 | sizeTableHeaders[i].BitDepth = reader.ReadByte(); 34 | sizeTableHeaders[i].Flags = (BitmapSizeFlags)reader.ReadByte(); 35 | } 36 | 37 | // read index subtables 38 | var indexSubTables = stackalloc IndexSubTable[count]; 39 | for (int i = 0; i < count; i++) { 40 | reader.Seek(baseOffset + sizeTableHeaders[i].SubTableOffset); 41 | indexSubTables[i] = new IndexSubTable { 42 | FirstGlyph = reader.ReadUInt16BE(), 43 | LastGlyph = reader.ReadUInt16BE(), 44 | Offset = reader.ReadUInt32BE() 45 | }; 46 | } 47 | 48 | // read the actual data for each strike table 49 | for (int i = 0; i < count; i++) { 50 | // read the subtable header 51 | reader.Seek(baseOffset + sizeTableHeaders[i].SubTableOffset + indexSubTables[i].Offset); 52 | var indexFormat = reader.ReadUInt16BE(); 53 | var imageFormat = reader.ReadUInt16BE(); 54 | var imageDataOffset = reader.ReadUInt32BE(); 55 | 56 | 57 | } 58 | 59 | return null; 60 | } 61 | 62 | struct BitmapSizeTable { 63 | public uint SubTableOffset; 64 | public uint SubTableSize; 65 | public uint SubTableCount; 66 | public byte PpemX; 67 | public byte PpemY; 68 | public byte BitDepth; 69 | public BitmapSizeFlags Flags; 70 | } 71 | 72 | struct IndexSubTable { 73 | public ushort FirstGlyph; 74 | public ushort LastGlyph; 75 | public uint Offset; 76 | } 77 | 78 | [Flags] 79 | enum BitmapSizeFlags { 80 | None, 81 | Horizontal, 82 | Vertical 83 | } 84 | 85 | const int MaxBitmapStrikes = 1024; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SharpFont/Internal/SfntTables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Numerics; 5 | using System.Text; 6 | 7 | namespace SharpFont { 8 | // raw SFNT container table reading routines 9 | unsafe static class SfntTables { 10 | public static uint[] ReadTTCHeader (DataReader reader) { 11 | // read the file header; if we have a collection, we want to 12 | // figure out where all the different faces are in the file 13 | // if we don't have a collection, there's just one font in the file 14 | var tag = reader.ReadUInt32(); 15 | if (tag != FourCC.Ttcf) 16 | return new[] { 0u }; 17 | 18 | // font file is a TrueType collection; read the TTC header 19 | reader.Skip(4); // version number 20 | var count = reader.ReadUInt32BE(); 21 | if (count == 0 || count > MaxFontsInCollection) 22 | throw new InvalidFontException("Invalid TTC header"); 23 | 24 | var offsets = new uint[count]; 25 | for (int i = 0; i < count; i++) 26 | offsets[i] = reader.ReadUInt32BE(); 27 | 28 | return offsets; 29 | } 30 | 31 | public static TableRecord[] ReadFaceHeader (DataReader reader) { 32 | var tag = reader.ReadUInt32BE(); 33 | if (tag != TTFv1 && tag != TTFv2 && tag != FourCC.True) 34 | throw new InvalidFontException("Unknown or unsupported sfnt version."); 35 | 36 | var tableCount = reader.ReadUInt16BE(); 37 | reader.Skip(6); // skip the rest of the header 38 | 39 | // read each font table descriptor 40 | var tables = new TableRecord[tableCount]; 41 | for (int i = 0; i < tableCount; i++) { 42 | tables[i] = new TableRecord { 43 | Tag = reader.ReadUInt32(), 44 | CheckSum = reader.ReadUInt32BE(), 45 | Offset = reader.ReadUInt32BE(), 46 | Length = reader.ReadUInt32BE(), 47 | }; 48 | } 49 | 50 | return tables; 51 | } 52 | 53 | public static void ReadHead (DataReader reader, TableRecord[] tables, out FaceHeader header) { 54 | SeekToTable(reader, tables, FourCC.Head, required: true); 55 | 56 | // 'head' table contains global information for the font face 57 | // we only care about a few fields in it 58 | reader.Skip(sizeof(int) * 4); // version, revision, checksum, magic number 59 | 60 | header = new FaceHeader { 61 | Flags = (HeadFlags)reader.ReadUInt16BE(), 62 | UnitsPerEm = reader.ReadUInt16BE() 63 | }; 64 | if (header.UnitsPerEm == 0) 65 | throw new InvalidFontException("Invalid 'head' table."); 66 | 67 | // skip over created and modified times, bounding box, 68 | // deprecated style bits, direction hints, and size hints 69 | reader.Skip(sizeof(long) * 2 + sizeof(short) * 7); 70 | 71 | header.IndexFormat = (IndexFormat)reader.ReadInt16BE(); 72 | } 73 | 74 | public static void ReadMaxp (DataReader reader, TableRecord[] tables, ref FaceHeader header) { 75 | SeekToTable(reader, tables, FourCC.Maxp, required: true); 76 | 77 | if (reader.ReadInt32BE() != 0x00010000) 78 | throw new InvalidFontException("Font contains an old style maxp table."); 79 | 80 | header.GlyphCount = reader.ReadUInt16BE(); 81 | if (header.GlyphCount > MaxGlyphs) 82 | throw new InvalidFontException("Font contains too many glyphs."); 83 | 84 | // skip maxPoints, maxContours, maxCompositePoints, maxCompositeContours, maxZones 85 | reader.Skip(sizeof(short) * 5); 86 | 87 | header.MaxTwilightPoints = reader.ReadUInt16BE(); 88 | header.MaxStorageLocations = reader.ReadUInt16BE(); 89 | header.MaxFunctionDefs = reader.ReadUInt16BE(); 90 | header.MaxInstructionDefs = reader.ReadUInt16BE(); 91 | header.MaxStackSize = reader.ReadUInt16BE(); 92 | 93 | // sanity checking 94 | if (header.MaxTwilightPoints > MaxTwilightPoints || header.MaxStorageLocations > MaxStorageLocations || 95 | header.MaxFunctionDefs > MaxFunctionDefs || header.MaxInstructionDefs > MaxFunctionDefs || 96 | header.MaxStackSize > MaxStackSize) 97 | throw new InvalidFontException("Font programs have limits that are larger than built-in sanity checks."); 98 | } 99 | 100 | public static MetricsHeader ReadMetricsHeader (DataReader reader) { 101 | // skip over version 102 | reader.Skip(sizeof(int)); 103 | 104 | var header = new MetricsHeader { 105 | Ascender = reader.ReadInt16BE(), 106 | Descender = reader.ReadInt16BE(), 107 | LineGap = reader.ReadInt16BE() 108 | }; 109 | 110 | // skip over advanceWidthMax, minLsb, minRsb, xMaxExtent, caretSlopeRise, 111 | // caretSlopeRun, caretOffset, 4 reserved entries, and metricDataFormat 112 | reader.Skip(sizeof(short) * 12); 113 | 114 | header.MetricCount = reader.ReadUInt16BE(); 115 | return header; 116 | } 117 | 118 | public static MetricsEntry[] ReadMetricsTable (DataReader reader, int glyphCount, int metricCount) { 119 | var results = new MetricsEntry[glyphCount]; 120 | for (int i = 0; i < metricCount; i++) { 121 | results[i] = new MetricsEntry { 122 | Advance = reader.ReadUInt16BE(), 123 | FrontSideBearing = reader.ReadInt16BE() 124 | }; 125 | } 126 | 127 | // there might be an additional array of fsb-only entries 128 | var extraCount = glyphCount - metricCount; 129 | var lastAdvance = results[metricCount - 1].Advance; 130 | for (int i = 0; i < extraCount; i++) { 131 | results[i + metricCount] = new MetricsEntry { 132 | Advance = lastAdvance, 133 | FrontSideBearing = reader.ReadInt16BE() 134 | }; 135 | } 136 | 137 | return results; 138 | } 139 | 140 | public static OS2Data ReadOS2 (DataReader reader, TableRecord[] tables) { 141 | SeekToTable(reader, tables, FourCC.OS_2, required: true); 142 | 143 | // skip over version, xAvgCharWidth 144 | reader.Skip(sizeof(short) * 2); 145 | 146 | var result = new OS2Data { 147 | Weight = (FontWeight)reader.ReadUInt16BE(), 148 | Stretch = (FontStretch)reader.ReadUInt16BE() 149 | }; 150 | 151 | // skip over fsType, ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, ySubscriptYOffset, 152 | // ySuperscriptXSize, ySuperscriptYSize, ySuperscriptXOffset, ySuperscriptXOffset 153 | reader.Skip(sizeof(short) * 9); 154 | 155 | result.StrikeoutSize = reader.ReadInt16BE(); 156 | result.StrikeoutPosition = reader.ReadInt16BE(); 157 | 158 | // skip over sFamilyClass, panose[10], ulUnicodeRange1-4, achVendID[4] 159 | reader.Skip(sizeof(short) + sizeof(int) * 4 + 14); 160 | 161 | // check various style flags 162 | var fsSelection = (FsSelectionFlags)reader.ReadUInt16BE(); 163 | result.Style = (fsSelection & FsSelectionFlags.Italic) != 0 ? FontStyle.Italic : 164 | (fsSelection & FsSelectionFlags.Bold) != 0 ? FontStyle.Bold : 165 | (fsSelection & FsSelectionFlags.Oblique) != 0 ? FontStyle.Oblique : 166 | FontStyle.Regular; 167 | result.IsWWSFont = (fsSelection & FsSelectionFlags.WWS) != 0; 168 | result.UseTypographicMetrics = (fsSelection & FsSelectionFlags.UseTypoMetrics) != 0; 169 | 170 | // skip over usFirstCharIndex, usLastCharIndex 171 | reader.Skip(sizeof(short) * 2); 172 | 173 | result.TypographicAscender = reader.ReadInt16BE(); 174 | result.TypographicDescender = reader.ReadInt16BE(); 175 | result.TypographicLineGap = reader.ReadInt16BE(); 176 | result.WinAscent = reader.ReadUInt16BE(); 177 | result.WinDescent = reader.ReadUInt16BE(); 178 | 179 | // skip over ulCodePageRange1-2 180 | reader.Skip(sizeof(int) * 2); 181 | 182 | result.XHeight = reader.ReadInt16BE(); 183 | result.CapHeight = reader.ReadInt16BE(); 184 | 185 | return result; 186 | } 187 | 188 | public static void ReadPost (DataReader reader, TableRecord[] tables, ref FaceHeader header) { 189 | if (!SeekToTable(reader, tables, FourCC.Post)) 190 | return; 191 | 192 | // skip over version and italicAngle 193 | reader.Skip(sizeof(int) * 2); 194 | 195 | header.UnderlinePosition = reader.ReadInt16BE(); 196 | header.UnderlineThickness = reader.ReadInt16BE(); 197 | header.IsFixedPitch = reader.ReadUInt32BE() != 0; 198 | } 199 | 200 | public static void ReadLoca (DataReader reader, TableRecord[] tables, IndexFormat format, uint* table, int count) { 201 | SeekToTable(reader, tables, FourCC.Loca, required: true); 202 | 203 | if (format == IndexFormat.Short) { 204 | // values are ushort, divided by 2, so we need to shift back 205 | for (int i = 0; i < count; i++) 206 | *table++ = (uint)(reader.ReadUInt16BE() << 1); 207 | } 208 | else { 209 | for (int i = 0; i < count; i++) 210 | *table++ = reader.ReadUInt32BE(); 211 | } 212 | } 213 | 214 | public unsafe static NameData ReadNames (DataReader reader, TableRecord[] tables) { 215 | if (!SeekToTable(reader, tables, FourCC.Name)) 216 | return default(NameData); 217 | 218 | // read header 219 | var currentOffset = reader.Position; 220 | var format = reader.ReadUInt16BE(); 221 | var count = reader.ReadUInt16BE(); 222 | var dataOffset = currentOffset + reader.ReadUInt16BE(); 223 | 224 | // read name records, filtering out non-Unicode and platforms we don't know about 225 | var stringData = stackalloc StringData[count]; 226 | var stringDataCount = 0; 227 | for (int i = 0; i < count; i++) { 228 | var platform = reader.ReadUInt16BE(); 229 | var encoding = reader.ReadUInt16BE(); 230 | var language = reader.ReadUInt16BE(); 231 | var name = reader.ReadUInt16BE(); 232 | var length = reader.ReadUInt16BE(); 233 | var offset = reader.ReadUInt16BE(); 234 | 235 | // we only support Unicode strings 236 | if (platform == PlatformID.Microsoft) { 237 | if (encoding != WindowsEncoding.UnicodeBmp && encoding != WindowsEncoding.UnicodeFull) 238 | continue; 239 | 240 | if (language != CultureInfo.CurrentCulture.LCID) 241 | continue; 242 | } 243 | else if (platform != PlatformID.Unicode) 244 | continue; 245 | 246 | stringData[stringDataCount++] = new StringData { 247 | Name = name, 248 | Offset = offset, 249 | Length = length 250 | }; 251 | } 252 | 253 | // find strings we care about and extract them from the blob 254 | var nameData = new NameData(); 255 | for (int i = 0; i < stringDataCount; i++) { 256 | var data = stringData[i]; 257 | switch (data.Name) { 258 | case NameID.FamilyName: nameData.FamilyName = ExtractString(reader, dataOffset, data); break; 259 | case NameID.SubfamilyName: nameData.SubfamilyName = ExtractString(reader, dataOffset, data); break; 260 | case NameID.UniqueID: nameData.UniqueID = ExtractString(reader, dataOffset, data); break; 261 | case NameID.FullName: nameData.FullName = ExtractString(reader, dataOffset, data); break; 262 | case NameID.Version: nameData.Version = ExtractString(reader, dataOffset, data); break; 263 | case NameID.Description: nameData.Description = ExtractString(reader, dataOffset, data); break; 264 | case NameID.TypographicFamilyName: nameData.TypographicFamilyName = ExtractString(reader, dataOffset, data); break; 265 | case NameID.TypographicSubfamilyName: nameData.TypographicSubfamilyName = ExtractString(reader, dataOffset, data); break; 266 | } 267 | } 268 | 269 | return nameData; 270 | } 271 | 272 | public static FUnit[] ReadCvt (DataReader reader, TableRecord[] tables) { 273 | var index = FindTable(tables, FourCC.Cvt); 274 | if (index == -1) 275 | return null; 276 | 277 | reader.Seek(tables[index].Offset); 278 | 279 | var results = new FUnit[tables[index].Length / sizeof(short)]; 280 | for (int i = 0; i < results.Length; i++) 281 | results[i] = (FUnit)reader.ReadInt16BE(); 282 | 283 | return results; 284 | } 285 | 286 | public static byte[] ReadProgram (DataReader reader, TableRecord[] tables, FourCC tag) { 287 | var index = FindTable(tables, tag); 288 | if (index == -1) 289 | return null; 290 | 291 | reader.Seek(tables[index].Offset); 292 | return reader.ReadBytes((int)tables[index].Length); 293 | } 294 | 295 | public static int FindTable (TableRecord[] tables, FourCC tag) { 296 | var index = -1; 297 | for (int i = 0; i < tables.Length; i++) { 298 | if (tables[i].Tag == tag) { 299 | index = i; 300 | break; 301 | } 302 | } 303 | 304 | return index; 305 | } 306 | 307 | public static bool SeekToTable (DataReader reader, TableRecord[] tables, FourCC tag, bool required = false) { 308 | // check if we have the desired table and that it's not empty 309 | var index = FindTable(tables, tag); 310 | if (index == -1 || tables[index].Length == 0) { 311 | if (required) 312 | throw new InvalidFontException($"Missing or empty '{tag}' table."); 313 | return false; 314 | } 315 | 316 | // seek to the appropriate offset 317 | reader.Seek(tables[index].Offset); 318 | return true; 319 | } 320 | 321 | public static void ReadGlyph ( 322 | DataReader reader, int glyphIndex, int recursionDepth, 323 | BaseGlyph[] glyphTable, uint glyfOffset, uint glyfLength, uint* loca 324 | ) { 325 | // check if this glyph has already been loaded; this can happen 326 | // if we're recursively loading subglyphs as part of a composite 327 | if (glyphTable[glyphIndex] != null) 328 | return; 329 | 330 | // prevent bad font data from causing infinite recursion 331 | if (recursionDepth > MaxRecursion) 332 | throw new InvalidFontException("Bad font data; infinite composite recursion."); 333 | 334 | // check if this glyph doesn't have any actual data 335 | GlyphHeader header; 336 | var offset = loca[glyphIndex]; 337 | if ((glyphIndex < glyphTable.Length - 1 && offset == loca[glyphIndex + 1]) || offset >= glyfLength) { 338 | // this is an empty glyph, so synthesize a header 339 | header = default(GlyphHeader); 340 | } 341 | else { 342 | // seek to the right spot and load the header 343 | reader.Seek(glyfOffset + loca[glyphIndex]); 344 | header = new GlyphHeader { 345 | ContourCount = reader.ReadInt16BE(), 346 | MinX = reader.ReadInt16BE(), 347 | MinY = reader.ReadInt16BE(), 348 | MaxX = reader.ReadInt16BE(), 349 | MaxY = reader.ReadInt16BE() 350 | }; 351 | 352 | if (header.ContourCount < -1 || header.ContourCount > MaxContours) 353 | throw new InvalidFontException("Invalid number of contours for glyph."); 354 | } 355 | 356 | if (header.ContourCount > 0) { 357 | // positive contours means a simple glyph 358 | glyphTable[glyphIndex] = ReadSimpleGlyph(reader, header.ContourCount); 359 | } 360 | else if (header.ContourCount == -1) { 361 | // -1 means composite glyph 362 | var composite = ReadCompositeGlyph(reader); 363 | var subglyphs = composite.Subglyphs; 364 | 365 | // read each subglyph recrusively 366 | for (int i = 0; i < subglyphs.Length; i++) 367 | ReadGlyph(reader, subglyphs[i].Index, recursionDepth + 1, glyphTable, glyfOffset, glyfLength, loca); 368 | 369 | glyphTable[glyphIndex] = composite; 370 | } 371 | else { 372 | // no data, so synthesize an empty glyph 373 | glyphTable[glyphIndex] = new SimpleGlyph { 374 | Points = new Point[0], 375 | ContourEndpoints = new int[0] 376 | }; 377 | } 378 | 379 | // save bounding box 380 | var glyph = glyphTable[glyphIndex]; 381 | glyph.MinX = header.MinX; 382 | glyph.MinY = header.MinY; 383 | glyph.MaxX = header.MaxX; 384 | glyph.MaxY = header.MaxY; 385 | } 386 | 387 | static SimpleGlyph ReadSimpleGlyph (DataReader reader, int contourCount) { 388 | // read contour endpoints 389 | var contours = new int[contourCount]; 390 | var lastEndpoint = reader.ReadUInt16BE(); 391 | contours[0] = lastEndpoint; 392 | for (int i = 1; i < contours.Length; i++) { 393 | var endpoint = reader.ReadUInt16BE(); 394 | contours[i] = endpoint; 395 | if (contours[i] <= lastEndpoint) 396 | throw new InvalidFontException("Glyph contour endpoints are unordered."); 397 | 398 | lastEndpoint = endpoint; 399 | } 400 | 401 | // the last contour's endpoint is the number of points in the glyph 402 | var pointCount = lastEndpoint + 1; 403 | var points = new Point[pointCount]; 404 | 405 | // read instruction data 406 | var instructionLength = reader.ReadUInt16BE(); 407 | var instructions = reader.ReadBytes(instructionLength); 408 | 409 | // read flags 410 | var flags = new SimpleGlyphFlags[pointCount]; 411 | int flagIndex = 0; 412 | while (flagIndex < flags.Length) { 413 | var f = (SimpleGlyphFlags)reader.ReadByte(); 414 | flags[flagIndex++] = f; 415 | 416 | // if Repeat is set, this flag data is repeated n more times 417 | if ((f & SimpleGlyphFlags.Repeat) != 0) { 418 | var count = reader.ReadByte(); 419 | for (int i = 0; i < count; i++) 420 | flags[flagIndex++] = f; 421 | } 422 | } 423 | 424 | // Read points, first doing all X coordinates and then all Y coordinates. 425 | // The point packing is insane; coords are either 1 byte or 2; they're 426 | // deltas from previous point, and flags let you repeat identical points. 427 | var x = 0; 428 | for (int i = 0; i < points.Length; i++) { 429 | var f = flags[i]; 430 | var delta = 0; 431 | 432 | if ((f & SimpleGlyphFlags.ShortX) != 0) { 433 | delta = reader.ReadByte(); 434 | if ((f & SimpleGlyphFlags.SameX) == 0) 435 | delta = -delta; 436 | } 437 | else if ((f & SimpleGlyphFlags.SameX) == 0) 438 | delta = reader.ReadInt16BE(); 439 | 440 | x += delta; 441 | points[i].X = (FUnit)x; 442 | } 443 | 444 | var y = 0; 445 | for (int i = 0; i < points.Length; i++) { 446 | var f = flags[i]; 447 | var delta = 0; 448 | 449 | if ((f & SimpleGlyphFlags.ShortY) != 0) { 450 | delta = reader.ReadByte(); 451 | if ((f & SimpleGlyphFlags.SameY) == 0) 452 | delta = -delta; 453 | } 454 | else if ((f & SimpleGlyphFlags.SameY) == 0) 455 | delta = reader.ReadInt16BE(); 456 | 457 | y += delta; 458 | points[i].Y = (FUnit)y; 459 | points[i].Type = (f & SimpleGlyphFlags.OnCurve) != 0 ? PointType.OnCurve : PointType.Quadratic; 460 | } 461 | 462 | return new SimpleGlyph { 463 | Points = points, 464 | ContourEndpoints = contours, 465 | Instructions = instructions 466 | }; 467 | } 468 | 469 | static CompositeGlyph ReadCompositeGlyph (DataReader reader) { 470 | // we need to keep reading glyphs for as long as 471 | // our flags tell us that there are more to read 472 | var subglyphs = new List(); 473 | 474 | CompositeGlyphFlags flags; 475 | do { 476 | flags = (CompositeGlyphFlags)reader.ReadUInt16BE(); 477 | 478 | var subglyph = new Subglyph { Flags = flags }; 479 | subglyph.Index = reader.ReadUInt16BE(); 480 | 481 | // read in args; they vary in size based on flags 482 | if ((flags & CompositeGlyphFlags.ArgsAreWords) != 0) { 483 | subglyph.Arg1 = reader.ReadInt16BE(); 484 | subglyph.Arg2 = reader.ReadInt16BE(); 485 | } 486 | else { 487 | subglyph.Arg1 = reader.ReadSByte(); 488 | subglyph.Arg2 = reader.ReadSByte(); 489 | } 490 | 491 | // figure out the transform; we can either have no scale, a uniform 492 | // scale, two independent scales, or a full 2x2 transform matrix 493 | // transform components are in 2.14 fixed point format 494 | var transform = Matrix3x2.Identity; 495 | if ((flags & CompositeGlyphFlags.HaveScale) != 0) { 496 | var scale = reader.ReadInt16BE() / F2Dot14ToFloat; 497 | transform.M11 = scale; 498 | transform.M22 = scale; 499 | } 500 | else if ((flags & CompositeGlyphFlags.HaveXYScale) != 0) { 501 | transform.M11 = reader.ReadInt16BE() / F2Dot14ToFloat; 502 | transform.M22 = reader.ReadInt16BE() / F2Dot14ToFloat; 503 | } 504 | else if ((flags & CompositeGlyphFlags.HaveTransform) != 0) { 505 | transform.M11 = reader.ReadInt16BE() / F2Dot14ToFloat; 506 | transform.M12 = reader.ReadInt16BE() / F2Dot14ToFloat; 507 | transform.M21 = reader.ReadInt16BE() / F2Dot14ToFloat; 508 | transform.M22 = reader.ReadInt16BE() / F2Dot14ToFloat; 509 | } 510 | 511 | subglyph.Transform = transform; 512 | subglyphs.Add(subglyph); 513 | 514 | } while ((flags & CompositeGlyphFlags.MoreComponents) != 0); 515 | 516 | var result = new CompositeGlyph { Subglyphs = subglyphs.ToArray() }; 517 | 518 | // if we have instructions, read them now 519 | if ((flags & CompositeGlyphFlags.HaveInstructions) != 0) { 520 | var instructionLength = reader.ReadUInt16BE(); 521 | result.Instructions = reader.ReadBytes(instructionLength); 522 | } 523 | 524 | return result; 525 | } 526 | 527 | static string ExtractString (DataReader reader, uint baseOffset, StringData data) { 528 | reader.Seek(baseOffset + data.Offset); 529 | 530 | var bytes = reader.ReadBytes(data.Length); 531 | return Encoding.BigEndianUnicode.GetString(bytes); 532 | } 533 | 534 | // most of these limits are arbitrary; they can be increased if you 535 | // run into a font in the wild that is constrained by them 536 | const uint TTFv1 = 0x10000; 537 | const uint TTFv2 = 0x20000; 538 | const int MaxGlyphs = short.MaxValue; 539 | const int MaxContours = 256; 540 | const int MaxRecursion = 128; 541 | const int MaxFontsInCollection = 64; 542 | const int MaxStackSize = 16384; 543 | const int MaxTwilightPoints = short.MaxValue; 544 | const int MaxFunctionDefs = 4096; 545 | const int MaxStorageLocations = 16384; 546 | const float F2Dot14ToFloat = 16384.0f; 547 | 548 | [Flags] 549 | enum SimpleGlyphFlags { 550 | None = 0, 551 | OnCurve = 0x1, 552 | ShortX = 0x2, 553 | ShortY = 0x4, 554 | Repeat = 0x8, 555 | SameX = 0x10, 556 | SameY = 0x20 557 | } 558 | 559 | [Flags] 560 | enum FsSelectionFlags { 561 | Italic = 0x1, 562 | Bold = 0x20, 563 | Regular = 0x40, 564 | UseTypoMetrics = 0x80, 565 | WWS = 0x100, 566 | Oblique = 0x200 567 | } 568 | 569 | struct GlyphHeader { 570 | public short ContourCount; 571 | public short MinX; 572 | public short MinY; 573 | public short MaxX; 574 | public short MaxY; 575 | } 576 | 577 | struct StringData { 578 | public ushort Name; 579 | public ushort Offset; 580 | public ushort Length; 581 | } 582 | 583 | static class NameID { 584 | public const int FamilyName = 1; 585 | public const int SubfamilyName = 2; 586 | public const int UniqueID = 3; 587 | public const int FullName = 4; 588 | public const int Version = 5; 589 | public const int Description = 10; 590 | public const int TypographicFamilyName = 16; 591 | public const int TypographicSubfamilyName = 17; 592 | } 593 | } 594 | 595 | struct TableRecord { 596 | public FourCC Tag; 597 | public uint CheckSum; 598 | public uint Offset; 599 | public uint Length; 600 | 601 | public override string ToString () => Tag.ToString(); 602 | } 603 | 604 | struct FaceHeader { 605 | public HeadFlags Flags; 606 | public int UnitsPerEm; 607 | public IndexFormat IndexFormat; 608 | public int UnderlinePosition; 609 | public int UnderlineThickness; 610 | public bool IsFixedPitch; 611 | public int GlyphCount; 612 | public int MaxTwilightPoints; 613 | public int MaxStorageLocations; 614 | public int MaxFunctionDefs; 615 | public int MaxInstructionDefs; 616 | public int MaxStackSize; 617 | } 618 | 619 | struct MetricsHeader { 620 | public int Ascender; 621 | public int Descender; 622 | public int LineGap; 623 | public int MetricCount; 624 | } 625 | 626 | struct MetricsEntry { 627 | public int Advance; 628 | public int FrontSideBearing; 629 | } 630 | 631 | struct OS2Data { 632 | public FontWeight Weight; 633 | public FontStretch Stretch; 634 | public FontStyle Style; 635 | public int StrikeoutSize; 636 | public int StrikeoutPosition; 637 | public int TypographicAscender; 638 | public int TypographicDescender; 639 | public int TypographicLineGap; 640 | public int WinAscent; 641 | public int WinDescent; 642 | public bool UseTypographicMetrics; 643 | public bool IsWWSFont; 644 | public int XHeight; 645 | public int CapHeight; 646 | } 647 | 648 | struct NameData { 649 | public string FamilyName; 650 | public string SubfamilyName; 651 | public string UniqueID; 652 | public string FullName; 653 | public string Version; 654 | public string Description; 655 | public string TypographicFamilyName; 656 | public string TypographicSubfamilyName; 657 | } 658 | 659 | abstract class BaseGlyph { 660 | public byte[] Instructions; 661 | public int MinX; 662 | public int MinY; 663 | public int MaxX; 664 | public int MaxY; 665 | } 666 | 667 | class SimpleGlyph : BaseGlyph { 668 | public Point[] Points; 669 | public int[] ContourEndpoints; 670 | } 671 | 672 | struct Subglyph { 673 | public Matrix3x2 Transform; 674 | public CompositeGlyphFlags Flags; 675 | public int Index; 676 | public int Arg1; 677 | public int Arg2; 678 | } 679 | 680 | class CompositeGlyph : BaseGlyph { 681 | public Subglyph[] Subglyphs; 682 | } 683 | 684 | static class PlatformID { 685 | public const int Unicode = 0; 686 | public const int Microsoft = 3; 687 | } 688 | 689 | static class WindowsEncoding { 690 | public const int UnicodeBmp = 1; 691 | public const int UnicodeFull = 10; 692 | } 693 | 694 | static class UnicodeEncoding { 695 | public const int Unicode32 = 4; 696 | } 697 | 698 | enum IndexFormat { 699 | Short, 700 | Long 701 | } 702 | 703 | [Flags] 704 | enum CompositeGlyphFlags { 705 | None = 0, 706 | ArgsAreWords = 0x1, 707 | ArgsAreXYValues = 0x2, 708 | RoundXYToGrid = 0x4, 709 | HaveScale = 0x8, 710 | MoreComponents = 0x20, 711 | HaveXYScale = 0x40, 712 | HaveTransform = 0x80, 713 | HaveInstructions = 0x100, 714 | UseMetrics = 0x200, 715 | ScaledComponentOffset = 0x800 716 | } 717 | 718 | [Flags] 719 | enum HeadFlags { 720 | None = 0, 721 | SimpleBaseline = 0x1, 722 | SimpleLsb = 0x2, 723 | SizeDependentInstructions = 0x4, 724 | IntegerPpem = 0x8, 725 | InstructionsAlterAdvance = 0x10 726 | } 727 | 728 | // helper wrapper around 4CC codes for debugging purposes 729 | struct FourCC { 730 | uint value; 731 | 732 | public FourCC (uint value) { 733 | this.value = value; 734 | } 735 | 736 | public FourCC (string str) { 737 | if (str.Length != 4) 738 | throw new InvalidOperationException("Invalid FourCC code"); 739 | value = str[0] | ((uint)str[1] << 8) | ((uint)str[2] << 16) | ((uint)str[3] << 24); 740 | } 741 | 742 | public override string ToString () { 743 | return new string(new[] { 744 | (char)(value & 0xff), 745 | (char)((value >> 8) & 0xff), 746 | (char)((value >> 16) & 0xff), 747 | (char)(value >> 24) 748 | }); 749 | } 750 | 751 | public static implicit operator FourCC (string value) => new FourCC(value); 752 | public static implicit operator FourCC (uint value) => new FourCC(value); 753 | public static implicit operator uint (FourCC fourCC) => fourCC.value; 754 | 755 | public static readonly FourCC Otto = "OTTO"; 756 | public static readonly FourCC True = "true"; 757 | public static readonly FourCC Ttcf = "ttcf"; 758 | public static readonly FourCC Typ1 = "typ1"; 759 | public static readonly FourCC Head = "head"; 760 | public static readonly FourCC Maxp = "maxp"; 761 | public static readonly FourCC Post = "post"; 762 | public static readonly FourCC OS_2 = "OS/2"; 763 | public static readonly FourCC Hhea = "hhea"; 764 | public static readonly FourCC Hmtx = "hmtx"; 765 | public static readonly FourCC Vhea = "vhea"; 766 | public static readonly FourCC Vmtx = "vmtx"; 767 | public static readonly FourCC Loca = "loca"; 768 | public static readonly FourCC Glyf = "glyf"; 769 | public static readonly FourCC Cmap = "cmap"; 770 | public static readonly FourCC Kern = "kern"; 771 | public static readonly FourCC Name = "name"; 772 | public static readonly FourCC Cvt = "cvt "; 773 | public static readonly FourCC Fpgm = "fpgm"; 774 | public static readonly FourCC Prep = "prep"; 775 | public static readonly FourCC Eblc = "EBLC"; 776 | } 777 | } 778 | -------------------------------------------------------------------------------- /SharpFont/InvalidFontException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace SharpFont { 5 | /// 6 | /// Represents errors that occur due to invalid data in a font file. 7 | /// 8 | [Serializable] 9 | public class InvalidFontException : Exception { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public InvalidFontException () { 14 | } 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The error message that explains the reason for the exception. 20 | public InvalidFontException (string message) 21 | : base(message) { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The error message that explains the reason for the exception. 28 | /// The exception that is the cause of the current exception. 29 | public InvalidFontException (string message, Exception innerException) 30 | : base(message, innerException) { 31 | } 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// The that holds the serialized object data about the exception being thrown. 37 | /// The that contains contextual information about the source or destination. 38 | /// The parameter is null. 39 | /// The class name is null or is zero (0). 40 | protected InvalidFontException (SerializationInfo info, StreamingContext context) 41 | : base(info, context) { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SharpFont/Metrics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | 4 | namespace SharpFont { 5 | /// 6 | /// Contains various metrics that apply to a font face as a whole, scaled for a particular size. 7 | /// 8 | public sealed class FaceMetrics { 9 | /// 10 | /// The distance from the baseline up to the top of the em box. 11 | /// 12 | public readonly float CellAscent; 13 | 14 | /// 15 | /// The distance from the baseline down to the bottom of the em box. 16 | /// 17 | public readonly float CellDescent; 18 | 19 | /// 20 | /// The baseline-to-baseline distance. 21 | /// 22 | public readonly float LineHeight; 23 | 24 | /// 25 | /// The average height of "lowercase" characters. 26 | /// 27 | public readonly float XHeight; 28 | 29 | /// 30 | /// The average height of "uppercase" characters. 31 | /// 32 | public readonly float CapHeight; 33 | 34 | /// 35 | /// The thickness of an underline marker. 36 | /// 37 | public readonly float UnderlineSize; 38 | 39 | /// 40 | /// The distance from the baseline at which to place the underline marker. 41 | /// 42 | public readonly float UnderlinePosition; 43 | 44 | /// 45 | /// The thickness of a strikeout marker. 46 | /// 47 | public readonly float StrikeoutSize; 48 | 49 | /// 50 | /// The distance from the baseline at which to place the strikeout marker. 51 | /// 52 | public readonly float StrikeoutPosition; 53 | 54 | /// 55 | /// Initializes a new instance of the class. 56 | /// 57 | /// The cell ascent. 58 | /// The cell descent. 59 | /// The line height. 60 | /// The average height of "lowercase" characters. 61 | /// The average height of "uppercase" characters. 62 | /// The underline size. 63 | /// The underline position. 64 | /// The strikeout size. 65 | /// The strikeout position. 66 | public FaceMetrics ( 67 | float cellAscent, float cellDescent, float lineHeight, float xHeight, 68 | float capHeight, float underlineSize, float underlinePosition, 69 | float strikeoutSize, float strikeoutPosition 70 | ) { 71 | CellAscent = cellAscent; 72 | CellDescent = cellDescent; 73 | LineHeight = lineHeight; 74 | XHeight = xHeight; 75 | CapHeight = capHeight; 76 | UnderlineSize = underlineSize; 77 | UnderlinePosition = underlinePosition; 78 | StrikeoutSize = strikeoutSize; 79 | StrikeoutPosition = strikeoutPosition; 80 | } 81 | } 82 | 83 | /// 84 | /// Contains metrics for a single glyph. 85 | /// 86 | public struct GlyphMetrics { 87 | /// 88 | /// The leading bearings; this is the offset from the pen at which to position the glyph. 89 | /// 90 | public readonly Vector2 Bearing; 91 | 92 | /// 93 | /// The glyph advance; this is the distance to advance the pen after positioning the glyph. 94 | /// 95 | public readonly float Advance; 96 | 97 | /// 98 | /// The linear advance; this is the original advance distance unaffected by hinting. 99 | /// Use this if you want a guaranteed smooth transition of advances across various pixel sizes. 100 | /// 101 | public readonly float LinearAdvance; 102 | 103 | /// 104 | /// Initializes a new instance of the struct. 105 | /// 106 | /// The bearings. 107 | /// The advance distance. 108 | /// The linear unhinted advance distance. 109 | public GlyphMetrics (Vector2 bearing, float advance, float linearAdvance) { 110 | Bearing = bearing; 111 | Advance = advance; 112 | LinearAdvance = linearAdvance; 113 | } 114 | } 115 | 116 | /// 117 | /// Represents an image surface in memory. 118 | /// 119 | public struct Surface { 120 | /// 121 | /// A pointer to the image data. 122 | /// 123 | public IntPtr Bits { get; set; } 124 | 125 | /// 126 | /// The width of the image, in pixels. 127 | /// 128 | public int Width { get; set; } 129 | 130 | /// 131 | /// The height of the image, in pixels. 132 | /// 133 | public int Height { get; set; } 134 | 135 | /// 136 | /// The width of a row of pixels, in bytes. 137 | /// 138 | public int Pitch { get; set; } 139 | } 140 | 141 | /// 142 | /// Represents a single Unicode codepoint. 143 | /// 144 | public struct CodePoint : IComparable, IEquatable { 145 | readonly int value; 146 | 147 | /// 148 | /// Initializes a new instance of the struct. 149 | /// 150 | /// The 32-bit value of the codepoint. 151 | public CodePoint (int codePoint) { 152 | value = codePoint; 153 | } 154 | 155 | /// 156 | /// Initializes a new instance of the struct. 157 | /// 158 | /// The 16-bit value of the codepoint. 159 | public CodePoint (char character) { 160 | value = character; 161 | } 162 | 163 | /// 164 | /// Initializes a new instance of the struct. 165 | /// 166 | /// The first member of a surrogate pair representing the codepoint. 167 | /// The second member of a surrogate pair representing the codepoint. 168 | public CodePoint (char highSurrogate, char lowSurrogate) { 169 | value = char.ConvertToUtf32(highSurrogate, lowSurrogate); 170 | } 171 | 172 | /// 173 | /// Compares this instance to the specified value. 174 | /// 175 | /// The value to compare. 176 | /// A signed number indicating the relative values of this instance and . 177 | public int CompareTo (CodePoint other) => value.CompareTo(other.value); 178 | 179 | /// 180 | /// Returns a value indicating whether this instance is equal to the specified object. 181 | /// 182 | /// The object to compare. 183 | /// true if this instance equals ; otherwise, false. 184 | public bool Equals (CodePoint other) => value.Equals(other.value); 185 | 186 | /// 187 | /// Returns a value indicating whether this instance is equal to the specified object. 188 | /// 189 | /// The object to compare. 190 | /// true if this instance equals ; otherwise, false. 191 | public override bool Equals (object obj) { 192 | var codepoint = obj as CodePoint?; 193 | if (codepoint == null) 194 | return false; 195 | 196 | return Equals(codepoint); 197 | } 198 | 199 | /// 200 | /// Returns the hash code for this instance. 201 | /// 202 | /// The instance's hashcode. 203 | public override int GetHashCode () => value.GetHashCode(); 204 | 205 | /// 206 | /// Converts the value to its equivalent string representation. 207 | /// 208 | /// 209 | public override string ToString () => $"{value} ({(char)value})"; 210 | 211 | /// 212 | /// Implements the equality operator. 213 | /// 214 | /// The left hand side of the operator. 215 | /// The right hand side of the operator. 216 | /// The result of the operator. 217 | public static bool operator ==(CodePoint left, CodePoint right) => left.Equals(right); 218 | 219 | /// 220 | /// Implements the inequality operator. 221 | /// 222 | /// The left hand side of the operator. 223 | /// The right hand side of the operator. 224 | /// The result of the operator. 225 | public static bool operator !=(CodePoint left, CodePoint right) => !left.Equals(right); 226 | 227 | /// 228 | /// Implements the less-than operator. 229 | /// 230 | /// The left hand side of the operator. 231 | /// The right hand side of the operator. 232 | /// The result of the operator. 233 | public static bool operator <(CodePoint left, CodePoint right) => left.value < right.value; 234 | 235 | /// 236 | /// Implements the greater-than operator. 237 | /// 238 | /// The left hand side of the operator. 239 | /// The right hand side of the operator. 240 | /// The result of the operator. 241 | public static bool operator >(CodePoint left, CodePoint right) => left.value > right.value; 242 | 243 | /// 244 | /// Implements the less-than-or-equal-to operator. 245 | /// 246 | /// The left hand side of the operator. 247 | /// The right hand side of the operator. 248 | /// The result of the operator. 249 | public static bool operator <=(CodePoint left, CodePoint right) => left.value <= right.value; 250 | 251 | /// 252 | /// Implements the greater-than-or-equal-to operator. 253 | /// 254 | /// The left hand side of the operator. 255 | /// The right hand side of the operator. 256 | /// The result of the operator. 257 | public static bool operator >=(CodePoint left, CodePoint right) => left.value >= right.value; 258 | 259 | /// 260 | /// Implements an explicit conversion from integer to . 261 | /// 262 | /// The codepoint value. 263 | public static explicit operator CodePoint (int codePoint) => new CodePoint(codePoint); 264 | 265 | /// 266 | /// Implements an implicit conversion from character to . 267 | /// 268 | /// The character value. 269 | public static implicit operator CodePoint (char character) => new CodePoint(character); 270 | 271 | /// 272 | /// Implements an explicit conversion from to character. 273 | /// 274 | /// The codepoint value. 275 | public static explicit operator char (CodePoint codePoint) => (char)codePoint.value; 276 | } 277 | 278 | /// 279 | /// Specifies various font weights. 280 | /// 281 | public enum FontWeight { 282 | /// 283 | /// The weight is unknown or unspecified. 284 | /// 285 | Unknown = 0, 286 | 287 | /// 288 | /// Very thin. 289 | /// 290 | Thin = 100, 291 | 292 | /// 293 | /// Extra light. 294 | /// 295 | ExtraLight = 200, 296 | 297 | /// 298 | /// Light. 299 | /// 300 | Light = 300, 301 | 302 | /// 303 | /// Normal. 304 | /// 305 | Normal = 400, 306 | 307 | /// 308 | /// Medium. 309 | /// 310 | Medium = 500, 311 | 312 | /// 313 | /// Somewhat bold. 314 | /// 315 | SemiBold = 600, 316 | 317 | /// 318 | /// Bold. 319 | /// 320 | Bold = 700, 321 | 322 | /// 323 | /// Extra bold. 324 | /// 325 | ExtraBold = 800, 326 | 327 | /// 328 | /// Extremely bold. 329 | /// 330 | Black = 900 331 | } 332 | 333 | /// 334 | /// Specifies the font stretching level. 335 | /// 336 | public enum FontStretch { 337 | /// 338 | /// The stretch is unknown or unspecified. 339 | /// 340 | Unknown, 341 | 342 | /// 343 | /// Ultra condensed. 344 | /// 345 | UltraCondensed, 346 | 347 | /// 348 | /// Extra condensed. 349 | /// 350 | ExtraCondensed, 351 | 352 | /// 353 | /// Condensed. 354 | /// 355 | Condensed, 356 | 357 | /// 358 | /// Somewhat condensed. 359 | /// 360 | SemiCondensed, 361 | 362 | /// 363 | /// Normal. 364 | /// 365 | Normal, 366 | 367 | /// 368 | /// Somewhat expanded. 369 | /// 370 | SemiExpanded, 371 | 372 | /// 373 | /// Expanded. 374 | /// 375 | Expanded, 376 | 377 | /// 378 | /// Extra expanded. 379 | /// 380 | ExtraExpanded, 381 | 382 | /// 383 | /// Ultra expanded. 384 | /// 385 | UltraExpanded 386 | } 387 | 388 | /// 389 | /// Specifies various font styles. 390 | /// 391 | public enum FontStyle { 392 | /// 393 | /// No particular styles applied. 394 | /// 395 | Regular, 396 | 397 | /// 398 | /// The font is emboldened. 399 | /// 400 | Bold, 401 | 402 | /// 403 | /// The font is stylistically italic. 404 | /// 405 | Italic, 406 | 407 | /// 408 | /// The font is algorithmically italic / angled. 409 | /// 410 | Oblique 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /SharpFont/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpFont")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpFont")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8492da27-3077-43a2-80b0-d51e7da83bd2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpFont/SharpFont.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8492DA27-3077-43A2-80B0-D51E7DA83BD2} 8 | Library 9 | Properties 10 | SharpFont 11 | SharpFont 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | true 24 | true 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | true 34 | 35 | 36 | 37 | 38 | 39 | False 40 | ..\external\System.Numerics.Vectors\System.Numerics.Vectors.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /SharpFont/TextAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace SharpFont { 7 | public interface IGlyphAtlas { 8 | int Width { get; } 9 | int Height { get; } 10 | 11 | void Insert (int page, int x, int y, int width, int height, IntPtr data); 12 | } 13 | 14 | public unsafe sealed class TextAnalyzer { 15 | [ThreadStatic] 16 | static MemoryBuffer memoryBuffer; 17 | 18 | IGlyphAtlas atlas; 19 | BinPacker packer; 20 | Dictionary cache; 21 | ResizableArray buffer; 22 | int currentPage; 23 | 24 | public int Dpi { 25 | get; 26 | set; 27 | } 28 | 29 | public TextAnalyzer (IGlyphAtlas atlas) { 30 | this.atlas = atlas; 31 | Dpi = 96; 32 | cache = new Dictionary(CacheKey.Comparer); 33 | packer = new BinPacker(atlas.Width, atlas.Height); 34 | buffer = new ResizableArray(32); 35 | } 36 | 37 | public void Clear () => buffer.Clear(); 38 | 39 | public void AppendText (string text, TextFormat format) => AppendText(text, 0, text.Length, format); 40 | 41 | public void AppendText (string text, int startIndex, int count, TextFormat format) { 42 | fixed (char* ptr = text) 43 | AppendText(ptr + startIndex, count, format); 44 | } 45 | 46 | public void AppendText (char[] text, int startIndex, int count, TextFormat format) { 47 | fixed (char* ptr = text) 48 | AppendText(ptr + startIndex, count, format); 49 | } 50 | 51 | public void AppendText (char* text, int count, TextFormat format) { 52 | // look up the cache entry for the given font and size 53 | CachedFace cachedFace; 54 | var font = format.Font; 55 | var size = FontFace.ComputePixelSize(format.Size, Dpi); 56 | var key = new CacheKey(font.Id, size); 57 | if (!cache.TryGetValue(key, out cachedFace)) 58 | cache.Add(key, cachedFace = new CachedFace(font, size)); 59 | 60 | // process each character in the string 61 | var nextBreak = BreakCategory.None; 62 | var previous = new CodePoint(); 63 | char* end = text + count; 64 | while (text != end) { 65 | // handle surrogate pairs properly 66 | CodePoint codePoint; 67 | char c = *text++; 68 | if (char.IsSurrogate(c) && text != end) 69 | codePoint = new CodePoint(c, *text++); 70 | else 71 | codePoint = c; 72 | 73 | // ignore linefeeds directly after a carriage return 74 | if (c == '\n' && (char)previous == '\r') 75 | continue; 76 | 77 | // get the glyph data 78 | CachedGlyph glyph; 79 | if (!cachedFace.Glyphs.TryGetValue(codePoint, out glyph) && !char.IsControl(c)) { 80 | var data = font.GetGlyph(codePoint, size); 81 | var width = data.RenderWidth; 82 | var height = data.RenderHeight; 83 | if (width > atlas.Width || height > atlas.Height) 84 | throw new InvalidOperationException("Glyph is larger than the size of the provided atlas."); 85 | 86 | var rect = new Rect(); 87 | if (width > 0 && height > 0) { 88 | // render the glyph 89 | var memSize = width * height; 90 | var mem = memoryBuffer; 91 | if (mem == null) 92 | memoryBuffer = mem = new MemoryBuffer(memSize); 93 | 94 | mem.Clear(memSize); 95 | data.RenderTo(new Surface { 96 | Bits = mem.Pointer, 97 | Width = width, 98 | Height = height, 99 | Pitch = width 100 | }); 101 | 102 | // save the rasterized glyph in the user's atlas 103 | rect = packer.Insert(width, height); 104 | if (rect.Height == 0) { 105 | // didn't fit in the atlas... start a new sheet 106 | currentPage++; 107 | packer.Clear(atlas.Width, atlas.Height); 108 | rect = packer.Insert(width, height); 109 | if (rect.Height == 0) 110 | throw new InvalidOperationException("Failed to insert glyph into fresh page."); 111 | } 112 | atlas.Insert(currentPage, rect.X, rect.Y, rect.Width, rect.Height, mem.Pointer); 113 | } 114 | 115 | glyph = new CachedGlyph(rect, data.HorizontalMetrics.Bearing, data.HorizontalMetrics.Advance); 116 | cachedFace.Glyphs.Add(codePoint, glyph); 117 | } 118 | 119 | // check for a kerning offset 120 | var kerning = font.GetKerning(previous, codePoint, size); 121 | previous = codePoint; 122 | 123 | // figure out whether this character can serve as a line break point 124 | // TODO: more robust character class handling 125 | var breakCategory = BreakCategory.None; 126 | if (char.IsWhiteSpace(c)) { 127 | if (c == '\r' || c == '\n') 128 | breakCategory = BreakCategory.Mandatory; 129 | else 130 | breakCategory = BreakCategory.Opportunity; 131 | } 132 | 133 | // the previous character might make us think that this one should be a break opportunity 134 | if (nextBreak > breakCategory) 135 | breakCategory = nextBreak; 136 | if (c == '-') 137 | nextBreak = BreakCategory.Opportunity; 138 | 139 | // alright, we have all the right glyph data cached and loaded 140 | // append relevant info to our buffer; we'll do the actual layout later 141 | buffer.Add(new BufferEntry { 142 | GlyphData = glyph, 143 | Kerning = kerning, 144 | Break = breakCategory 145 | }); 146 | } 147 | } 148 | 149 | public void PerformLayout (float x, float y, float width, float height, TextLayout layout) { 150 | layout.SetCount(buffer.Count); 151 | 152 | var pen = new Vector2(x, y); 153 | for (int i = 0; i < buffer.Count; i++) { 154 | var entry = buffer[i]; 155 | if (entry.Break == BreakCategory.Mandatory) { 156 | pen.X = x; 157 | pen.Y += 32; // TODO: line spacing 158 | } 159 | 160 | // data can be null for control characters, 161 | // or for glyphs without image data 162 | var data = entry.GlyphData; 163 | if (data == null) 164 | continue; 165 | 166 | pen.X += entry.Kerning; 167 | layout.AddGlyph( 168 | (int)Math.Round(pen.X + data.Bearing.X), 169 | (int)Math.Round(pen.Y - data.Bearing.Y), 170 | data.Bounds.X, 171 | data.Bounds.Y, 172 | data.Bounds.Width, 173 | data.Bounds.Height 174 | ); 175 | 176 | pen.X += (float)Math.Round(data.AdvanceWidth); 177 | } 178 | } 179 | 180 | struct BufferEntry { 181 | public CachedGlyph GlyphData; 182 | public float Kerning; 183 | public BreakCategory Break; 184 | } 185 | 186 | struct CachedFace { 187 | public FaceMetrics Metrics; 188 | public Dictionary Glyphs; 189 | 190 | public CachedFace (FontFace font, float size) { 191 | Metrics = font.GetFaceMetrics(size); 192 | Glyphs = new Dictionary(); 193 | } 194 | } 195 | 196 | class CachedGlyph { 197 | public Rect Bounds; 198 | public Vector2 Bearing; 199 | public float AdvanceWidth; 200 | 201 | public CachedGlyph (Rect bounds, Vector2 bearing, float advance) { 202 | Bounds = bounds; 203 | Bearing = bearing; 204 | AdvanceWidth = advance; 205 | } 206 | } 207 | 208 | struct CacheKey { 209 | public int Id; 210 | public float Size; 211 | 212 | public CacheKey (int id, float size) { 213 | Id = id; 214 | Size = size; 215 | } 216 | 217 | public static readonly IEqualityComparer Comparer = new CacheKeyComparer(); 218 | 219 | class CacheKeyComparer : IEqualityComparer { 220 | public bool Equals (CacheKey x, CacheKey y) => x.Id == y.Id && x.Size == y.Size; 221 | public int GetHashCode (CacheKey obj) => obj.Id.GetHashCode() ^ obj.Size.GetHashCode(); 222 | } 223 | } 224 | 225 | class MemoryBuffer { 226 | public IntPtr Pointer; 227 | int size; 228 | 229 | public MemoryBuffer (int initialSize) { 230 | size = RoundSize(initialSize); 231 | Pointer = Marshal.AllocHGlobal(size); 232 | } 233 | 234 | public void Clear (int newSize) { 235 | newSize = RoundSize(newSize); 236 | if (newSize > size) { 237 | Pointer = Marshal.ReAllocHGlobal(Pointer, (IntPtr)newSize); 238 | size = newSize; 239 | } 240 | 241 | // clear the memory 242 | for (int* ptr = (int*)Pointer, end = ptr + (newSize >> 2); ptr != end; ptr++) 243 | *ptr = 0; 244 | } 245 | 246 | static int RoundSize (int size) => (size + 3) & ~3; 247 | } 248 | 249 | enum BreakCategory { 250 | None, 251 | Opportunity, 252 | Mandatory 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /SharpFont/TextFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpFont { 8 | public sealed class TextFormat { 9 | public FontFace Font { get; set; } 10 | public float Size { get; set; } 11 | public float TabStop { get; set; } 12 | public TextStyle Style { get; set; } 13 | public TextAlignment LineAlignment { get; set; } 14 | public TextAlignment ParagraphAlignment { get; set; } 15 | public BreakDelimiter WordWrap { get; set; } 16 | public BreakDelimiter Trimming { get; set; } 17 | public char TrimmingRelacement { get; set; } 18 | } 19 | 20 | [Flags] 21 | public enum TextStyle { 22 | None, 23 | Strikeout = 0x1, 24 | Underline = 0x2 25 | } 26 | 27 | public enum TextAlignment { 28 | Near, 29 | Far, 30 | Center 31 | } 32 | 33 | public enum BreakDelimiter { 34 | None, 35 | Character, 36 | Word 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SharpFont/TextLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpFont { 8 | public class TextLayout { 9 | public List Stuff = new List(); 10 | 11 | internal void SetCount (int count) { 12 | Stuff.Clear(); 13 | Stuff.Capacity = count; 14 | } 15 | 16 | internal void AddGlyph (int destX, int destY, int sourceX, int sourceY, int width, int height) { 17 | Stuff.Add(new Data { 18 | DestX = destX, 19 | DestY = destY, 20 | SourceX = sourceX, 21 | SourceY = sourceY, 22 | Width = width, 23 | Height = height 24 | }); 25 | } 26 | 27 | public struct Data { 28 | public int DestX, DestY; 29 | public int SourceX, SourceY; 30 | public int Width, Height; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Test/Program.cs: -------------------------------------------------------------------------------- 1 | using SharpFont; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace Test { 10 | unsafe class Program { 11 | const string ComparisonPath = "../../../../font_rasters/"; 12 | 13 | static void Main (string[] args) { 14 | var typeface = LoadTypeface("../../../Fonts/OpenSans-Regular.ttf"); 15 | 16 | for (int c = 33; c < 127; c++) { 17 | var comparisonFile = Path.Combine(ComparisonPath, c + ".png"); 18 | CompareRender(typeface, (char)c, comparisonFile); 19 | } 20 | 21 | //var surface = RenderGlyph(typeface, 'I'); 22 | //SaveSurface(surface, "result.png"); 23 | } 24 | 25 | static void CompareRender (FontFace typeface, char c, string comparisonFile) { 26 | var surface = RenderGlyph(typeface, c); 27 | 28 | // compare against FreeType renders 29 | var compare = (Bitmap)Image.FromFile(comparisonFile); 30 | if (compare.Width != surface.Width || compare.Height != surface.Height) 31 | throw new Exception(); 32 | 33 | var bitmapData = compare.LockBits(new Rectangle(0, 0, surface.Width, surface.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 34 | for (int y = 0; y < surface.Height; y++) { 35 | var dest = (byte*)bitmapData.Scan0 + y * bitmapData.Stride; 36 | var src = (byte*)surface.Bits + y * surface.Pitch; 37 | 38 | for (int x = 0; x < surface.Width; x++) { 39 | var a = *src++; 40 | var b = *dest; 41 | if (Math.Abs(a - b) > 12) 42 | throw new Exception(); 43 | dest += 3; 44 | } 45 | } 46 | 47 | compare.UnlockBits(bitmapData); 48 | compare.Dispose(); 49 | Marshal.FreeHGlobal(surface.Bits); 50 | } 51 | 52 | static Surface RenderGlyph (FontFace typeface, char c) { 53 | var glyph = typeface.GetGlyph(c, 32); 54 | var surface = new Surface { 55 | Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight), 56 | Width = glyph.RenderWidth, 57 | Height = glyph.RenderHeight, 58 | Pitch = glyph.RenderWidth 59 | }; 60 | 61 | var stuff = (byte*)surface.Bits; 62 | for (int i = 0; i < surface.Width * surface.Height; i++) 63 | *stuff++ = 0; 64 | 65 | glyph.RenderTo(surface); 66 | 67 | return surface; 68 | } 69 | 70 | static void SaveSurface (Surface surface, string fileName) { 71 | var bitmap = new Bitmap(surface.Width, surface.Height, PixelFormat.Format24bppRgb); 72 | var bitmapData = bitmap.LockBits(new Rectangle(0, 0, surface.Width, surface.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); 73 | for (int y = 0; y < surface.Height; y++) { 74 | var dest = (byte*)bitmapData.Scan0 + y * bitmapData.Stride; 75 | var src = (byte*)surface.Bits + y * surface.Pitch; 76 | 77 | for (int x = 0; x < surface.Width; x++) { 78 | var b = *src++; 79 | *dest++ = b; 80 | *dest++ = b; 81 | *dest++ = b; 82 | } 83 | } 84 | 85 | bitmap.UnlockBits(bitmapData); 86 | bitmap.Save(fileName); 87 | bitmap.Dispose(); 88 | Marshal.FreeHGlobal(surface.Bits); 89 | } 90 | 91 | static FontFace LoadTypeface (string fileName) { 92 | using (var file = File.OpenRead(fileName)) 93 | return new FontFace(file); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b7428745-ad58-411b-b8be-75b1393d50b5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B7428745-AD58-411B-B8BE-75B1393D50B5} 8 | Exe 9 | Properties 10 | Test 11 | Test 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | true 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {8492da27-3077-43a2-80b0-d51e7da83bd2} 49 | SharpFont 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /external/System.Numerics.Vectors/System.Numerics.Vectors.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/external/System.Numerics.Vectors/System.Numerics.Vectors.dll -------------------------------------------------------------------------------- /external/System.Numerics.Vectors/System.Numerics.Vectors.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/external/System.Numerics.Vectors/System.Numerics.Vectors.pdb -------------------------------------------------------------------------------- /external/bgfx/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010-2014 Branimir Karadzic. All rights reserved. 2 | 3 | https://github.com/bkaradzic/bgfx 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 17 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 18 | SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 23 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 24 | OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | https://github.com/bkaradzic/bgfx/blob/master/LICENSE 27 | -------------------------------------------------------------------------------- /external/bgfx/bgfx.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/external/bgfx/bgfx.dll -------------------------------------------------------------------------------- /external/bgfx/bgfx_debug.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikePopoloski/SharpFont/b28555e8fae94c57f1b5ccd809cdd1260f0eb55f/external/bgfx/bgfx_debug.dll --------------------------------------------------------------------------------