diff --git a/Cargo.lock b/Cargo.lock index 7a033a1..4b299f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,6 +874,7 @@ dependencies = [ "ratatui", "tempfile", "thiserror 2.0.21", + "unicode-width", ] [[package]] diff --git a/crates/ghostty-font/Cargo.toml b/crates/ghostty-font/Cargo.toml index 4229cc3..8140e6f 100644 --- a/crates/ghostty-font/Cargo.toml +++ b/crates/ghostty-font/Cargo.toml @@ -9,6 +9,41 @@ tiny-skia = "0.12.0" png = "0.18.1" thiserror = "2.0.21" log = "0.4.34" +freetype-sys = "0.23.0" +harfbuzz-sys = "0.8.0" +ghostty-unicode.workspace = true +bitflags = "2.13.2" +yeslogic-fontconfig-sys = "6.0.1" +ghostty-datastruct.workspace = true -[lints] -workspace = true +# This crate wraps the freetype / harfbuzz / fontconfig C libraries through +# their `-sys` crates; unsafe is denied by default and allowed on minimal, +# `// SAFETY:`-commented items only. +[lints.rust] +unsafe_code = "deny" + +[lints.clippy] +pedantic = { level = "warn", priority = -1 } +todo = "warn" +unwrap_used = "warn" +cast_possible_truncation = "allow" +cast_possible_wrap = "allow" +cast_sign_loss = "allow" +cast_precision_loss = "allow" +cast_lossless = "allow" +module_name_repetitions = "allow" +too_many_lines = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +similar_names = "allow" +struct_excessive_bools = "allow" +fn_params_excessive_bools = "allow" +match_same_arms = "allow" +unreadable_literal = "allow" +many_single_char_names = "allow" +doc_markdown = "allow" +must_use_candidate = "allow" +return_self_not_must_use = "allow" +used_underscore_binding = "allow" +large_stack_arrays = "allow" +inline_always = "allow" diff --git a/crates/ghostty-font/res/BSD-2-Clause.txt b/crates/ghostty-font/res/BSD-2-Clause.txt new file mode 100644 index 0000000..4387948 --- /dev/null +++ b/crates/ghostty-font/res/BSD-2-Clause.txt @@ -0,0 +1,24 @@ +Copyright (c) 2018-2024, Frederic Cambus +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/crates/ghostty-font/res/CodeNewRoman-Regular.otf b/crates/ghostty-font/res/CodeNewRoman-Regular.otf new file mode 100644 index 0000000..dcc7c35 Binary files /dev/null and b/crates/ghostty-font/res/CodeNewRoman-Regular.otf differ diff --git a/crates/ghostty-font/res/CozetteVector.ttf b/crates/ghostty-font/res/CozetteVector.ttf new file mode 100644 index 0000000..05ba976 Binary files /dev/null and b/crates/ghostty-font/res/CozetteVector.ttf differ diff --git a/crates/ghostty-font/res/GeistMono-Regular.ttf b/crates/ghostty-font/res/GeistMono-Regular.ttf new file mode 100644 index 0000000..a909c5d Binary files /dev/null and b/crates/ghostty-font/res/GeistMono-Regular.ttf differ diff --git a/crates/ghostty-font/res/Inconsolata-Regular.ttf b/crates/ghostty-font/res/Inconsolata-Regular.ttf new file mode 100755 index 0000000..20251d9 Binary files /dev/null and b/crates/ghostty-font/res/Inconsolata-Regular.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-Bold.ttf b/crates/ghostty-font/res/JetBrainsMono-Bold.ttf new file mode 100644 index 0000000..8c93043 Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-Bold.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-BoldItalic.ttf b/crates/ghostty-font/res/JetBrainsMono-BoldItalic.ttf new file mode 100644 index 0000000..1ddf216 Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-BoldItalic.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-Italic-Variable.ttf b/crates/ghostty-font/res/JetBrainsMono-Italic-Variable.ttf new file mode 100644 index 0000000..5414835 Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-Italic-Variable.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-Italic.ttf b/crates/ghostty-font/res/JetBrainsMono-Italic.ttf new file mode 100644 index 0000000..ccc9d6a Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-Italic.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-OFL.txt b/crates/ghostty-font/res/JetBrainsMono-OFL.txt new file mode 100644 index 0000000..8bee414 --- /dev/null +++ b/crates/ghostty-font/res/JetBrainsMono-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/crates/ghostty-font/res/JetBrainsMono-Regular.ttf b/crates/ghostty-font/res/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..dff66cc Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-Regular.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMono-Variable.ttf b/crates/ghostty-font/res/JetBrainsMono-Variable.ttf new file mode 100644 index 0000000..b60e77f Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMono-Variable.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMonoNerdFont-Regular.ttf b/crates/ghostty-font/res/JetBrainsMonoNerdFont-Regular.ttf new file mode 100644 index 0000000..a603188 Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMonoNerdFont-Regular.ttf differ diff --git a/crates/ghostty-font/res/JetBrainsMonoNoNF-Regular.ttf b/crates/ghostty-font/res/JetBrainsMonoNoNF-Regular.ttf new file mode 100644 index 0000000..dff66cc Binary files /dev/null and b/crates/ghostty-font/res/JetBrainsMonoNoNF-Regular.ttf differ diff --git a/crates/ghostty-font/res/JuliaMono-Regular.ttf b/crates/ghostty-font/res/JuliaMono-Regular.ttf new file mode 100644 index 0000000..605230a Binary files /dev/null and b/crates/ghostty-font/res/JuliaMono-Regular.ttf differ diff --git a/crates/ghostty-font/res/KawkabMono-Regular.ttf b/crates/ghostty-font/res/KawkabMono-Regular.ttf new file mode 100644 index 0000000..4841678 Binary files /dev/null and b/crates/ghostty-font/res/KawkabMono-Regular.ttf differ diff --git a/crates/ghostty-font/res/MIT.txt b/crates/ghostty-font/res/MIT.txt new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/crates/ghostty-font/res/MIT.txt @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/ghostty-font/res/MonaspaceNeon-Regular.otf b/crates/ghostty-font/res/MonaspaceNeon-Regular.otf new file mode 100644 index 0000000..75aeb62 Binary files /dev/null and b/crates/ghostty-font/res/MonaspaceNeon-Regular.otf differ diff --git a/crates/ghostty-font/res/NerdFontsSymbols-LICENSE b/crates/ghostty-font/res/NerdFontsSymbols-LICENSE new file mode 100644 index 0000000..06eb073 --- /dev/null +++ b/crates/ghostty-font/res/NerdFontsSymbols-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ryan L McIntyre + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/ghostty-font/res/NotoColorEmoji.ttf b/crates/ghostty-font/res/NotoColorEmoji.ttf new file mode 100755 index 0000000..2c1f104 Binary files /dev/null and b/crates/ghostty-font/res/NotoColorEmoji.ttf differ diff --git a/crates/ghostty-font/res/NotoEmoji-Regular.ttf b/crates/ghostty-font/res/NotoEmoji-Regular.ttf new file mode 100755 index 0000000..850d972 Binary files /dev/null and b/crates/ghostty-font/res/NotoEmoji-Regular.ttf differ diff --git a/crates/ghostty-font/res/OFL.txt b/crates/ghostty-font/res/OFL.txt new file mode 100644 index 0000000..e1fa6e8 --- /dev/null +++ b/crates/ghostty-font/res/OFL.txt @@ -0,0 +1,91 @@ +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/crates/ghostty-font/res/README-upstream.md b/crates/ghostty-font/res/README-upstream.md new file mode 100644 index 0000000..b4d77a7 --- /dev/null +++ b/crates/ghostty-font/res/README-upstream.md @@ -0,0 +1,39 @@ +# Fonts and Licenses + +This project uses several fonts which fall under the SIL Open Font License (OFL-1.1), MIT License, and BSD 2-Clause License: + +- Code New Roman (OFL-1.1) + - [© 2014 Sam Radian. All Rights Reserved.](https://github.com/chrissimpkins/codeface/blob/master/fonts/code-new-roman/license.txt) +- Geist Mono (OFL-1.1) + - [Copyright (c) 2023 Vercel, in collaboration with basement.studio](https://github.com/vercel/geist-font/blob/main/LICENSE.txt) +- Inconsolata (OFL-1.1) + - [Copyright 2006 The Inconsolata Project Authors](https://github.com/google/fonts/blob/main/ofl/inconsolata/OFL.txt) +- JetBrains Mono (OFL-1.1) + - [Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)](https://github.com/JetBrains/JetBrainsMono/blob/master/OFL.txt) +- JuliaMono (OFL-1.1) + - [Copyright (c) 2020 - 2023, cormullion + with Reserved Font Name JuliaMono.](https://github.com/cormullion/juliamono/blob/master/LICENSE) +- Kawkab Mono (OFL-1.1) + - [Copyright (c) 2015, Abdullah Arif (abdullah.a@gmail.com). Copyright 2010, 2012, 2014 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. + ](https://github.com/aiaf/kawkab-mono/blob/master/OFL.txt) +- Lilex (OFL-1.1) + - [Copyright 2019 The Lilex Project Authors (https://github.com/mishamyrt/Lilex)](https://github.com/mishamyrt/Lilex/blob/master/OFL.txt) +- Monaspace Neon (OFL-1.1) + - [Copyright (c) 2023, GitHub https://github.com/githubnext/monaspace + with Reserved Font Name "Monaspace", including subfamilies: "Argon", "Neon", "Xenon", "Radon", and "Krypton"](https://github.com/githubnext/monaspace/blob/main/LICENSE) +- Noto Emoji (OFL-1.1) + - [Copyright 2013 Google LLC](https://github.com/googlefonts/noto-emoji/blob/main/LICENSE) +- Cozette (MIT) + - [Copyright (c) 2020, Slavfox](https://github.com/slavfox/Cozette/blob/main/LICENSE) +- Terminus TTF (OFL-1.1) + - [Copyright (c) 2010-2020 Dimitar Toshkov Zhekov with Reserved Font Name "Terminus Font"](https://sourceforge.net/projects/terminus-font/) + - [Copyright (c) 2011-2023 Tilman Blumenbach with Reserved Font Name "Terminus (TTF)"](https://files.ax86.net/terminus-ttf/) +- Spleen (BSD 2-Clause) + - [Copyright (c) 2018-2024, Frederic Cambus](https://github.com/fcambus/spleen) + +A full copy of the OFL license can be found at [OFL.txt](./OFL.txt). +An accompanying FAQ is also available at . + +A full copy of the MIT license can be found at [MIT.txt](./MIT.txt). + +A full copy of the BSD 2-Clause license can be found at [BSD-2-Clause.txt](./BSD-2-Clause.txt). diff --git a/crates/ghostty-font/res/SymbolsNerdFont-Regular.ttf b/crates/ghostty-font/res/SymbolsNerdFont-Regular.ttf new file mode 100644 index 0000000..38c24fb Binary files /dev/null and b/crates/ghostty-font/res/SymbolsNerdFont-Regular.ttf differ diff --git a/crates/ghostty-font/res/TerminusTTF-Regular.ttf b/crates/ghostty-font/res/TerminusTTF-Regular.ttf new file mode 100644 index 0000000..d125e63 Binary files /dev/null and b/crates/ghostty-font/res/TerminusTTF-Regular.ttf differ diff --git a/crates/ghostty-font/res/spleen-8x16.bdf b/crates/ghostty-font/res/spleen-8x16.bdf new file mode 100644 index 0000000..5c7c268 --- /dev/null +++ b/crates/ghostty-font/res/spleen-8x16.bdf @@ -0,0 +1,22328 @@ +STARTFONT 2.1 +COMMENT /* +COMMENT * Spleen 8x16 2.1.0 +COMMENT * Copyright (c) 2018-2024, Frederic Cambus +COMMENT * https://www.cambus.net/ +COMMENT * +COMMENT * Created: 2018-08-11 +COMMENT * Last Updated: 2024-01-27 +COMMENT * +COMMENT * Spleen is released under the BSD 2-Clause license. +COMMENT * See LICENSE file for details. +COMMENT * +COMMENT * SPDX-License-Identifier: BSD-2-Clause +COMMENT */ +FONT -misc-spleen-medium-r-normal--16-160-72-72-C-80-ISO10646-1 +SIZE 16 72 72 +FONTBOUNDINGBOX 8 16 0 -4 +STARTPROPERTIES 20 +FAMILY_NAME "Spleen" +WEIGHT_NAME "Medium" +FONT_VERSION "2.1.0" +FOUNDRY "misc" +SLANT "R" +SETWIDTH_NAME "Normal" +PIXEL_SIZE 16 +POINT_SIZE 160 +RESOLUTION_X 72 +RESOLUTION_Y 72 +SPACING "C" +AVERAGE_WIDTH 80 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +MIN_SPACE 8 +FONT_ASCENT 12 +FONT_DESCENT 4 +COPYRIGHT "Copyright (c) 2018-2024, Frederic Cambus" +DEFAULT_CHAR 32 +_GBDFED_INFO "Edited with gbdfed 1.6." +ENDPROPERTIES +CHARS 969 +STARTCHAR SPACE +ENCODING 32 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR EXCLAMATION MARK +ENCODING 33 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +18 +18 +18 +18 +18 +00 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR QUOTATION MARK +ENCODING 34 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR NUMBER SIGN +ENCODING 35 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +FE +6C +6C +6C +6C +FE +6C +6C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOLLAR SIGN +ENCODING 36 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +7E +D0 +D0 +D0 +7C +16 +16 +16 +16 +FC +10 +00 +00 +00 +ENDCHAR +STARTCHAR PERCENT SIGN +ENCODING 37 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +06 +66 +6C +0C +18 +18 +30 +36 +66 +60 +00 +00 +00 +00 +ENDCHAR +STARTCHAR AMPERSAND +ENCODING 38 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +38 +6C +6C +6C +38 +70 +DA +CC +CC +7A +00 +00 +00 +00 +ENDCHAR +STARTCHAR APOSTROPHE +ENCODING 39 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +18 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT PARENTHESIS +ENCODING 40 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0E +18 +30 +30 +60 +60 +60 +60 +30 +30 +18 +0E +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT PARENTHESIS +ENCODING 41 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +70 +18 +0C +0C +06 +06 +06 +06 +0C +0C +18 +70 +00 +00 +00 +ENDCHAR +STARTCHAR ASTERISK +ENCODING 42 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +66 +3C +18 +FF +18 +3C +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR PLUS SIGN +ENCODING 43 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +18 +18 +7E +18 +18 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR COMMA +ENCODING 44 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +18 +18 +30 +00 +00 +00 +ENDCHAR +STARTCHAR HYPHEN-MINUS +ENCODING 45 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +7E +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR FULL STOP +ENCODING 46 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SOLIDUS +ENCODING 47 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +0C +0C +18 +18 +30 +30 +60 +60 +C0 +C0 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT ZERO +ENCODING 48 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +CE +DE +F6 +E6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT ONE +ENCODING 49 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +38 +78 +58 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT TWO +ENCODING 50 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +06 +06 +0C +18 +30 +60 +C6 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT THREE +ENCODING 51 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +06 +06 +3C +06 +06 +06 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT FOUR +ENCODING 52 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +CC +CC +CC +CC +FE +0C +0C +0C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT FIVE +ENCODING 53 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +C6 +C0 +C0 +FC +06 +06 +06 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT SIX +ENCODING 54 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C0 +C0 +FC +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT SEVEN +ENCODING 55 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +C6 +06 +06 +0C +18 +30 +30 +30 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT EIGHT +ENCODING 56 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +7C +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIGIT NINE +ENCODING 57 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +C6 +7E +06 +06 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR COLON +ENCODING 58 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +18 +18 +00 +00 +00 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SEMICOLON +ENCODING 59 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +18 +18 +00 +00 +00 +18 +18 +30 +00 +00 +00 +ENDCHAR +STARTCHAR LESS-THAN SIGN +ENCODING 60 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +06 +0C +18 +30 +60 +60 +30 +18 +0C +06 +00 +00 +00 +00 +ENDCHAR +STARTCHAR EQUALS SIGN +ENCODING 61 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +00 +00 +7E +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREATER-THAN SIGN +ENCODING 62 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +60 +30 +18 +0C +06 +06 +0C +18 +30 +60 +00 +00 +00 +00 +ENDCHAR +STARTCHAR QUESTION MARK +ENCODING 63 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +06 +0C +18 +30 +30 +00 +30 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR COMMERCIAL AT +ENCODING 64 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +C2 +DA +DA +DA +DA +DE +C0 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A +ENCODING 65 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER B +ENCODING 66 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C +ENCODING 67 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER D +ENCODING 68 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E +ENCODING 69 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER F +ENCODING 70 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G +ENCODING 71 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER H +ENCODING 72 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I +ENCODING 73 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +18 +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER J +ENCODING 74 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +18 +18 +18 +18 +18 +18 +18 +18 +F0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER K +ENCODING 75 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +CC +F8 +CC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L +ENCODING 76 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER M +ENCODING 77 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +EE +FE +D6 +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER N +ENCODING 78 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O +ENCODING 79 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER P +ENCODING 80 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Q +ENCODING 81 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +D6 +D6 +7C +18 +0C +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R +ENCODING 82 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER S +ENCODING 83 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +7C +06 +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER T +ENCODING 84 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FF +18 +18 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U +ENCODING 85 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER V +ENCODING 86 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +6C +38 +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER W +ENCODING 87 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +D6 +FE +EE +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER X +ENCODING 88 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +6C +38 +6C +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Y +ENCODING 89 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Z +ENCODING 90 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +06 +06 +0C +18 +30 +60 +C0 +C0 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT SQUARE BRACKET +ENCODING 91 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +3E +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +3E +00 +00 +00 +ENDCHAR +STARTCHAR REVERSE SOLIDUS +ENCODING 92 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +C0 +C0 +60 +60 +30 +30 +18 +18 +0C +0C +06 +06 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT SQUARE BRACKET +ENCODING 93 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +0C +0C +0C +0C +0C +0C +0C +0C +0C +0C +7C +00 +00 +00 +ENDCHAR +STARTCHAR CIRCUMFLEX ACCENT +ENCODING 94 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +C6 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LOW LINE +ENCODING 95 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FE +00 +ENDCHAR +STARTCHAR GRAVE ACCENT +ENCODING 96 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +30 +18 +0C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A +ENCODING 97 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER B +ENCODING 98 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C +ENCODING 99 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER D +ENCODING 100 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +06 +06 +06 +7E +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E +ENCODING 101 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER F +ENCODING 102 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +1E +30 +30 +30 +7C +30 +30 +30 +30 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G +ENCODING 103 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER H +ENCODING 104 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I +ENCODING 105 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER J +ENCODING 106 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +18 +18 +18 +18 +18 +18 +18 +18 +18 +70 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER K +ENCODING 107 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +CC +D8 +F0 +F0 +D8 +CC +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L +ENCODING 108 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER M +ENCODING 109 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +EC +D6 +D6 +D6 +D6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N +ENCODING 110 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O +ENCODING 111 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER P +ENCODING 112 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +FC +C0 +C0 +C0 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Q +ENCODING 113 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R +ENCODING 114 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER S +ENCODING 115 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C0 +C0 +7C +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER T +ENCODING 116 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +7C +30 +30 +30 +30 +30 +1E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U +ENCODING 117 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER V +ENCODING 118 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +6C +38 +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER W +ENCODING 119 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +D6 +D6 +D6 +D6 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER X +ENCODING 120 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +6C +38 +38 +6C +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Y +ENCODING 121 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Z +ENCODING 122 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FE +06 +0C +18 +30 +60 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT CURLY BRACKET +ENCODING 123 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0E +18 +18 +18 +18 +70 +70 +18 +18 +18 +18 +0E +00 +00 +00 +ENDCHAR +STARTCHAR VERTICAL LINE +ENCODING 124 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT CURLY BRACKET +ENCODING 125 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +70 +18 +18 +18 +18 +0E +0E +18 +18 +18 +18 +70 +00 +00 +00 +ENDCHAR +STARTCHAR TILDE +ENCODING 126 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +32 +7E +4C +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR +ENCODING 127 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR NO-BREAK SPACE +ENCODING 160 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR INVERTED EXCLAMATION MARK +ENCODING 161 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CENT SIGN +ENCODING 162 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +08 +7E +C8 +C8 +C8 +C8 +C8 +7E +08 +00 +00 +00 +ENDCHAR +STARTCHAR POUND SIGN +ENCODING 163 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +38 +6C +60 +60 +60 +F8 +60 +60 +C0 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR CURRENCY SIGN +ENCODING 164 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +3C +66 +66 +66 +3C +66 +00 +00 +00 +00 +ENDCHAR +STARTCHAR YEN SIGN +ENCODING 165 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C3 +C3 +66 +3C +18 +3C +18 +3C +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BROKEN BAR +ENCODING 166 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +18 +18 +18 +00 +00 +18 +18 +18 +18 +18 +00 +00 +ENDCHAR +STARTCHAR SECTION SIGN +ENCODING 167 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +3C +66 +60 +30 +3C +66 +66 +66 +66 +3C +0C +06 +66 +3C +00 +ENDCHAR +STARTCHAR DIAERESIS +ENCODING 168 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR COPYRIGHT SIGN +ENCODING 169 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +82 +9A +A2 +A2 +A2 +9A +82 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR FEMININE ORDINAL INDICATOR +ENCODING 170 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +0C +3C +4C +3C +00 +7C +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +ENCODING 171 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +33 +66 +CC +66 +33 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR NOT SIGN +ENCODING 172 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FE +06 +06 +06 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SOFT HYPHEN +ENCODING 173 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +3C +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR REGISTERED SIGN +ENCODING 174 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +82 +BA +AA +B2 +AA +AA +82 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR MACRON +ENCODING 175 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DEGREE SIGN +ENCODING 176 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +38 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR PLUS-MINUS SIGN +ENCODING 177 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +18 +18 +7E +18 +18 +00 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR SUPERSCRIPT TWO +ENCODING 178 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +4C +0C +38 +60 +7C +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SUPERSCRIPT THREE +ENCODING 179 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +1C +26 +0C +06 +26 +1C +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR ACUTE ACCENT +ENCODING 180 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR MICRO SIGN +ENCODING 181 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +CC +CC +CC +CC +CC +CC +F6 +C0 +C0 +C0 +00 +ENDCHAR +STARTCHAR PILCROW SIGN +ENCODING 182 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +D6 +D6 +D6 +76 +16 +16 +16 +16 +16 +00 +00 +00 +00 +ENDCHAR +STARTCHAR MIDDLE DOT +ENCODING 183 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +18 +18 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CEDILLA +ENCODING 184 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +18 +18 +30 +00 +ENDCHAR +STARTCHAR SUPERSCRIPT ONE +ENCODING 185 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +38 +18 +18 +18 +3C +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR MASCULINE ORDINAL INDICATOR +ENCODING 186 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +38 +00 +00 +7C +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +ENCODING 187 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +CC +66 +33 +66 +CC +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR VULGAR FRACTION ONE QUARTER +ENCODING 188 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +40 +C0 +40 +42 +46 +EC +18 +30 +70 +D4 +94 +1E +04 +04 +00 +ENDCHAR +STARTCHAR VULGAR FRACTION ONE HALF +ENCODING 189 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +40 +C0 +40 +42 +46 +EC +18 +30 +6C +D2 +82 +0C +10 +1E +00 +ENDCHAR +STARTCHAR VULGAR FRACTION THREE QUARTERS +ENCODING 190 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +90 +20 +12 +96 +6C +18 +30 +70 +D4 +94 +1E +04 +04 +00 +ENDCHAR +STARTCHAR INVERTED QUESTION MARK +ENCODING 191 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +18 +18 +30 +60 +C0 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH GRAVE +ENCODING 192 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +30 +18 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH ACUTE +ENCODING 193 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH CIRCUMFLEX +ENCODING 194 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH TILDE +ENCODING 195 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH DIAERESIS +ENCODING 196 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH RING ABOVE +ENCODING 197 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +38 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER AE +ENCODING 198 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +D8 +D8 +D8 +FE +D8 +D8 +D8 +D8 +DE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C WITH CEDILLA +ENCODING 199 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH GRAVE +ENCODING 200 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +30 +18 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH ACUTE +ENCODING 201 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH CIRCUMFLEX +ENCODING 202 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH DIAERESIS +ENCODING 203 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH GRAVE +ENCODING 204 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +30 +18 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH ACUTE +ENCODING 205 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +0C +18 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH CIRCUMFLEX +ENCODING 206 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH DIAERESIS +ENCODING 207 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +66 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER ETH +ENCODING 208 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +66 +66 +66 +F6 +66 +66 +66 +66 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER N WITH TILDE +ENCODING 209 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH GRAVE +ENCODING 210 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +30 +18 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH ACUTE +ENCODING 211 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH CIRCUMFLEX +ENCODING 212 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH TILDE +ENCODING 213 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH DIAERESIS +ENCODING 214 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR MULTIPLICATION SIGN +ENCODING 215 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +6C +38 +38 +6C +C6 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH STROKE +ENCODING 216 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +02 +7C +C6 +CE +CE +D6 +D6 +E6 +E6 +C6 +7C +80 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH GRAVE +ENCODING 217 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +30 +18 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH ACUTE +ENCODING 218 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH CIRCUMFLEX +ENCODING 219 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH DIAERESIS +ENCODING 220 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Y WITH ACUTE +ENCODING 221 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER THORN +ENCODING 222 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +FC +C0 +C0 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER SHARP S +ENCODING 223 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +78 +CC +CC +CC +D8 +CC +C6 +C6 +D6 +DC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH GRAVE +ENCODING 224 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +30 +18 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH ACUTE +ENCODING 225 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH CIRCUMFLEX +ENCODING 226 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH TILDE +ENCODING 227 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +32 +7E +4C +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH DIAERESIS +ENCODING 228 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH RING ABOVE +ENCODING 229 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +38 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER AE +ENCODING 230 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +6E +16 +16 +7E +D0 +D0 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C WITH CEDILLA +ENCODING 231 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH GRAVE +ENCODING 232 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +30 +18 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH ACUTE +ENCODING 233 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH CIRCUMFLEX +ENCODING 234 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH DIAERESIS +ENCODING 235 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH GRAVE +ENCODING 236 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +30 +18 +0C +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH ACUTE +ENCODING 237 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH CIRCUMFLEX +ENCODING 238 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +3C +66 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH DIAERESIS +ENCODING 239 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +66 +66 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER ETH +ENCODING 240 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +70 +D8 +0C +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N WITH TILDE +ENCODING 241 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +32 +7E +4C +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH GRAVE +ENCODING 242 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +30 +18 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH ACUTE +ENCODING 243 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH CIRCUMFLEX +ENCODING 244 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH TILDE +ENCODING 245 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +32 +7E +4C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH DIAERESIS +ENCODING 246 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DIVISION SIGN +ENCODING 247 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +18 +18 +00 +7E +00 +18 +18 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH STROKE +ENCODING 248 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +02 +7C +C6 +CE +D6 +E6 +C6 +7C +80 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH GRAVE +ENCODING 249 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +30 +18 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH ACUTE +ENCODING 250 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH CIRCUMFLEX +ENCODING 251 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH DIAERESIS +ENCODING 252 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Y WITH ACUTE +ENCODING 253 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER THORN +ENCODING 254 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +FC +C0 +C0 +C0 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Y WITH DIAERESIS +ENCODING 255 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH MACRON +ENCODING 256 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH MACRON +ENCODING 257 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH BREVE +ENCODING 258 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH BREVE +ENCODING 259 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH OGONEK +ENCODING 260 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH OGONEK +ENCODING 261 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +06 +7E +C6 +C6 +C6 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C WITH ACUTE +ENCODING 262 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C WITH ACUTE +ENCODING 263 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C WITH CIRCUMFLEX +ENCODING 264 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C WITH CIRCUMFLEX +ENCODING 265 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C WITH DOT ABOVE +ENCODING 266 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C WITH DOT ABOVE +ENCODING 267 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER C WITH CARON +ENCODING 268 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER C WITH CARON +ENCODING 269 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER D WITH CARON +ENCODING 270 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +FC +C6 +C6 +C6 +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER D WITH CARON +ENCODING 271 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +16 +06 +06 +7E +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER D WITH STROKE +ENCODING 272 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +66 +66 +66 +F6 +66 +66 +66 +66 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER D WITH STROKE +ENCODING 273 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +06 +1F +06 +7E +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH MACRON +ENCODING 274 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH MACRON +ENCODING 275 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH BREVE +ENCODING 276 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH BREVE +ENCODING 277 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH DOT ABOVE +ENCODING 278 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH DOT ABOVE +ENCODING 279 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH OGONEK +ENCODING 280 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +C0 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH OGONEK +ENCODING 281 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH CARON +ENCODING 282 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH CARON +ENCODING 283 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH CIRCUMFLEX +ENCODING 284 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH CIRCUMFLEX +ENCODING 285 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH BREVE +ENCODING 286 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH BREVE +ENCODING 287 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH DOT ABOVE +ENCODING 288 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH DOT ABOVE +ENCODING 289 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH CEDILLA +ENCODING 290 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +C6 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH CEDILLA +ENCODING 291 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +30 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER H WITH CIRCUMFLEX +ENCODING 292 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C6 +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER H WITH CIRCUMFLEX +ENCODING 293 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER H WITH STROKE +ENCODING 294 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +FF +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER H WITH STROKE +ENCODING 295 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +F0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH TILDE +ENCODING 296 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH TILDE +ENCODING 297 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +32 +7E +4C +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH MACRON +ENCODING 298 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7E +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH MACRON +ENCODING 299 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7E +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH BREVE +ENCODING 300 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +3C +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH BREVE +ENCODING 301 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +3C +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH OGONEK +ENCODING 302 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +18 +18 +18 +18 +18 +18 +18 +18 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH OGONEK +ENCODING 303 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +38 +18 +18 +18 +18 +18 +1C +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH DOT ABOVE +ENCODING 304 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER DOTLESS I +ENCODING 305 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LIGATURE IJ +ENCODING 306 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +F7 +66 +66 +66 +66 +66 +66 +66 +66 +EC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LIGATURE IJ +ENCODING 307 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +66 +66 +00 +E6 +66 +66 +66 +66 +66 +76 +06 +06 +1C +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER J WITH CIRCUMFLEX +ENCODING 308 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +18 +18 +18 +18 +18 +18 +18 +F0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER J WITH CIRCUMFLEX +ENCODING 309 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +3C +66 +00 +18 +18 +18 +18 +18 +18 +18 +18 +18 +70 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER K WITH CEDILLA +ENCODING 310 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +CC +F8 +CC +C6 +C6 +C6 +C6 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER K WITH CEDILLA +ENCODING 311 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +CC +D8 +F0 +F0 +D8 +CC +C6 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER KRA +ENCODING 312 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +CC +D8 +F0 +D8 +CC +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L WITH ACUTE +ENCODING 313 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L WITH ACUTE +ENCODING 314 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +30 +30 +30 +30 +30 +30 +30 +30 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L WITH CEDILLA +ENCODING 315 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L WITH CEDILLA +ENCODING 316 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +1C +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L WITH CARON +ENCODING 317 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L WITH CARON +ENCODING 318 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +30 +30 +30 +30 +30 +30 +30 +30 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L WITH MIDDLE DOT +ENCODING 319 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +C0 +C0 +C0 +CC +CC +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L WITH MIDDLE DOT +ENCODING 320 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +30 +36 +36 +30 +30 +30 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER L WITH STROKE +ENCODING 321 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +60 +60 +68 +78 +70 +E0 +E0 +60 +60 +3E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER L WITH STROKE +ENCODING 322 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +34 +3C +38 +70 +70 +30 +30 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER N WITH ACUTE +ENCODING 323 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N WITH ACUTE +ENCODING 324 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER N WITH CEDILLA +ENCODING 325 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N WITH CEDILLA +ENCODING 326 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER N WITH CARON +ENCODING 327 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N WITH CARON +ENCODING 328 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +ENCODING 329 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +C0 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER ENG +ENCODING 330 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +E6 +E6 +D6 +D6 +CE +CE +C6 +C6 +06 +06 +0C +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER ENG +ENCODING 331 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +06 +06 +0C +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH MACRON +ENCODING 332 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH MACRON +ENCODING 333 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH BREVE +ENCODING 334 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH BREVE +ENCODING 335 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +ENCODING 336 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +CC +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH DOUBLE ACUTE +ENCODING 337 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +36 +6C +D8 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LIGATURE OE +ENCODING 338 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +D8 +D8 +D8 +DE +D8 +D8 +D8 +D8 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LIGATURE OE +ENCODING 339 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +6E +D6 +D6 +DE +D0 +D0 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R WITH ACUTE +ENCODING 340 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R WITH ACUTE +ENCODING 341 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R WITH CEDILLA +ENCODING 342 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +C6 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R WITH CEDILLA +ENCODING 343 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R WITH CARON +ENCODING 344 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R WITH CARON +ENCODING 345 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER S WITH ACUTE +ENCODING 346 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7E +C0 +C0 +C0 +7C +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER S WITH ACUTE +ENCODING 347 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7E +C0 +C0 +7C +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER S WITH CIRCUMFLEX +ENCODING 348 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +C0 +C0 +C0 +7C +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER S WITH CIRCUMFLEX +ENCODING 349 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +7E +C0 +C0 +7C +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER S WITH CEDILLA +ENCODING 350 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +7C +06 +06 +06 +06 +FC +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER S WITH CEDILLA +ENCODING 351 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C0 +C0 +7C +06 +06 +FC +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER S WITH CARON +ENCODING 352 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7E +C0 +C0 +C0 +7C +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER S WITH CARON +ENCODING 353 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7E +C0 +C0 +7C +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER T WITH CEDILLA +ENCODING 354 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FF +18 +18 +18 +18 +18 +18 +18 +18 +18 +0C +0C +18 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER T WITH CEDILLA +ENCODING 355 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +7C +30 +30 +30 +30 +30 +1E +0C +0C +18 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER T WITH CARON +ENCODING 356 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +FF +18 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER T WITH CARON +ENCODING 357 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +30 +30 +7C +30 +30 +30 +30 +30 +1E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER T WITH STROKE +ENCODING 358 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FF +18 +18 +18 +18 +7E +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER T WITH STROKE +ENCODING 359 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +7C +30 +7C +30 +30 +30 +1E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH TILDE +ENCODING 360 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH TILDE +ENCODING 361 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +32 +7E +4C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH MACRON +ENCODING 362 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH MACRON +ENCODING 363 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH BREVE +ENCODING 364 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH BREVE +ENCODING 365 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH RING ABOVE +ENCODING 366 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +38 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH RING ABOVE +ENCODING 367 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +38 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +ENCODING 368 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +CC +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH DOUBLE ACUTE +ENCODING 369 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +36 +6C +D8 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH OGONEK +ENCODING 370 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH OGONEK +ENCODING 371 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +0C +08 +06 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER W WITH CIRCUMFLEX +ENCODING 372 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C6 +C6 +C6 +C6 +C6 +D6 +FE +EE +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER W WITH CIRCUMFLEX +ENCODING 373 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +C6 +C6 +D6 +D6 +D6 +D6 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +ENCODING 374 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Y WITH CIRCUMFLEX +ENCODING 375 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +10 +38 +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Y WITH DIAERESIS +ENCODING 376 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Z WITH ACUTE +ENCODING 377 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +FE +06 +0C +18 +30 +60 +C0 +C0 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Z WITH ACUTE +ENCODING 378 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +FE +06 +0C +18 +30 +60 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Z WITH DOT ABOVE +ENCODING 379 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +FE +06 +0C +18 +30 +60 +C0 +C0 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Z WITH DOT ABOVE +ENCODING 380 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +FE +06 +0C +18 +30 +60 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER Z WITH CARON +ENCODING 381 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +FE +06 +0C +18 +30 +60 +C0 +C0 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER Z WITH CARON +ENCODING 382 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +FE +06 +0C +18 +30 +60 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER LONG S +ENCODING 383 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +1E +30 +30 +70 +30 +30 +30 +30 +30 +30 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER F WITH HOOK +ENCODING 402 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +0E +1B +1B +18 +18 +18 +7E +18 +18 +18 +D8 +D8 +70 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH CARON +ENCODING 461 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH CARON +ENCODING 462 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH CARON +ENCODING 463 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH CARON +ENCODING 464 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH CARON +ENCODING 465 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH CARON +ENCODING 466 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH CARON +ENCODING 467 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH CARON +ENCODING 468 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER AE WITH MACRON +ENCODING 482 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +7E +D8 +D8 +D8 +FE +D8 +D8 +D8 +DE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER AE WITH MACRON +ENCODING 483 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +6E +16 +16 +7E +D0 +D0 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH CARON +ENCODING 486 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH CARON +ENCODING 487 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER K WITH CARON +ENCODING 488 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C6 +C6 +C6 +CC +F8 +CC +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER K WITH CARON +ENCODING 489 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C0 +C0 +CC +D8 +F0 +F0 +D8 +CC +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH OGONEK +ENCODING 490 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +18 +10 +0C +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH OGONEK +ENCODING 491 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +18 +10 +0C +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH OGONEK AND MACRON +ENCODING 492 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +7C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +18 +10 +0C +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH OGONEK AND MACRON +ENCODING 493 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +18 +10 +0C +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER J WITH CARON +ENCODING 496 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +38 +10 +00 +18 +18 +18 +18 +18 +18 +18 +18 +18 +70 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER G WITH ACUTE +ENCODING 500 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7E +C0 +C0 +C0 +DE +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER G WITH ACUTE +ENCODING 501 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +7E +C6 +C6 +C6 +C6 +C6 +7C +06 +06 +FC +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER AE WITH ACUTE +ENCODING 508 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +00 +7E +D8 +D8 +D8 +FE +D8 +D8 +D8 +DE +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER AE WITH ACUTE +ENCODING 509 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +00 +6E +16 +16 +7E +D0 +D0 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +ENCODING 510 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +30 +02 +7C +CE +CE +D6 +D6 +E6 +E6 +C6 +7C +80 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH STROKE AND ACUTE +ENCODING 511 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0C +18 +30 +02 +7C +C6 +CE +D6 +E6 +C6 +7C +80 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +ENCODING 512 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH DOUBLE GRAVE +ENCODING 513 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH INVERTED BREVE +ENCODING 514 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH INVERTED BREVE +ENCODING 515 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +ENCODING 516 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH DOUBLE GRAVE +ENCODING 517 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH INVERTED BREVE +ENCODING 518 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH INVERTED BREVE +ENCODING 519 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +ENCODING 520 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH DOUBLE GRAVE +ENCODING 521 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER I WITH INVERTED BREVE +ENCODING 522 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +3C +66 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER I WITH INVERTED BREVE +ENCODING 523 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +3C +66 +66 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +ENCODING 524 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH DOUBLE GRAVE +ENCODING 525 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH INVERTED BREVE +ENCODING 526 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH INVERTED BREVE +ENCODING 527 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +ENCODING 528 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R WITH DOUBLE GRAVE +ENCODING 529 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER R WITH INVERTED BREVE +ENCODING 530 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER R WITH INVERTED BREVE +ENCODING 531 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +00 +7E +C6 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +ENCODING 532 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +CC +66 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH DOUBLE GRAVE +ENCODING 533 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +D8 +6C +36 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER U WITH INVERTED BREVE +ENCODING 534 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +38 +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER U WITH INVERTED BREVE +ENCODING 535 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +38 +6C +6C +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER H WITH CARON +ENCODING 542 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C6 +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER H WITH CARON +ENCODING 543 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +10 +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER A WITH DOT ABOVE +ENCODING 550 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER A WITH DOT ABOVE +ENCODING 551 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER E WITH CEDILLA +ENCODING 552 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +C0 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER E WITH CEDILLA +ENCODING 553 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +18 +18 +30 +00 +ENDCHAR +STARTCHAR LATIN CAPITAL LETTER O WITH DOT ABOVE +ENCODING 558 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LATIN SMALL LETTER O WITH DOT ABOVE +ENCODING 559 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR BREVE +ENCODING 728 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOT ABOVE +ENCODING 729 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR OGONEK +ENCODING 731 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +0C +08 +06 +00 +ENDCHAR +STARTCHAR SMALL TILDE +ENCODING 732 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +32 +4C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOUBLE ACUTE ACCENT +ENCODING 733 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +CC +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR COMBINING BREVE +ENCODING 774 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR COMBINING DIAERESIS +ENCODING 776 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK CAPITAL LETTER GAMMA +ENCODING 915 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +C6 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK CAPITAL LETTER THETA +ENCODING 920 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +38 +6C +C6 +C6 +FE +C6 +C6 +C6 +6C +38 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK CAPITAL LETTER SIGMA +ENCODING 931 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +C6 +60 +30 +18 +18 +30 +60 +C6 +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK CAPITAL LETTER PHI +ENCODING 934 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +10 +7C +D6 +D6 +D6 +D6 +D6 +D6 +7C +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK CAPITAL LETTER OMEGA +ENCODING 937 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +38 +6C +C6 +C6 +C6 +C6 +C6 +6C +6C +EE +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER ALPHA +ENCODING 945 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +76 +DC +D8 +D8 +D8 +DC +76 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER DELTA +ENCODING 948 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +1E +30 +18 +0C +3C +66 +66 +66 +66 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER EPSILON +ENCODING 949 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +C6 +C0 +70 +C0 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER PI +ENCODING 960 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FE +6C +6C +6C +6C +6C +6C +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER SIGMA +ENCODING 963 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +D8 +D8 +D8 +D8 +D8 +70 +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER TAU +ENCODING 964 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +18 +18 +18 +18 +18 +0C +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREEK SMALL LETTER PHI +ENCODING 966 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +4C +D6 +D6 +D6 +D6 +D6 +7C +10 +10 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER IO +ENCODING 1025 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +6C +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I +ENCODING 1030 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +18 +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER YI +ENCODING 1031 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +66 +66 +00 +7E +18 +18 +18 +18 +18 +18 +18 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER SHORT U +ENCODING 1038 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER A +ENCODING 1040 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER BE +ENCODING 1041 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C0 +C0 +C0 +FC +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER VE +ENCODING 1042 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C6 +C6 +C6 +C6 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER GHE +ENCODING 1043 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER DE +ENCODING 1044 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +1C +3C +6C +6C +6C +6C +6C +6C +FE +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER IE +ENCODING 1045 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +F8 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER ZHE +ENCODING 1046 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +D6 +D6 +D6 +7C +38 +7C +D6 +D6 +D6 +D6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER ZE +ENCODING 1047 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +06 +06 +06 +3C +06 +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER I +ENCODING 1048 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +CE +CE +D6 +D6 +E6 +E6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER SHORT I +ENCODING 1049 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +6C +38 +00 +C6 +CE +CE +D6 +D6 +E6 +E6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER KA +ENCODING 1050 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +CC +F8 +CC +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER EL +ENCODING 1051 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER EM +ENCODING 1052 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +EE +FE +D6 +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER EN +ENCODING 1053 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +FE +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER O +ENCODING 1054 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER PE +ENCODING 1055 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FE +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER ER +ENCODING 1056 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +C6 +C6 +C6 +FC +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER ES +ENCODING 1057 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER TE +ENCODING 1058 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FF +18 +18 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER U +ENCODING 1059 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER EF +ENCODING 1060 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +10 +7C +D6 +D6 +D6 +D6 +D6 +D6 +7C +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER HA +ENCODING 1061 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +6C +38 +6C +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER TSE +ENCODING 1062 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7F +03 +03 +06 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER CHE +ENCODING 1063 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +7E +06 +06 +06 +06 +06 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER SHA +ENCODING 1064 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER SHCHA +ENCODING 1065 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +D6 +7F +03 +03 +06 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER HARD SIGN +ENCODING 1066 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +F0 +F0 +30 +30 +3C +36 +36 +36 +36 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER YERU +ENCODING 1067 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C6 +C6 +C6 +C6 +F6 +DE +DE +DE +DE +F6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER SOFT SIGN +ENCODING 1068 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +30 +30 +30 +30 +3C +36 +36 +36 +36 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER E +ENCODING 1069 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FC +06 +06 +06 +3E +06 +06 +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER YU +ENCODING 1070 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +CC +D6 +D6 +D6 +F6 +F6 +D6 +D6 +D6 +CC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER YA +ENCODING 1071 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +C6 +C6 +C6 +7E +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER A +ENCODING 1072 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +06 +7E +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER BE +ENCODING 1073 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +C0 +FC +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER VE +ENCODING 1074 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +70 +D8 +D8 +D8 +FC +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER GHE +ENCODING 1075 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +06 +06 +7C +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER DE +ENCODING 1076 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER IE +ENCODING 1077 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER ZHE +ENCODING 1078 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +D6 +7C +38 +38 +7C +D6 +D6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER ZE +ENCODING 1079 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +06 +06 +3C +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER I +ENCODING 1080 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER SHORT I +ENCODING 1081 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER KA +ENCODING 1082 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +CC +CC +D8 +F0 +F0 +D8 +CC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER EL +ENCODING 1083 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER EM +ENCODING 1084 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +EE +FE +D6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER EN +ENCODING 1085 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +FE +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER O +ENCODING 1086 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER PE +ENCODING 1087 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER ER +ENCODING 1088 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +C6 +C6 +C6 +C6 +C6 +FC +C0 +C0 +C0 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER ES +ENCODING 1089 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C0 +C0 +C0 +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER TE +ENCODING 1090 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +EC +D6 +D6 +D6 +D6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER U +ENCODING 1091 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER EF +ENCODING 1092 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7C +D6 +D6 +D6 +D6 +D6 +7C +10 +10 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER HA +ENCODING 1093 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +6C +38 +38 +6C +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER TSE +ENCODING 1094 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7F +03 +03 +06 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER CHE +ENCODING 1095 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +C6 +7E +06 +06 +06 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER SHA +ENCODING 1096 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +D6 +D6 +D6 +D6 +6E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER SHCHA +ENCODING 1097 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +D6 +D6 +D6 +D6 +6F +03 +03 +06 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER HARD SIGN +ENCODING 1098 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +F0 +30 +3C +36 +36 +36 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER YERU +ENCODING 1099 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +C6 +C6 +F6 +DE +DE +DE +F6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER SOFT SIGN +ENCODING 1100 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +30 +30 +3C +36 +36 +36 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER E +ENCODING 1101 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FC +06 +06 +3E +06 +06 +FC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER YU +ENCODING 1102 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +CC +D6 +D6 +F6 +D6 +D6 +CC +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER YA +ENCODING 1103 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +C6 +C6 +7E +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER IO +ENCODING 1105 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +6C +6C +00 +7E +C6 +C6 +FE +C0 +C0 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +ENCODING 1110 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER YI +ENCODING 1111 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +66 +66 +00 +38 +18 +18 +18 +18 +18 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER SHORT U +ENCODING 1118 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +6C +6C +38 +00 +C6 +C6 +C6 +C6 +C6 +C6 +7E +06 +06 +FC +00 +ENDCHAR +STARTCHAR CYRILLIC CAPITAL LETTER GHE WITH UPTURN +ENCODING 1168 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +06 +06 +7C +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR CYRILLIC SMALL LETTER GHE WITH UPTURN +ENCODING 1169 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +06 +06 +7C +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOUBLE VERTICAL LINE +ENCODING 8214 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +66 +66 +66 +66 +66 +66 +66 +66 +66 +66 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT SINGLE QUOTATION MARK +ENCODING 8216 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +18 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT SINGLE QUOTATION MARK +ENCODING 8217 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +18 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT DOUBLE QUOTATION MARK +ENCODING 8220 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT DOUBLE QUOTATION MARK +ENCODING 8221 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BULLET +ENCODING 8226 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +18 +3C +3C +18 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR HORIZONTAL ELLIPSIS +ENCODING 8230 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +DB +DB +00 +00 +00 +00 +ENDCHAR +STARTCHAR SINGLE LEFT-POINTING ANGLE QUOTATION MARK +ENCODING 8249 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +0C +18 +30 +18 +0C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +ENCODING 8250 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +30 +18 +0C +18 +30 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOUBLE EXCLAMATION MARK +ENCODING 8252 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +66 +66 +66 +66 +66 +66 +66 +00 +66 +66 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SUPERSCRIPT LATIN SMALL LETTER N +ENCODING 8319 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +78 +6C +6C +6C +6C +6C +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR PESETA SIGN +ENCODING 8359 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +F8 +CC +CC +F8 +C0 +CC +DE +CC +CC +CC +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR EURO SIGN +ENCODING 8364 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +1C +36 +60 +F8 +60 +F8 +60 +36 +1C +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFTWARDS ARROW +ENCODING 8592 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +20 +40 +FE +40 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UPWARDS ARROW +ENCODING 8593 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +5A +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHTWARDS ARROW +ENCODING 8594 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +08 +04 +FE +04 +08 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOWNWARDS ARROW +ENCODING 8595 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +18 +18 +18 +18 +18 +5A +3C +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT RIGHT ARROW +ENCODING 8596 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +28 +44 +FE +44 +28 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UP DOWN ARROW +ENCODING 8597 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +5A +18 +18 +18 +18 +5A +3C +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UP DOWN ARROW WITH BASE +ENCODING 8616 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +5A +18 +18 +18 +18 +5A +3C +18 +7E +00 +00 +00 +ENDCHAR +STARTCHAR BULLET OPERATOR +ENCODING 8729 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +38 +38 +38 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR SQUARE ROOT +ENCODING 8730 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +0F +0C +0C +0C +0C +0C +CC +6C +3C +1C +0C +00 +00 +00 +00 +ENDCHAR +STARTCHAR INFINITY +ENCODING 8734 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +7E +DB +DB +DB +7E +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR INTERSECTION +ENCODING 8745 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +7C +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UNION +ENCODING 8746 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +7C +00 +00 +00 +00 +ENDCHAR +STARTCHAR ALMOST EQUAL TO +ENCODING 8776 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +32 +4C +00 +32 +4C +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR IDENTICAL TO +ENCODING 8801 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7E +00 +00 +7E +00 +00 +7E +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LESS-THAN OR EQUAL TO +ENCODING 8804 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +0C +18 +30 +18 +0C +00 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR GREATER-THAN OR EQUAL TO +ENCODING 8805 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +30 +18 +0C +18 +30 +00 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR HOUSE +ENCODING 8962 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +10 +38 +6C +C6 +C6 +C6 +FE +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR REVERSED NOT SIGN +ENCODING 8976 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FE +C0 +C0 +C0 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TURNED NOT SIGN +ENCODING 8985 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +C0 +C0 +C0 +FE +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TOP HALF INTEGRAL +ENCODING 8992 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +0E +1B +1B +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOTTOM HALF INTEGRAL +ENCODING 8993 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +18 +18 +D8 +D8 +70 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT HORIZONTAL +ENCODING 9472 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY HORIZONTAL +ENCODING 9473 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT VERTICAL +ENCODING 9474 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY VERTICAL +ENCODING 9475 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL +ENCODING 9476 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +DB +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL +ENCODING 9477 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +DB +DB +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL +ENCODING 9478 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +00 +00 +18 +18 +18 +00 +00 +18 +18 +18 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL +ENCODING 9479 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +1C +1C +1C +00 +00 +1C +1C +1C +00 +00 +1C +1C +1C +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL +ENCODING 9480 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +AA +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL +ENCODING 9481 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +AA +AA +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL +ENCODING 9482 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +00 +00 +18 +18 +00 +00 +18 +18 +00 +00 +18 +18 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL +ENCODING 9483 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +1C +1C +00 +00 +1C +1C +00 +00 +1C +1C +00 +00 +1C +1C +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOWN AND RIGHT +ENCODING 9484 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +1F +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY +ENCODING 9485 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +1F +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT +ENCODING 9486 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +1F +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOWN AND RIGHT +ENCODING 9487 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +1F +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOWN AND LEFT +ENCODING 9488 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F8 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY +ENCODING 9489 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F8 +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT +ENCODING 9490 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FC +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOWN AND LEFT +ENCODING 9491 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FC +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT UP AND RIGHT +ENCODING 9492 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND RIGHT HEAVY +ENCODING 9493 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +1F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND RIGHT LIGHT +ENCODING 9494 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY UP AND RIGHT +ENCODING 9495 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +1F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT UP AND LEFT +ENCODING 9496 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +F8 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND LEFT HEAVY +ENCODING 9497 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +F8 +F8 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND LEFT LIGHT +ENCODING 9498 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY UP AND LEFT +ENCODING 9499 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +FC +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND RIGHT +ENCODING 9500 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY +ENCODING 9501 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT +ENCODING 9502 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT +ENCODING 9503 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT +ENCODING 9504 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY +ENCODING 9505 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY +ENCODING 9506 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +1F +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND RIGHT +ENCODING 9507 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1F +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND LEFT +ENCODING 9508 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +F8 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY +ENCODING 9509 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +F8 +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT +ENCODING 9510 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT +ENCODING 9511 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FC +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT +ENCODING 9512 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY +ENCODING 9513 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +FC +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY +ENCODING 9514 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FC +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND LEFT +ENCODING 9515 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FC +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOWN AND HORIZONTAL +ENCODING 9516 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT +ENCODING 9517 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F8 +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT +ENCODING 9518 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +1F +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY +ENCODING 9519 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT +ENCODING 9520 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY +ENCODING 9521 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY +ENCODING 9522 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOWN AND HORIZONTAL +ENCODING 9523 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +FF +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT UP AND HORIZONTAL +ENCODING 9524 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT +ENCODING 9525 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +F8 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT +ENCODING 9526 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +1F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY +ENCODING 9527 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT +ENCODING 9528 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY +ENCODING 9529 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +FC +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY +ENCODING 9530 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +1F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY UP AND HORIZONTAL +ENCODING 9531 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL +ENCODING 9532 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT +ENCODING 9533 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT +ENCODING 9534 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY +ENCODING 9535 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT +ENCODING 9536 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT +ENCODING 9537 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT +ENCODING 9538 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT +ENCODING 9539 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT +ENCODING 9540 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT +ENCODING 9541 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT +ENCODING 9542 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY +ENCODING 9543 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY +ENCODING 9544 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +FF +FF +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY +ENCODING 9545 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +FC +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY +ENCODING 9546 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +1F +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL +ENCODING 9547 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +FF +FF +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL +ENCODING 9548 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +EE +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL +ENCODING 9549 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +EE +EE +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL +ENCODING 9550 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +18 +18 +18 +18 +18 +18 +00 +00 +18 +18 +18 +18 +18 +18 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL +ENCODING 9551 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +1C +1C +1C +1C +1C +1C +00 +00 +1C +1C +1C +1C +1C +1C +00 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE HORIZONTAL +ENCODING 9552 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FF +00 +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE VERTICAL +ENCODING 9553 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE +ENCODING 9554 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +1F +18 +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE +ENCODING 9555 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +3F +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE DOWN AND RIGHT +ENCODING 9556 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +3F +30 +37 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE +ENCODING 9557 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +F8 +18 +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE +ENCODING 9558 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FE +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE DOWN AND LEFT +ENCODING 9559 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FE +06 +F6 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE +ENCODING 9560 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +1F +18 +1F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE +ENCODING 9561 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +3F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE UP AND RIGHT +ENCODING 9562 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +37 +30 +3F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP SINGLE AND LEFT DOUBLE +ENCODING 9563 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +F8 +18 +F8 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP DOUBLE AND LEFT SINGLE +ENCODING 9564 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +FE +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE UP AND LEFT +ENCODING 9565 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +F6 +06 +FE +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE +ENCODING 9566 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +1F +18 +1F +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE +ENCODING 9567 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +37 +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND RIGHT +ENCODING 9568 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +37 +30 +37 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE +ENCODING 9569 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +F8 +18 +F8 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE +ENCODING 9570 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +F6 +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND LEFT +ENCODING 9571 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +F6 +06 +F6 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE +ENCODING 9572 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FF +00 +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE +ENCODING 9573 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL +ENCODING 9574 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FF +00 +F7 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE +ENCODING 9575 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +FF +00 +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE +ENCODING 9576 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +FF +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE UP AND HORIZONTAL +ENCODING 9577 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +F7 +00 +FF +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE +ENCODING 9578 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +FF +18 +FF +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE +ENCODING 9579 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +36 +FF +36 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL +ENCODING 9580 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +36 +36 +36 +36 +36 +36 +F7 +00 +F7 +36 +36 +36 +36 +36 +36 +36 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT ARC DOWN AND RIGHT +ENCODING 9581 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +0F +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT ARC DOWN AND LEFT +ENCODING 9582 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F0 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT ARC UP AND LEFT +ENCODING 9583 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +F0 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT ARC UP AND RIGHT +ENCODING 9584 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +0F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT +ENCODING 9585 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +01 +03 +03 +06 +06 +0C +0C +18 +18 +30 +30 +60 +60 +C0 +C0 +80 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT +ENCODING 9586 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +80 +C0 +C0 +60 +60 +30 +30 +18 +18 +0C +0C +06 +06 +03 +03 +01 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DIAGONAL CROSS +ENCODING 9587 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +81 +C3 +C3 +66 +66 +3C +3C +18 +18 +3C +3C +66 +66 +C3 +C3 +81 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT LEFT +ENCODING 9588 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F0 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT UP +ENCODING 9589 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT RIGHT +ENCODING 9590 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +0F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT DOWN +ENCODING 9591 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY LEFT +ENCODING 9592 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +F0 +F0 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY UP +ENCODING 9593 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1C +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY RIGHT +ENCODING 9594 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +0F +0F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY DOWN +ENCODING 9595 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT +ENCODING 9596 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +0F +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS LIGHT UP AND HEAVY DOWN +ENCODING 9597 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +18 +18 +18 +18 +18 +18 +18 +18 +1C +1C +1C +1C +1C +1C +1C +1C +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT +ENCODING 9598 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +FF +F0 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BOX DRAWINGS HEAVY UP AND LIGHT DOWN +ENCODING 9599 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +1C +1C +1C +1C +1C +1C +1C +1C +18 +18 +18 +18 +18 +18 +18 +18 +ENDCHAR +STARTCHAR UPPER HALF BLOCK +ENCODING 9600 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +FF +FF +FF +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LOWER ONE EIGHTH BLOCK +ENCODING 9601 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FF +FF +ENDCHAR +STARTCHAR LOWER ONE QUARTER BLOCK +ENCODING 9602 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FF +FF +FF +FF +ENDCHAR +STARTCHAR LOWER THREE EIGHTHS BLOCK +ENCODING 9603 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR LOWER HALF BLOCK +ENCODING 9604 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR LOWER FIVE EIGHTHS BLOCK +ENCODING 9605 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR LOWER THREE QUARTERS BLOCK +ENCODING 9606 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR LOWER SEVEN EIGHTHS BLOCK +ENCODING 9607 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR FULL BLOCK +ENCODING 9608 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR LEFT SEVEN EIGHTHS BLOCK +ENCODING 9609 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +FE +ENDCHAR +STARTCHAR LEFT THREE QUARTERS BLOCK +ENCODING 9610 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +FC +ENDCHAR +STARTCHAR LEFT FIVE EIGHTHS BLOCK +ENCODING 9611 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +F8 +ENDCHAR +STARTCHAR LEFT HALF BLOCK +ENCODING 9612 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +ENDCHAR +STARTCHAR LEFT THREE EIGHTHS BLOCK +ENCODING 9613 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +E0 +ENDCHAR +STARTCHAR LEFT ONE QUARTER BLOCK +ENCODING 9614 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR LEFT ONE EIGHTH BLOCK +ENCODING 9615 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +80 +ENDCHAR +STARTCHAR RIGHT HALF BLOCK +ENCODING 9616 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +0F +ENDCHAR +STARTCHAR LIGHT SHADE +ENCODING 9617 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +11 +44 +11 +44 +11 +44 +11 +44 +11 +44 +11 +44 +11 +44 +11 +44 +ENDCHAR +STARTCHAR MEDIUM SHADE +ENCODING 9618 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +55 +AA +55 +AA +55 +AA +55 +AA +55 +AA +55 +AA +55 +AA +55 +AA +ENDCHAR +STARTCHAR DARK SHADE +ENCODING 9619 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +DD +77 +DD +77 +DD +77 +DD +77 +DD +77 +DD +77 +DD +77 +DD +77 +ENDCHAR +STARTCHAR UPPER ONE EIGHTH BLOCK +ENCODING 9620 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHT ONE EIGHTH BLOCK +ENCODING 9621 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +01 +ENDCHAR +STARTCHAR QUADRANT LOWER LEFT +ENCODING 9622 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +ENDCHAR +STARTCHAR QUADRANT LOWER RIGHT +ENCODING 9623 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +0F +0F +0F +0F +0F +0F +0F +0F +ENDCHAR +STARTCHAR QUADRANT UPPER LEFT +ENCODING 9624 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT +ENCODING 9625 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR QUADRANT UPPER LEFT AND LOWER RIGHT +ENCODING 9626 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +0F +0F +0F +0F +0F +0F +0F +0F +ENDCHAR +STARTCHAR QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT +ENCODING 9627 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +FF +FF +FF +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +ENDCHAR +STARTCHAR QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT +ENCODING 9628 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +FF +FF +FF +0F +0F +0F +0F +0F +0F +0F +0F +ENDCHAR +STARTCHAR QUADRANT UPPER RIGHT +ENCODING 9629 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +0F +0F +0F +0F +0F +0F +0F +0F +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR QUADRANT UPPER RIGHT AND LOWER LEFT +ENCODING 9630 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +0F +0F +0F +0F +0F +0F +0F +0F +F0 +F0 +F0 +F0 +F0 +F0 +F0 +F0 +ENDCHAR +STARTCHAR QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT +ENCODING 9631 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +0F +0F +0F +0F +0F +0F +0F +0F +FF +FF +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR BLACK SQUARE +ENCODING 9632 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7C +7C +7C +7C +7C +7C +7C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK RECTANGLE +ENCODING 9644 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +FE +FE +FE +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK UP-POINTING TRIANGLE +ENCODING 9650 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +10 +38 +38 +7C +7C +FE +FE +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK DOWN-POINTING TRIANGLE +ENCODING 9660 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +FE +FE +7C +7C +38 +38 +10 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK DIAMOND +ENCODING 9670 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +10 +38 +7C +FE +7C +38 +10 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LOZENGE +ENCODING 9674 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +10 +38 +6C +C6 +6C +38 +10 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR WHITE CIRCLE +ENCODING 9675 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +3C +66 +66 +66 +66 +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK CIRCLE +ENCODING 9679 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +3C +7E +7E +7E +7E +3C +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR INVERSE BULLET +ENCODING 9688 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +FF +E7 +C3 +C3 +E7 +FF +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR INVERSE WHITE CIRCLE +ENCODING 9689 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FF +FF +FF +FF +C3 +99 +99 +99 +99 +C3 +FF +FF +FF +FF +FF +ENDCHAR +STARTCHAR BLACK LOWER RIGHT TRIANGLE +ENCODING 9698 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +80 +C0 +E0 +F0 +F8 +FC +FE +FF +ENDCHAR +STARTCHAR BLACK LOWER LEFT TRIANGLE +ENCODING 9699 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +01 +03 +07 +0F +1F +3F +7F +FF +ENDCHAR +STARTCHAR BLACK UPPER LEFT TRIANGLE +ENCODING 9700 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +FE +FC +F8 +F0 +E0 +C0 +80 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK UPPER RIGHT TRIANGLE +ENCODING 9701 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +FF +7F +3F +1F +0F +07 +03 +01 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR HEAVEN +ENCODING 9776 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7E +00 +00 +7E +00 +00 +7E +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR LAKE +ENCODING 9777 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +66 +00 +00 +7E +00 +00 +7E +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR FIRE +ENCODING 9778 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7E +00 +00 +66 +00 +00 +7E +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR THUNDER +ENCODING 9779 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +66 +00 +00 +66 +00 +00 +7E +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR WIND +ENCODING 9780 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7E +00 +00 +7E +00 +00 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR WATER +ENCODING 9781 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +66 +00 +00 +7E +00 +00 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR MOUNTAIN +ENCODING 9782 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +7E +00 +00 +66 +00 +00 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR TRIGRAM FOR EARTH +ENCODING 9783 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +66 +00 +00 +66 +00 +00 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR WHITE SMILING FACE +ENCODING 9786 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +81 +A5 +81 +81 +A5 +99 +81 +81 +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK SMILING FACE +ENCODING 9787 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +FF +DB +FF +FF +DB +E7 +FF +FF +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR WHITE SUN WITH RAYS +ENCODING 9788 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +18 +18 +DB +3C +E7 +3C +DB +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR FEMALE SIGN +ENCODING 9792 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +3C +66 +66 +66 +66 +3C +18 +3C +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR MALE SIGN +ENCODING 9794 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +0F +07 +0D +19 +3C +66 +66 +66 +66 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK SPADE SUIT +ENCODING 9824 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +18 +3C +7E +FF +FF +7E +18 +18 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK CLUB SUIT +ENCODING 9827 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +18 +3C +3C +E7 +E7 +E7 +18 +18 +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK HEART SUIT +ENCODING 9829 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +6C +FE +FE +FE +7C +38 +10 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BLACK DIAMOND SUIT +ENCODING 9830 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +10 +38 +7C +FE +7C +38 +10 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR EIGHTH NOTE +ENCODING 9834 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +66 +7E +60 +60 +60 +60 +60 +C0 +C0 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BEAMED EIGHTH NOTES +ENCODING 9835 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +7E +66 +7E +66 +66 +66 +66 +66 +6E +CC +C0 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN BLANK +ENCODING 10240 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1 +ENCODING 10241 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2 +ENCODING 10242 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12 +ENCODING 10243 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3 +ENCODING 10244 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13 +ENCODING 10245 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23 +ENCODING 10246 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123 +ENCODING 10247 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-4 +ENCODING 10248 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-14 +ENCODING 10249 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-24 +ENCODING 10250 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-124 +ENCODING 10251 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-34 +ENCODING 10252 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-134 +ENCODING 10253 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-234 +ENCODING 10254 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1234 +ENCODING 10255 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-5 +ENCODING 10256 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-15 +ENCODING 10257 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-25 +ENCODING 10258 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-125 +ENCODING 10259 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-35 +ENCODING 10260 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-135 +ENCODING 10261 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-235 +ENCODING 10262 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1235 +ENCODING 10263 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-45 +ENCODING 10264 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-145 +ENCODING 10265 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-245 +ENCODING 10266 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1245 +ENCODING 10267 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-345 +ENCODING 10268 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1345 +ENCODING 10269 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2345 +ENCODING 10270 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12345 +ENCODING 10271 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-6 +ENCODING 10272 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-16 +ENCODING 10273 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-26 +ENCODING 10274 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-126 +ENCODING 10275 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-36 +ENCODING 10276 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-136 +ENCODING 10277 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-236 +ENCODING 10278 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1236 +ENCODING 10279 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-46 +ENCODING 10280 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-146 +ENCODING 10281 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-246 +ENCODING 10282 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1246 +ENCODING 10283 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-346 +ENCODING 10284 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1346 +ENCODING 10285 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2346 +ENCODING 10286 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12346 +ENCODING 10287 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-56 +ENCODING 10288 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-156 +ENCODING 10289 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-256 +ENCODING 10290 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1256 +ENCODING 10291 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-356 +ENCODING 10292 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1356 +ENCODING 10293 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2356 +ENCODING 10294 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12356 +ENCODING 10295 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-456 +ENCODING 10296 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1456 +ENCODING 10297 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2456 +ENCODING 10298 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12456 +ENCODING 10299 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3456 +ENCODING 10300 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13456 +ENCODING 10301 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23456 +ENCODING 10302 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123456 +ENCODING 10303 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-7 +ENCODING 10304 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-17 +ENCODING 10305 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-27 +ENCODING 10306 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-127 +ENCODING 10307 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-37 +ENCODING 10308 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-137 +ENCODING 10309 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-237 +ENCODING 10310 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1237 +ENCODING 10311 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-47 +ENCODING 10312 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-147 +ENCODING 10313 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-247 +ENCODING 10314 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1247 +ENCODING 10315 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-347 +ENCODING 10316 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1347 +ENCODING 10317 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2347 +ENCODING 10318 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12347 +ENCODING 10319 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-57 +ENCODING 10320 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-157 +ENCODING 10321 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-257 +ENCODING 10322 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1257 +ENCODING 10323 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-357 +ENCODING 10324 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1357 +ENCODING 10325 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2357 +ENCODING 10326 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12357 +ENCODING 10327 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-457 +ENCODING 10328 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1457 +ENCODING 10329 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2457 +ENCODING 10330 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12457 +ENCODING 10331 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3457 +ENCODING 10332 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13457 +ENCODING 10333 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23457 +ENCODING 10334 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123457 +ENCODING 10335 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-67 +ENCODING 10336 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-167 +ENCODING 10337 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-267 +ENCODING 10338 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1267 +ENCODING 10339 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-367 +ENCODING 10340 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1367 +ENCODING 10341 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2367 +ENCODING 10342 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12367 +ENCODING 10343 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-467 +ENCODING 10344 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1467 +ENCODING 10345 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2467 +ENCODING 10346 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12467 +ENCODING 10347 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3467 +ENCODING 10348 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13467 +ENCODING 10349 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23467 +ENCODING 10350 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123467 +ENCODING 10351 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-567 +ENCODING 10352 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1567 +ENCODING 10353 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2567 +ENCODING 10354 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12567 +ENCODING 10355 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3567 +ENCODING 10356 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13567 +ENCODING 10357 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23567 +ENCODING 10358 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123567 +ENCODING 10359 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-4567 +ENCODING 10360 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-14567 +ENCODING 10361 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-24567 +ENCODING 10362 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-124567 +ENCODING 10363 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-34567 +ENCODING 10364 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-134567 +ENCODING 10365 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-234567 +ENCODING 10366 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1234567 +ENCODING 10367 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-8 +ENCODING 10368 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-18 +ENCODING 10369 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-28 +ENCODING 10370 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-128 +ENCODING 10371 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-38 +ENCODING 10372 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-138 +ENCODING 10373 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-238 +ENCODING 10374 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1238 +ENCODING 10375 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-48 +ENCODING 10376 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-148 +ENCODING 10377 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-248 +ENCODING 10378 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1248 +ENCODING 10379 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-348 +ENCODING 10380 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1348 +ENCODING 10381 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2348 +ENCODING 10382 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12348 +ENCODING 10383 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-58 +ENCODING 10384 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-158 +ENCODING 10385 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-258 +ENCODING 10386 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1258 +ENCODING 10387 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-358 +ENCODING 10388 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1358 +ENCODING 10389 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2358 +ENCODING 10390 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12358 +ENCODING 10391 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-458 +ENCODING 10392 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1458 +ENCODING 10393 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2458 +ENCODING 10394 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12458 +ENCODING 10395 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3458 +ENCODING 10396 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13458 +ENCODING 10397 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23458 +ENCODING 10398 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123458 +ENCODING 10399 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-68 +ENCODING 10400 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-168 +ENCODING 10401 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-268 +ENCODING 10402 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1268 +ENCODING 10403 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-368 +ENCODING 10404 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1368 +ENCODING 10405 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2368 +ENCODING 10406 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12368 +ENCODING 10407 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-468 +ENCODING 10408 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1468 +ENCODING 10409 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2468 +ENCODING 10410 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12468 +ENCODING 10411 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3468 +ENCODING 10412 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13468 +ENCODING 10413 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23468 +ENCODING 10414 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123468 +ENCODING 10415 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-568 +ENCODING 10416 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1568 +ENCODING 10417 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2568 +ENCODING 10418 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12568 +ENCODING 10419 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3568 +ENCODING 10420 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13568 +ENCODING 10421 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23568 +ENCODING 10422 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123568 +ENCODING 10423 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-4568 +ENCODING 10424 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-14568 +ENCODING 10425 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-24568 +ENCODING 10426 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-124568 +ENCODING 10427 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-34568 +ENCODING 10428 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-134568 +ENCODING 10429 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-234568 +ENCODING 10430 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1234568 +ENCODING 10431 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-78 +ENCODING 10432 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-178 +ENCODING 10433 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-278 +ENCODING 10434 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1278 +ENCODING 10435 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-378 +ENCODING 10436 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1378 +ENCODING 10437 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2378 +ENCODING 10438 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12378 +ENCODING 10439 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-478 +ENCODING 10440 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1478 +ENCODING 10441 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2478 +ENCODING 10442 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12478 +ENCODING 10443 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3478 +ENCODING 10444 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13478 +ENCODING 10445 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23478 +ENCODING 10446 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123478 +ENCODING 10447 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-578 +ENCODING 10448 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1578 +ENCODING 10449 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2578 +ENCODING 10450 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12578 +ENCODING 10451 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3578 +ENCODING 10452 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13578 +ENCODING 10453 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23578 +ENCODING 10454 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123578 +ENCODING 10455 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-4578 +ENCODING 10456 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-14578 +ENCODING 10457 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-24578 +ENCODING 10458 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-124578 +ENCODING 10459 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-34578 +ENCODING 10460 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-134578 +ENCODING 10461 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-234578 +ENCODING 10462 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1234578 +ENCODING 10463 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-678 +ENCODING 10464 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1678 +ENCODING 10465 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2678 +ENCODING 10466 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12678 +ENCODING 10467 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-3678 +ENCODING 10468 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-13678 +ENCODING 10469 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-23678 +ENCODING 10470 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-123678 +ENCODING 10471 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-4678 +ENCODING 10472 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-14678 +ENCODING 10473 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-24678 +ENCODING 10474 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-124678 +ENCODING 10475 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-34678 +ENCODING 10476 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-134678 +ENCODING 10477 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-234678 +ENCODING 10478 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1234678 +ENCODING 10479 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-5678 +ENCODING 10480 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-15678 +ENCODING 10481 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-25678 +ENCODING 10482 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-125678 +ENCODING 10483 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-35678 +ENCODING 10484 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-135678 +ENCODING 10485 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-235678 +ENCODING 10486 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1235678 +ENCODING 10487 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +60 +60 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-45678 +ENCODING 10488 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-145678 +ENCODING 10489 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-245678 +ENCODING 10490 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1245678 +ENCODING 10491 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-345678 +ENCODING 10492 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-1345678 +ENCODING 10493 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-2345678 +ENCODING 10494 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +06 +06 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR BRAILLE PATTERN DOTS-12345678 +ENCODING 10495 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +00 +66 +66 +00 +ENDCHAR +STARTCHAR UPWARDS BLACK ARROW +ENCODING 11014 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +7E +FF +3C +3C +3C +3C +3C +3C +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOWNWARDS BLACK ARROW +ENCODING 11015 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +3C +3C +3C +3C +3C +3C +FF +7E +3C +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFTWARDS TRIANGLE-HEADED ARROW +ENCODING 11104 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +20 +60 +FE +60 +20 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UPWARDS TRIANGLE-HEADED ARROW +ENCODING 11105 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +7E +18 +18 +18 +18 +18 +18 +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR RIGHTWARDS TRIANGLE-HEADED ARROW +ENCODING 11106 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +08 +0C +FE +0C +08 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR DOWNWARDS TRIANGLE-HEADED ARROW +ENCODING 11107 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +18 +18 +18 +18 +18 +18 +7E +3C +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR LEFT RIGHT TRIANGLE-HEADED ARROW +ENCODING 11108 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +00 +00 +00 +28 +6C +FE +6C +28 +00 +00 +00 +00 +00 +00 +ENDCHAR +STARTCHAR UP DOWN TRIANGLE-HEADED ARROW +ENCODING 11109 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +3C +7E +18 +18 +18 +18 +7E +3C +18 +00 +00 +00 +00 +ENDCHAR +STARTCHAR char57504 +ENCODING 57504 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +C0 +CC +DE +CC +CC +CC +98 +30 +60 +C0 +C0 +C0 +00 +00 +ENDCHAR +STARTCHAR char57505 +ENCODING 57505 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +40 +40 +40 +40 +40 +38 +00 +12 +1A +1A +16 +16 +12 +00 +00 +ENDCHAR +STARTCHAR char57506 +ENCODING 57506 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +00 +00 +18 +24 +24 +24 +7E +7E +66 +66 +7E +7E +00 +00 +00 +00 +ENDCHAR +STARTCHAR char57520 +ENCODING 57520 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +80 +C0 +E0 +F0 +F8 +FC +FE +FF +FF +FE +FC +F8 +F0 +E0 +C0 +80 +ENDCHAR +STARTCHAR char57521 +ENCODING 57521 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +80 +C0 +60 +30 +18 +0C +06 +03 +03 +06 +0C +18 +30 +60 +C0 +80 +ENDCHAR +STARTCHAR char57522 +ENCODING 57522 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +01 +03 +07 +0F +1F +3F +7F +FF +FF +7F +3F +1F +0F +07 +03 +01 +ENDCHAR +STARTCHAR char57523 +ENCODING 57523 +SWIDTH 500 0 +DWIDTH 8 0 +BBX 8 16 0 -4 +BITMAP +01 +03 +06 +0C +18 +30 +60 +C0 +C0 +60 +30 +18 +0C +06 +03 +01 +ENDCHAR +ENDFONT diff --git a/crates/ghostty-font/res/spleen-8x16.otb b/crates/ghostty-font/res/spleen-8x16.otb new file mode 100644 index 0000000..229b7ab Binary files /dev/null and b/crates/ghostty-font/res/spleen-8x16.otb differ diff --git a/crates/ghostty-font/res/spleen-8x16.pcf b/crates/ghostty-font/res/spleen-8x16.pcf new file mode 100644 index 0000000..1c4fbd3 Binary files /dev/null and b/crates/ghostty-font/res/spleen-8x16.pcf differ diff --git a/crates/ghostty-font/src/backend.rs b/crates/ghostty-font/src/backend.rs new file mode 100644 index 0000000..dfbc495 --- /dev/null +++ b/crates/ghostty-font/src/backend.rs @@ -0,0 +1,91 @@ +//! Font backend selection. Port of `font/backend.zig`. +//! +//! The Rust rewrite only targets Linux, so [`OPTIONS`] always selects +//! [`Backend::FontconfigFreetype`] (fontconfig discovery, FreeType rendering, +//! HarfBuzz shaping). The other variants are kept for fidelity with the Zig +//! enum and its helper predicates. + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Backend { + /// FreeType for font rendering with no font discovery enabled. + Freetype, + /// Fontconfig for font discovery and FreeType for font rendering. + FontconfigFreetype, + /// CoreText for font discovery, rendering, and shaping (macOS). + Coretext, + /// CoreText for font discovery, FreeType for rendering, and + /// HarfBuzz for shaping (macOS). + CoretextFreetype, + /// CoreText for font discovery and rendering, HarfBuzz for shaping + CoretextHarfbuzz, + /// CoreText for font discovery and rendering, no shaping. + CoretextNoshape, + /// Use the browser font system and the Canvas API (wasm). + WebCanvas, +} + +impl Backend { + /// Returns the default backend for a build environment. + pub fn default_for_target() -> Backend { + if cfg!(target_os = "macos") { + Backend::Coretext + } else { + Backend::FontconfigFreetype + } + } + + pub fn has_freetype(self) -> bool { + matches!( + self, + Backend::Freetype | Backend::FontconfigFreetype | Backend::CoretextFreetype + ) + } + + pub fn has_coretext(self) -> bool { + matches!( + self, + Backend::Coretext + | Backend::CoretextFreetype + | Backend::CoretextHarfbuzz + | Backend::CoretextNoshape + ) + } + + pub fn has_fontconfig(self) -> bool { + matches!(self, Backend::FontconfigFreetype) + } + + pub fn has_harfbuzz(self) -> bool { + matches!( + self, + Backend::Freetype + | Backend::FontconfigFreetype + | Backend::CoretextFreetype + | Backend::CoretextHarfbuzz + ) + } +} + +/// Build options (the `font.options` of Zig). +#[derive(Debug, Clone, Copy)] +pub struct Options { + pub backend: Backend, +} + +pub const OPTIONS: Options = Options { + backend: Backend::FontconfigFreetype, +}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn linux_backend_capabilities() { + let b = OPTIONS.backend; + assert!(b.has_freetype()); + assert!(b.has_fontconfig()); + assert!(b.has_harfbuzz()); + assert!(!b.has_coretext()); + } +} diff --git a/crates/ghostty-font/src/codepoint_map.rs b/crates/ghostty-font/src/codepoint_map.rs new file mode 100644 index 0000000..29e8e45 --- /dev/null +++ b/crates/ghostty-font/src/codepoint_map.rs @@ -0,0 +1,139 @@ +//! CodepointMap is a map of codepoints to a discovery descriptor of a font +//! to use for that codepoint. If the descriptor doesn't return any matching +//! font, the codepoint is rendered using the default font. +//! Port of `font/CodepointMap.zig`. + +use std::hash::{DefaultHasher, Hash, Hasher}; + +use crate::discovery::Descriptor; + +#[derive(Debug, Clone)] +pub struct Entry { + /// Unicode codepoint range (inclusive). Asserts range.0 <= range.1. + pub range: (u32, u32), + + /// The discovery descriptor of the font to use for this range. + pub descriptor: Descriptor, +} + +/// The list of entries. +/// +/// Note: we just do a linear search because we expect to always have very +/// few entries, so the overhead of a binary search is not worth it. This is +/// possible to defeat with some pathological inputs, but there is no realistic +/// scenario where this will be a problem except people trying to fuck around. +#[derive(Debug, Clone, Default)] +pub struct CodepointMap { + pub list: Vec, +} + +impl CodepointMap { + /// Add an entry to the map. + /// + /// For conflicting codepoints, entries added later take priority over + /// entries added earlier. + pub fn add(&mut self, entry: Entry) { + assert!(entry.range.0 <= entry.range.1); + self.list.push(entry); + } + + /// Get a descriptor for a codepoint. + pub fn get(&self, cp: u32) -> Option<&Descriptor> { + self.list + .iter() + .rev() + .find(|e| e.range.0 <= cp && cp <= e.range.1) + .map(|e| &e.descriptor) + } + + /// Hash with the given hasher. + pub fn hash_into(&self, hasher: &mut H) { + self.list.len().hash(hasher); + for entry in &self.list { + entry.range.hash(hasher); + hash_descriptor(&entry.descriptor, hasher); + } + } + + /// Returns a hash code that can be used to uniquely identify this + /// action. + pub fn hashcode(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.hash_into(&mut hasher); + hasher.finish() + } +} + +/// Mirrors `Descriptor.hash` of the Zig discovery module (same fields, same +/// float handling) without depending on how the discovery module exposes it. +fn hash_descriptor(d: &Descriptor, hasher: &mut H) { + d.family.hash(hasher); + d.style.hash(hasher); + d.codepoint.hash(hasher); + d.size.to_bits().hash(hasher); + d.bold.hash(hasher); + d.italic.hash(hasher); + d.monospace.hash(hasher); + d.variations.len().hash(hasher); + for v in &d.variations { + v.id.hash(hasher); + // This is not correct, but we don't currently depend on the + // hash value being different based on decimal values of variations. + (v.value as i64).hash(hasher); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn desc(family: &str) -> Descriptor { + Descriptor { + family: Some(family.to_string()), + ..Default::default() + } + } + + fn entry(a: u32, b: u32, family: &str) -> Entry { + Entry { + range: (a, b), + descriptor: desc(family), + } + } + + #[test] + fn codepointmap() { + let mut m = CodepointMap::default(); + + // Exact range + assert!(m.get(1).is_none()); + m.add(entry(1, 1, "A")); + assert_eq!("A", m.get(1).unwrap().family.as_deref().unwrap()); + + // Later entry takes priority + m.add(entry(1, 2, "B")); + assert_eq!("B", m.get(1).unwrap().family.as_deref().unwrap()); + + // Non-matching + assert!(m.get(0).is_none()); + assert!(m.get(3).is_none()); + + m.add(entry(3, 4, "C")); + m.add(entry(5, 6, "D")); + assert_eq!("C", m.get(3).unwrap().family.as_deref().unwrap()); + assert_eq!("B", m.get(1).unwrap().family.as_deref().unwrap()); + } + + #[test] + fn hashcode_depends_on_contents() { + let mut a = CodepointMap::default(); + let mut b = CodepointMap::default(); + assert_eq!(a.hashcode(), b.hashcode()); + a.add(entry(1, 2, "A")); + assert_ne!(a.hashcode(), b.hashcode()); + b.add(entry(1, 2, "A")); + assert_eq!(a.hashcode(), b.hashcode()); + b.add(entry(3, 3, "B")); + assert_ne!(a.hashcode(), b.hashcode()); + } +} diff --git a/crates/ghostty-font/src/codepoint_resolver.rs b/crates/ghostty-font/src/codepoint_resolver.rs new file mode 100644 index 0000000..c0de211 --- /dev/null +++ b/crates/ghostty-font/src/codepoint_resolver.rs @@ -0,0 +1,603 @@ +//! CodepointResolver maps a codepoint to a font. It is more dynamic +//! than "Collection" since it supports mapping codepoint ranges to +//! specific fonts, searching for fallback fonts, and more. +//! +//! To initialize the codepoint resolver, construct it with +//! [`CodepointResolver::new`], set the fields you want set, and begin using +//! the resolver. + +#![allow(clippy::collapsible_if, clippy::if_not_else, clippy::float_cmp)] + +use std::collections::HashMap; + +use crate::collection::{ + AddOptions, Collection, Entry, GetFaceError, Index, PresentationMode, Special, +}; +use crate::discovery::Discover; +use crate::face::{RenderOptions, freetype::RenderError as FaceRenderError}; +use crate::sprite; +use crate::{Atlas, CodepointMap, Glyph, Presentation, SpriteFace, Style}; + +/// Packed array of booleans to indicate if a style is enabled or not. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StyleStatus([bool; 4]); + +impl StyleStatus { + /// All styles set to `v`. + pub fn init_fill(v: bool) -> StyleStatus { + StyleStatus([v; 4]) + } + + pub fn get(&self, style: Style) -> bool { + self.0[style as usize] + } + + pub fn set(&mut self, style: Style, v: bool) { + self.0[style as usize] = v; + } +} + +/// Errors from [`CodepointResolver::render_glyph`]. +#[derive(Debug, thiserror::Error)] +pub enum RenderGlyphError { + #[error(transparent)] + GetFace(#[from] GetFaceError), + #[error(transparent)] + Face(#[from] FaceRenderError), + #[error(transparent)] + Sprite(#[from] sprite::face::RenderError), + /// The sprite face was not configured on the resolver. + #[error("sprite face not configured")] + SpriteUnavailable, +} + +impl RenderGlyphError { + /// True if this error is the atlas-full condition (the caller should + /// grow the atlas and retry). + pub fn is_atlas_full(&self) -> bool { + use crate::atlas::Error::AtlasFull; + matches!( + self, + RenderGlyphError::Face(FaceRenderError::Atlas(AtlasFull)) + | RenderGlyphError::Sprite(sprite::face::RenderError::Atlas(AtlasFull)) + ) + } +} + +pub struct CodepointResolver { + /// The underlying collection of fonts. This will be modified as + /// new fonts are found via the resolver. The resolver takes ownership + /// of the collection. + pub collection: Collection, + + /// The set of statuses and whether they're enabled or not. This defaults + /// to true. This can be changed at runtime with no ill effect. + pub styles: StyleStatus, + + /// If discovery is available, we'll look up fonts where we can't find + /// the codepoint. This can be set after initialization. + pub discover: Option, + + /// A map of codepoints to font requests for codepoint-level overrides. + pub codepoint_map: Option, + + /// The descriptor cache is used to cache the descriptor to font face + /// mapping for codepoint maps. + descriptor_cache: DescriptorCache, + + /// Set this to a non-null value to enable sprite glyph drawing. If this + /// isn't enabled we'll just fall through to trying to use regular fonts + /// to render sprite glyphs. But more than likely, if this isn't set then + /// terminal rendering will look wrong. + pub sprite: Option, +} + +/// Map of descriptors to faces. This is used with manual codepoint maps +/// to ensure that we don't load the same font multiple times. +/// +/// Note that the current implementation will load the same font multiple +/// times if the font used for a codepoint map is identical to a font used +/// for a regular style. That's just an inefficient choice made now because +/// the implementation is simpler and codepoint maps matching a regular +/// font is a rare case. +/// +/// As in upstream, descriptors are compared by their `hashcode()` only. +#[derive(Default)] +struct DescriptorCache(HashMap>); + +impl CodepointResolver { + /// A resolver with defaults: all styles enabled, no discovery, no + /// codepoint map, no sprite face. + pub fn new(collection: Collection) -> CodepointResolver { + CodepointResolver { + collection, + styles: StyleStatus::init_fill(true), + discover: None, + codepoint_map: None, + descriptor_cache: DescriptorCache::default(), + sprite: None, + } + } + + /// Looks up the font that should be used for a specific codepoint. + /// The font index is valid as long as font faces aren't removed. This + /// isn't cached; it is expected that downstream users handle caching if + /// that is important. + /// + /// Optionally, a presentation format can be specified. This presentation + /// format will be preferred but if it can't be found in this format, + /// any format will be accepted. If presentation is null, the UCD + /// (Unicode Character Database) will be used to determine the default + /// presentation for the codepoint. + /// + /// This function cannot error! If an error occurs for any reason, + /// the associated functionality is ignored and the + /// resolver attempts to use a different method to satisfy the codepoint. + /// This behavior is intentional to make the resolver apply best-effort + /// logic to satisfy the codepoint since its better to render something + /// than nothing. + /// + /// This logic is relatively complex so the exact algorithm is documented + /// here. If this gets out of sync with the code, ask questions. + /// + /// 1. If a font style is requested that is disabled (i.e. bold), + /// we start over with the regular font style. The regular font style + /// cannot be disabled, but it can be replaced with a stylized font + /// face. + /// + /// 2. If there is a codepoint override for the codepoint, we satisfy + /// that requirement if we can, no matter what style or presentation. + /// + /// 3. If this is a sprite codepoint (such as an underline), then the + /// sprite font always is the result. + /// + /// 4. If the exact style and presentation request can be satisfied by + /// one of our loaded fonts, we return that value. We search loaded + /// fonts in the order they're added to the group, so the caller must + /// set the priority order. + /// + /// 5. If the style isn't regular, we restart this process at this point + /// but with the regular style. This lets us fall back to regular with + /// our loaded fonts before trying a fallback. We'd rather show a regular + /// version of a codepoint from a loaded font than find a new font in + /// the correct style because styles in other fonts often change + /// metrics like glyph widths. + /// + /// 6. If the style is regular, and font discovery is enabled, we look + /// for a fallback font to satisfy our request. + /// + /// 7. Finally, as a last resort, we fall back to restarting this whole + /// process with a regular font face satisfying ANY presentation for + /// the codepoint. If this fails, we return null. + pub fn get_index(&mut self, cp: u32, style: Style, p: Option) -> Option { + // If we've disabled a font style, then fall back to regular. + if style != Style::Regular && !self.styles.get(style) { + return self.get_index(cp, Style::Regular, p); + } + + // Codepoint overrides. + if let Some(idx) = self.get_index_codepoint_override(cp) { + return Some(idx); + } + + // If we have sprite drawing enabled, check if our sprite face can + // handle this. + if let Some(sprite) = &self.sprite { + if sprite.has_codepoint(cp, p) { + return Some(Index::init_special(Special::Sprite)); + } + } + + // Build our presentation mode. If we don't have an explicit presentation + // given then we use the UCD (Unicode Character Database) to determine + // the default presentation. Note there is some inefficiency here because + // we'll do this multiple times if we recurse, but this is a cached function + // call higher up (GroupCache) so this should be rare. + let p_mode = match p { + Some(v) => PresentationMode::Explicit(v), + None => PresentationMode::Default(if ghostty_unicode::emoji_table::table().get(cp) { + Presentation::Emoji + } else { + Presentation::Text + }), + }; + + // If we can find the exact value, then return that. + if let Some(value) = self.collection.get_index(cp, style, p_mode) { + return Some(value); + } + + // If we're not a regular font style, try looking for a regular font + // that will satisfy this request. Blindly looking for unmatched styled + // fonts to satisfy one codepoint results in some ugly rendering. + if style != Style::Regular { + if let Some(value) = self.get_index(cp, Style::Regular, p) { + return Some(value); + } + } + + // If we are regular, try looking for a fallback using discovery. + if style == Style::Regular { + if let Some(idx) = self.discover_fallback(cp, style, p_mode) { + return Some(idx); + } + } + + // If this is regular with any matching presentation, then we are done + // there is nothing more we can do. Otherwise we fall through and do + // an any presentation search. + if style == Style::Regular && p_mode == PresentationMode::Any { + return None; + } + + // For non-regular fonts, we fall back to regular with any presentation + self.collection + .get_index(cp, Style::Regular, PresentationMode::Any) + } + + fn discover_fallback( + &mut self, + cp: u32, + style: Style, + p_mode: PresentationMode, + ) -> Option { + log::debug!("searching for a fallback font for cp={cp:X}"); + let disco = self.discover.as_ref()?; + let load_opts = self.collection.load_options.as_ref()?; + let desc = crate::discovery::Descriptor { + codepoint: cp, + size: load_opts.size.points, + bold: style == Style::Bold || style == Style::BoldItalic, + italic: style == Style::Italic || style == Style::BoldItalic, + monospace: false, + ..Default::default() + }; + let mut disco_it = disco.discover_fallback(&self.collection, &desc).ok()?; + + loop { + let deferred_face = match disco_it.next() { + Ok(Some(f)) => f, + Ok(None) => break, + Err(err) => { + log::warn!("fallback search failed with error err={err}"); + break; + } + }; + + // Discovery is supposed to only return faces that have our + // codepoint but we can't search presentation in discovery so + // we have to check it here. + let entry = Entry { + face: crate::collection::AnyFace::Deferred(deferred_face), + fallback: true, + scale_factor: crate::collection::ScaleFactor::Scale(1.0), + }; + if !entry.has_codepoint(cp, p_mode) { + continue; + } + let crate::collection::AnyFace::Deferred(deferred_face) = entry.face else { + unreachable!() + }; + + log::info!( + "found codepoint 0x{cp:X} in fallback face={}", + deferred_face + .name() + .unwrap_or_else(|_| "".to_string()) + ); + return self + .collection + .add_deferred( + deferred_face, + AddOptions { + style, + fallback: true, + size_adjustment: crate::DEFAULT_FALLBACK_ADJUSTMENT, + }, + ) + .ok(); + } + + log::debug!("no fallback face found for cp={cp:X}"); + None + } + + /// Checks if the codepoint is in the map of codepoint overrides, + /// finds the override font, and returns it. + fn get_index_codepoint_override(&mut self, cp: u32) -> Option { + // Get our codepoint map. If we have no map set then we have no + // codepoint overrides and we're done. + let map = self.codepoint_map.as_ref()?; + + // If the codepoint is in the map, the map returns a descriptor that + // can be used with font discovery to search for a matching font. + let desc = map.get(cp)?; + let key = desc.hashcode(); + + // Fast path: the descriptor is already loaded. This means that we + // already did the search before and we have an exact font for this + // codepoint. + let idx_: Option = if let Some(v) = self.descriptor_cache.0.get(&key) { + *v + } else { + // Slow path: we have to find this descriptor and load the font + let discover = self.discover.as_ref()?; + let mut disco_it = match discover.discover(desc) { + Ok(it) => it, + Err(err) => { + log::warn!("codepoint override failed codepoint={cp} err={err}"); + return None; + } + }; + + let face = match disco_it.next() { + Ok(Some(f)) => f, + Ok(None) => { + log::warn!( + "font lookup for codepoint map failed codepoint={cp} err=FontNotFound" + ); + + // Add null to the cache so we don't do a lookup again later. + self.descriptor_cache.0.insert(key, None); + return None; + } + Err(err) => { + log::warn!("codepoint override failed codepoint={cp} err={err}"); + return None; + } + }; + + // Add the font to our list of fonts so we can get an index for it, + // and ensure the index is stored in the descriptor cache for next time. + let idx = match self.collection.add_deferred( + face, + AddOptions { + style: Style::Regular, + fallback: false, + size_adjustment: crate::DEFAULT_FALLBACK_ADJUSTMENT, + }, + ) { + Ok(idx) => idx, + Err(err) => { + log::warn!("codepoint override failed codepoint={cp} err={err}"); + return None; + } + }; + self.descriptor_cache.0.insert(key, Some(idx)); + + Some(idx) + }; + + // The descriptor cache will populate null if the descriptor is not found + // to avoid expensive discoveries later, so if it is null then we already + // searched and found nothing. + let idx = idx_?; + + // We need to verify that this index has the codepoint we want. + if self + .collection + .has_codepoint(idx, cp, PresentationMode::Any) + { + log::debug!( + "codepoint override based on config codepoint={cp} family={}", + desc.family.as_deref().unwrap_or("") + ); + + return Some(idx); + } + + None + } + + /// Returns the presentation for a specific font index. This is useful for + /// determining what atlas is needed. + pub fn get_presentation( + &mut self, + index: Index, + glyph_index: u32, + ) -> Result { + if let Some(sp) = index.special() { + return Ok(match sp { + Special::Sprite => Presentation::Text, + }); + } + + let face = self.collection.get_face(index)?; + Ok(if face.is_color_glyph(glyph_index) { + Presentation::Emoji + } else { + Presentation::Text + }) + } + + /// Render a glyph by glyph index into the given font atlas and return + /// metadata about it. + /// + /// This performs no caching, it is up to the caller to cache calls to this + /// if they want. This will also not resize the atlas if it is full. + /// + /// IMPORTANT: this renders by /glyph index/ and not by /codepoint/. The caller + /// is expected to translate codepoints to glyph indexes in some way. The most + /// trivial way to do this is to get the Face and call glyph_index. If you're + /// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically + /// determine glyph indexes for a text run. + pub fn render_glyph( + &mut self, + atlas: &mut Atlas, + index: Index, + glyph_index: u32, + opts: RenderOptions, + ) -> Result { + // Special-case fonts are rendered directly. + if let Some(sp) = index.special() { + match sp { + Special::Sprite => { + let sprite = self + .sprite + .as_ref() + .ok_or(RenderGlyphError::SpriteUnavailable)?; + return Ok(sprite.render_glyph(atlas, glyph_index, opts)?); + } + } + } + + let face = self.collection.get_face(index)?; + Ok(face.render_glyph(atlas, glyph_index, opts)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collection::{LoadOptions, SizeAdjustment}; + use crate::face::{DesiredSize, Options}; + use crate::{Face, Library, embedded}; + + fn add(c: &mut Collection, lib: &Library, data: &'static [u8], style: Style, pts: f32) { + let size = if pts == 12.0 { + DesiredSize { + points: 12.0, + xdpi: 96, + ydpi: 96, + } + } else { + DesiredSize::new(pts) + }; + c.add( + Face::init(lib, data.into(), Options::new(size)).unwrap(), + AddOptions { + style, + fallback: false, + size_adjustment: SizeAdjustment::None, + }, + ) + .unwrap(); + } + + #[test] + fn get_index() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + add(&mut c, &lib, embedded::REGULAR, Style::Regular, 12.0); + add(&mut c, &lib, embedded::EMOJI, Style::Regular, 12.0); + add(&mut c, &lib, embedded::EMOJI_TEXT, Style::Regular, 12.0); + + let mut r = CodepointResolver::new(c); + + // Should find all visible ASCII + for i in 32u32..127 { + let idx = r.get_index(i, Style::Regular, None).unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(0, idx.idx); + } + + // Try emoji + { + let idx = r.get_index('🥸' as u32, Style::Regular, None).unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(1, idx.idx); + } + + // Try text emoji + { + let idx = r + .get_index(0x270C, Style::Regular, Some(Presentation::Text)) + .unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(2, idx.idx); + } + { + let idx = r + .get_index(0x270C, Style::Regular, Some(Presentation::Emoji)) + .unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(1, idx.idx); + } + + // Box glyph should be null since we didn't set a box font + assert!(r.get_index(0x1FB00, Style::Regular, None).is_none()); + } + + #[test] + fn get_index_disabled_font_style() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + add(&mut c, &lib, embedded::REGULAR, Style::Regular, 12.0); + add(&mut c, &lib, embedded::REGULAR, Style::Bold, 12.0); + add(&mut c, &lib, embedded::REGULAR, Style::Italic, 12.0); + + let mut r = CodepointResolver::new(c); + r.styles.set(Style::Bold, false); // Disable bold + + // Regular should work fine + { + let idx = r.get_index('A' as u32, Style::Regular, None).unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(0, idx.idx); + } + + // Bold should go to regular + { + let idx = r.get_index('A' as u32, Style::Bold, None).unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(0, idx.idx); + } + + // Italic should still work + { + let idx = r.get_index('A' as u32, Style::Italic, None).unwrap(); + assert_eq!(Style::Italic, idx.style); + assert_eq!(0, idx.idx); + } + } + + #[test] + fn get_index_box_glyph() { + let c = Collection::new(); + + let mut r = CodepointResolver::new(c); + r.sprite = Some(SpriteFace::new(crate::Metrics::calc( + crate::metrics::FaceMetrics { + px_per_em: 30.0, + cell_width: 18.0, + ascent: 30.0, + descent: -6.0, + line_gap: 0.0, + ..Default::default() + }, + ))); + + // Should find a box glyph + let idx = r.get_index(0x2500, Style::Regular, None).unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(Index::SPECIAL_START, idx.idx); + } + + #[test] + fn get_presentation_and_render_sprite() { + let mut r = CodepointResolver::new(Collection::new()); + let metrics = crate::Metrics::calc(crate::metrics::FaceMetrics { + px_per_em: 30.0, + cell_width: 18.0, + ascent: 30.0, + descent: -6.0, + line_gap: 0.0, + ..Default::default() + }); + r.sprite = Some(SpriteFace::new(metrics)); + let sprite_idx = Index::init_special(Special::Sprite); + assert_eq!( + Presentation::Text, + r.get_presentation(sprite_idx, 0x2500).unwrap() + ); + let mut atlas = Atlas::new(256, crate::atlas::Format::Grayscale); + let g = r + .render_glyph(&mut atlas, sprite_idx, 0x2500, RenderOptions::new(metrics)) + .unwrap(); + assert!(g.width > 0 && g.height > 0); + } +} diff --git a/crates/ghostty-font/src/collection.rs b/crates/ghostty-font/src/collection.rs new file mode 100644 index 0000000..547f48f --- /dev/null +++ b/crates/ghostty-font/src/collection.rs @@ -0,0 +1,1481 @@ +//! A font collection is a list of faces of different styles. The list is +//! ordered by priority (per style). All fonts in a collection share the same +//! size so they can be used interchangeably in cases a glyph is missing in one +//! and present in another. +//! +//! The purpose of a collection is to store a list of fonts by style +//! and priority order. A collection does not handle searching for font +//! callbacks, rasterization, etc. For this, see CodepointResolver. +//! +//! The collection can contain both loaded and deferred faces. Deferred faces +//! typically use less memory while still providing some necessary information +//! such as codepoint support, presentation, etc. This is useful for looking +//! for fallback fonts as efficiently as possible. For example, when the glyph +//! "X" is not found, we can quickly search through deferred fonts rather +//! than loading the font completely. + +#![allow(clippy::collapsible_if, clippy::if_not_else, clippy::float_cmp)] + +use crate::config_types::{FontSyntheticStyle, FreetypeLoadFlags}; +use crate::face::{self, DesiredSize}; +use crate::metrics::{FaceMetrics, ModifierSet}; +use crate::{DeferredFace, Face, Library, Metrics, Presentation, Style}; + +/// The available faces we have. This shouldn't be modified manually. +/// Instead, use the functions available on Collection. +/// +/// (Zig: `StyleArray`, an enum array of segmented lists. Aliases are +/// stored as the [`Index`] of the non-alias entry they point to, so there +/// is no need for pointer stability.) +type StyleArray = [Vec; 4]; + +pub struct Collection { + /// The available faces we have. This shouldn't be modified manually. + /// Instead, use the functions available on Collection. + faces: StyleArray, + + /// The metric modifiers to use for this collection. + /// + /// Call `Collection::update_metrics` to recompute the + /// collection's metrics after making changes to these. + pub metric_modifiers: ModifierSet, + + /// Metrics for this collection. Call `Collection::update_metrics` to + /// (re)compute these after adding a primary font or making changes to + /// `metric_modifiers`. + pub metrics: Option, + + /// The face metrics for the primary face in the collection. + /// + /// We keep this around so we don't need to re-compute it when calculating + /// the scale factor for additional fonts added to the collection. + pub primary_face_metrics: Option, + + /// The load options for deferred faces in the face list. If this + /// is not set, then deferred faces will not be loaded. Attempting to + /// add a deferred face will result in an error. + pub load_options: Option, +} + +impl Default for Collection { + fn default() -> Self { + Self::new() + } +} + +/// Options for adding a face to the collection. +#[derive(Debug, Clone, Copy)] +pub struct AddOptions { + /// What style this face is. + pub style: Style, + /// What size adjustment to use. + pub size_adjustment: SizeAdjustment, + /// Whether this is a fallback face. + pub fallback: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum AddError { + /// There's no more room in the collection. + #[error("collection is full")] + CollectionFull, + /// The call to `face.set_size` failed. + #[error("set size failed")] + SetSizeFailed, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum AddDeferredError { + /// There's no more room in the collection. + #[error("collection is full")] + CollectionFull, + /// `load_options` is None, can't do deferred loading. + #[error("deferred loading unavailable")] + DeferredLoadingUnavailable, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum EntryError { + /// Index represents a special font (built-in) and these don't + /// have an associated face. This should be caught upstream and use + /// alternate logic. + #[error("special font has no face")] + SpecialHasNoFace, + + /// Invalid index. + #[error("index out of bounds")] + IndexOutOfBounds, +} + +/// Errors from [`Collection::get_face`]. +#[derive(Debug, thiserror::Error)] +pub enum GetFaceError { + #[error(transparent)] + Entry(#[from] EntryError), + #[error("deferred loading unavailable")] + DeferredLoadingUnavailable, + #[error("face load failed: {0}")] + Load(#[from] crate::deferred_face::LoadError), + #[error("face error: {0}")] + Face(#[from] face::freetype::FaceError), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum CompleteError { + #[error("default face unavailable")] + DefaultUnavailable, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum UpdateMetricsError { + #[error("cannot load primary font")] + CannotLoadPrimaryFont, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum SetSizeError { + #[error(transparent)] + UpdateMetrics(#[from] UpdateMetricsError), + /// `load_options` is `None`. + #[error("deferred loading unavailable")] + DeferredLoadingUnavailable, + /// The call to `face.set_size` failed. + #[error("set size failed")] + SetSizeFailed, +} + +#[derive(Debug, thiserror::Error)] +enum SyntheticError { + #[error("deferred loading unavailable")] + DeferredLoadingUnavailable, + #[error(transparent)] + GetFace(#[from] GetFaceError), + #[error(transparent)] + Face(#[from] face::freetype::FaceError), +} + +impl Collection { + /// Initialize an empty collection. + pub fn new() -> Collection { + Collection { + faces: [Vec::new(), Vec::new(), Vec::new(), Vec::new()], + metric_modifiers: ModifierSet::new(), + metrics: None, + primary_face_metrics: None, + load_options: None, + } + } + + /// Number of entries (including aliases) for a style. + pub fn count(&self, style: Style) -> usize { + self.faces[style as usize].len() + } + + /// Add a face to the collection. This face will be added next in priority + /// if others exist already, i.e. it'll be the _last_ to be searched for a + /// glyph in that list. + /// + /// If no error is encountered then the collection takes ownership of the + /// face. (On error the face is dropped.) + /// + /// When added, the size of the face will be adjusted to match + /// `load_options`. + /// + /// Returns the index for the added face. + pub fn add(&mut self, face: Face, opts: AddOptions) -> Result { + // We have some special indexes so we must never pass those. + let idx = self.faces[opts.style as usize].len(); + if idx >= usize::from(Index::SPECIAL_START) - 1 { + return Err(AddError::CollectionFull); + } + + let mut owned_face = face; + + // Scale factor to adjust the size of the added face. + let scale_factor = self.scale_factor(&owned_face.get_metrics(), opts.size_adjustment); + + // If we have load options, we update the size to ensure + // it's matches and is normalized to the primary if possible. + if let Some(load_opts) = &self.load_options { + let mut new_opts = load_opts.clone(); + new_opts.size.points *= scale_factor as f32; + owned_face + .set_size(new_opts.face_options()) + .map_err(|_| AddError::SetSizeFailed)?; + } + + self.faces[opts.style as usize].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Loaded(owned_face), + fallback: opts.fallback, + scale_factor: ScaleFactor::Scale(scale_factor), + })); + + Ok(Index { + style: opts.style, + idx: idx as u16, + }) + } + + /// Add a deferred face to the collection. + /// + /// Returns the index for the added face. + pub fn add_deferred( + &mut self, + face: DeferredFace, + opts: AddOptions, + ) -> Result { + if self.load_options.is_none() { + return Err(AddDeferredError::DeferredLoadingUnavailable); + } + + // We have some special indexes so we must never pass those. + let idx = self.faces[opts.style as usize].len(); + if idx >= usize::from(Index::SPECIAL_START) - 1 { + return Err(AddDeferredError::CollectionFull); + } + + self.faces[opts.style as usize].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Deferred(face), + fallback: opts.fallback, + scale_factor: ScaleFactor::Adjustment(opts.size_adjustment), + })); + + Ok(Index { + style: opts.style, + idx: idx as u16, + }) + } + + /// Return the Face represented by a given Index. The returned reference + /// is only valid as long as this collection is not modified. + /// + /// This will initialize the face if it is deferred and not yet loaded, + /// which can fail. + pub fn get_face(&mut self, index: Index) -> Result<&mut Face, GetFaceError> { + let resolved = self.resolve(index)?; + self.get_face_from_entry(resolved) + } + + /// Return the face for an index only if it is already loaded (never + /// triggers loading, so it only needs a shared reference). Returns `None` + /// for special, out-of-range or still-deferred entries. + pub fn get_loaded_face(&self, index: Index) -> Option<&Face> { + let resolved = self.resolve(index).ok()?; + match &self.entry_at(resolved).face { + AnyFace::Loaded(f) => Some(f), + AnyFace::Deferred(_) => None, + } + } + + /// Resolve an index (which might point at an alias) to the index of the + /// non-alias entry. + fn resolve(&self, index: Index) -> Result { + if index.special().is_some() { + return Err(EntryError::SpecialHasNoFace); + } + let list = &self.faces[index.style as usize]; + match list.get(usize::from(index.idx)) { + None => Err(EntryError::IndexOutOfBounds), + Some(EntryOrAlias::Entry(_)) => Ok(index), + Some(EntryOrAlias::Alias(target)) => Ok(*target), + } + } + + fn entry_at(&self, resolved: Index) -> &Entry { + match &self.faces[resolved.style as usize][usize::from(resolved.idx)] { + EntryOrAlias::Entry(e) => e, + EntryOrAlias::Alias(_) => unreachable!("alias must point to a non-alias entry"), + } + } + + fn entry_at_mut(&mut self, resolved: Index) -> &mut Entry { + match &mut self.faces[resolved.style as usize][usize::from(resolved.idx)] { + EntryOrAlias::Entry(e) => e, + EntryOrAlias::Alias(_) => unreachable!("alias must point to a non-alias entry"), + } + } + + /// Get the unaliased entry from an index + pub fn get_entry(&mut self, index: Index) -> Result<&mut Entry, EntryError> { + let resolved = self.resolve(index)?; + Ok(self.entry_at_mut(resolved)) + } + + /// Get the unaliased entry from an index (shared). + pub fn get_const_entry(&self, index: Index) -> Result<&Entry, EntryError> { + let resolved = self.resolve(index)?; + Ok(self.entry_at(resolved)) + } + + /// Get the face from an entry (loading it if deferred). + /// + /// This entry must not be an alias. + fn get_face_from_entry(&mut self, entry: Index) -> Result<&mut Face, GetFaceError> { + if matches!(self.entry_at(entry).face, AnyFace::Deferred(_)) { + let mut opts = self + .load_options + .clone() + .ok_or(GetFaceError::DeferredLoadingUnavailable)?; + + // Load the face. + let mut face = match &mut self.entry_at_mut(entry).face { + AnyFace::Deferred(d) => d.load(&opts.library, opts.face_options())?, + AnyFace::Loaded(_) => unreachable!(), + }; + + // Calculate the scale factor for this + // entry now that we have a loaded face. + let mut scale = self.entry_at(entry).scale_factor; + if let ScaleFactor::Adjustment(adj) = scale { + let factor = self.scale_factor(&face.get_metrics(), adj); + scale = ScaleFactor::Scale(factor); + self.entry_at_mut(entry).scale_factor = scale; + } + + // If our scale factor is something other + // than 1.0 then we need to resize the face. + if let ScaleFactor::Scale(s) = scale { + if s != 1.0 { + opts.size.points *= s as f32; + face.set_size(opts.face_options())?; + } + } + + // Set the loaded face on the entry (dropping the deferred face). + self.entry_at_mut(entry).face = AnyFace::Loaded(face); + } + + match &mut self.entry_at_mut(entry).face { + AnyFace::Loaded(f) => Ok(f), + AnyFace::Deferred(_) => unreachable!(), + } + } + + /// Return the index of the font in this collection that contains + /// the given codepoint, style, and presentation. If no font is found, + /// None is returned. + /// + /// This does not trigger font loading; deferred fonts can be + /// searched for codepoints. + pub fn get_index(&self, cp: u32, style: Style, p_mode: PresentationMode) -> Option { + for (i, entry_or_alias) in self.faces[style as usize].iter().enumerate() { + if self.const_entry(entry_or_alias).has_codepoint(cp, p_mode) { + return Some(Index { + style, + idx: i as u16, + }); + } + } + + // Not found + None + } + + fn const_entry<'a>(&'a self, e: &'a EntryOrAlias) -> &'a Entry { + match e { + EntryOrAlias::Entry(e) => e, + EntryOrAlias::Alias(i) => self.entry_at(*i), + } + } + + /// Check if a specific font index has a specific codepoint. This does not + /// necessarily force the font to load. The presentation value "p" will + /// verify the Emoji representation matches if it is non-null. If "p" is + /// null then any presentation will be accepted. + pub fn has_codepoint(&self, index: Index, cp: u32, p_mode: PresentationMode) -> bool { + let list = &self.faces[index.style as usize]; + match list.get(usize::from(index.idx)) { + None => false, + Some(e) => self.const_entry(e).has_codepoint(cp, p_mode), + } + } + + /// Ensure we have an option for all styles in the collection, such + /// as italic and bold by synthesizing them if necessary from the + /// first regular face that has text glyphs. + /// + /// If there is no regular face that has text glyphs, then this + /// does nothing. + pub fn complete_styles( + &mut self, + synthetic_config: FontSyntheticStyle, + ) -> Result<(), CompleteError> { + // If every style has at least one entry then we're done! + // This is the most common case. + if self.faces.iter().all(|l| !l.is_empty()) { + return Ok(()); + } + + // Find the first regular face that has non-colorized text glyphs. + // This is the font we want to fallback to. This may not be index zero + // if a user configures something like an Emoji font first. + let regular_entry: Index = 'entry: { + let count = self.faces[Style::Regular as usize].len(); + if count == 0 { + return Ok(()); + } + + // Find our first regular face that has text glyphs. + for i in 0..count { + // Load our face. If we fail to load it, we just skip it and + // continue on to try the next one. + let index = Index { + style: Style::Regular, + idx: i as u16, + }; + let resolved = self.resolve(index).expect("index in range"); + let face = match self.get_face_from_entry(resolved) { + Ok(f) => f, + Err(err) => { + log::warn!("error loading regular entry={i} err={err}"); + continue; + } + }; + + // We have two conditionals here. The color check is obvious: + // we want to auto-italicize a normal text font. The second + // check is less obvious... for mixed color/non-color fonts, we + // accept the regular font if it has basic ASCII. This may not + // be strictly correct (especially with international fonts) but + // it's a reasonable heuristic and the first case will match 99% + // of the time. + if !face.has_color() || face.glyph_index('A' as u32).is_some() { + break 'entry resolved; + } + } + + // No regular text face found. We can't provide any fallback. + return Err(CompleteError::DefaultUnavailable); + }; + + // If we don't have italic, attempt to create a synthetic italic face. + // If we can't create a synthetic italic face, we'll just use the regular + // face for italic. + let have_italic = !self.faces[Style::Italic as usize].is_empty(); + if !have_italic { + if !synthetic_config.italic { + log::info!("italic style not available and synthetic italic disabled"); + self.faces[Style::Italic as usize].push(EntryOrAlias::Alias(regular_entry)); + } else { + match self.synthetic_italic(regular_entry) { + Ok(synthetic) => { + log::info!("synthetic italic face created"); + self.faces[Style::Italic as usize].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Loaded(synthetic), + fallback: false, + scale_factor: ScaleFactor::Scale(1.0), + })); + } + Err(err) => { + log::warn!( + "failed to create synthetic italic, italic style will not be available err={err}" + ); + self.faces[Style::Italic as usize].push(EntryOrAlias::Alias(regular_entry)); + } + } + } + } + + // If we don't have bold, use the regular font. + let have_bold = !self.faces[Style::Bold as usize].is_empty(); + if !have_bold { + if !synthetic_config.bold { + log::info!("bold style not available and synthetic bold disabled"); + self.faces[Style::Bold as usize].push(EntryOrAlias::Alias(regular_entry)); + } else { + match self.synthetic_bold(regular_entry) { + Ok(synthetic) => { + log::info!("synthetic bold face created"); + self.faces[Style::Bold as usize].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Loaded(synthetic), + fallback: false, + scale_factor: ScaleFactor::Scale(1.0), + })); + } + Err(err) => { + log::warn!( + "failed to create synthetic bold, bold style will not be available err={err}" + ); + self.faces[Style::Bold as usize].push(EntryOrAlias::Alias(regular_entry)); + } + } + } + } + + // If we don't have bold italic, we attempt to synthesize a bold variant + // of the italic font. If we can't do that, we'll use the italic font. + if self.faces[Style::BoldItalic as usize].is_empty() { + self.complete_bold_italic(synthetic_config, regular_entry, have_bold); + } + + Ok(()) + } + + fn complete_bold_italic( + &mut self, + synthetic_config: FontSyntheticStyle, + regular_entry: Index, + have_bold: bool, + ) { + let bi = Style::BoldItalic as usize; + if !synthetic_config.bold_italic { + log::info!("bold italic style not available and synthetic bold italic disabled"); + self.faces[bi].push(EntryOrAlias::Alias(regular_entry)); + return; + } + + // Prefer to synthesize on top of the face we already had. If we + // have bold then we try to synthesize italic on top of bold. + if have_bold { + let base_entry = self + .resolve(Index { + style: Style::Bold, + idx: 0, + }) + .expect("bold entry exists"); + if let Ok(synthetic) = self.synthetic_italic(base_entry) { + log::info!("synthetic bold italic face created from bold"); + self.faces[bi].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Loaded(synthetic), + fallback: false, + scale_factor: ScaleFactor::Scale(1.0), + })); + return; + } + + // If synthesizing italic failed, then we try to synthesize + // bold on whatever italic font we have. + } + + let base_entry = self + .resolve(Index { + style: Style::Italic, + idx: 0, + }) + .expect("italic entry exists"); + if let Ok(synthetic) = self.synthetic_bold(base_entry) { + log::info!("synthetic bold italic face created from italic"); + self.faces[bi].push(EntryOrAlias::Entry(Entry { + face: AnyFace::Loaded(synthetic), + fallback: false, + scale_factor: ScaleFactor::Scale(1.0), + })); + return; + } + + log::warn!("bold italic style not available, using italic font"); + self.faces[bi].push(EntryOrAlias::Alias(base_entry)); + } + + /// Create a synthetic bold font face from the given entry and return it. + fn synthetic_bold(&mut self, entry: Index) -> Result { + // We require loading options to create a synthetic bold face. + let opts = self + .load_options + .clone() + .ok_or(SyntheticError::DeferredLoadingUnavailable)?; + + // Try to bold it. + let regular = self.get_face_from_entry(entry)?; + + // NOTE: upstream computes a `face_opts` with the size inherited from + // the regular face but then (accidentally) passes `opts.faceOptions()` + // instead; we mirror that behaviour. + let face = regular.synthetic_bold(opts.face_options())?; + + log::info!("font synthetic bold created family={}", face.name()); + + Ok(face) + } + + /// Create a synthetic italic font face from the given entry and return it. + fn synthetic_italic(&mut self, entry: Index) -> Result { + // We require loading options to create a synthetic italic face. + let opts = self + .load_options + .clone() + .ok_or(SyntheticError::DeferredLoadingUnavailable)?; + + // Try to italicize it. + let regular = self.get_face_from_entry(entry)?; + + // See the NOTE in `synthetic_bold`. + let face = regular.synthetic_italic(opts.face_options())?; + + log::info!("font synthetic italic created family={}", face.name()); + + Ok(face) + } + + /// Update the size of all faces in the collection. This will + /// also update the size in the load options for future deferred + /// face loading. + /// + /// This requires load options to be set. + pub fn set_size(&mut self, size: DesiredSize) -> Result<(), SetSizeError> { + // Get our options so we can modify the size. + let opts = { + let opts = self + .load_options + .as_mut() + .ok_or(SetSizeError::DeferredLoadingUnavailable)?; + opts.size = size; + opts.clone() + }; + + let primary_entry = self + .resolve(Index { + style: Style::Regular, + idx: 0, + }) + .ok(); + + // Resize all our faces that are loaded + for style in Style::ALL { + for i in 0..self.faces[style as usize].len() { + // Resize all faces. We skip entries that are aliases, since + // the underlying face will have a non-alias entry somewhere. + if matches!(self.faces[style as usize][i], EntryOrAlias::Alias(_)) { + continue; + } + let this = Index { + style, + idx: i as u16, + }; + + let entry = self.entry_at_mut(this); + let scale = entry.scale_factor; + if let AnyFace::Loaded(face) = &mut entry.face { + // If this isn't our primary face, we scale + // the size appropriately before setting it. + // + // If we don't have a primary face we also don't. + let mut new_opts = opts.clone(); + if primary_entry.is_some() && Some(this) != primary_entry { + if let ScaleFactor::Scale(s) = scale { + new_opts.size.points *= s as f32; + } + } + face.set_size(new_opts.face_options()) + .map_err(|_| SetSizeError::SetSizeFailed)?; + } + } + } + + Ok(self.update_metrics()?) + } + + /// Calculate a factor by which to scale the provided face to match + /// it with the primary face, depending on the specified adjustment. + /// + /// If this encounters any problems loading the primary face or its + /// metrics then it just returns `1.0`. + /// + /// This functions very much like the `font-size-adjust` CSS property. + /// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust + fn scale_factor(&mut self, face_metrics: &FaceMetrics, adjustment: SizeAdjustment) -> f64 { + // If there's no adjustment, the scale is 1.0 + if adjustment == SizeAdjustment::None { + return 1.0; + } + + // If we haven't calculated our primary face metrics yet, do so now. + if self.primary_face_metrics.is_none() { + // If we can't load the primary face, just use 1.0 as the scale factor. + let Ok(primary_face) = self.get_face(Index::default()) else { + return 1.0; + }; + self.primary_face_metrics = Some(primary_face.get_metrics()); + } + + let primary_metrics = self.primary_face_metrics.expect("set above"); + + // We normalize the metrics values which are expressed in px to instead + // be in ems, so that it doesn't matter what size the faces actually are. + let primary_scale = 1.0 / primary_metrics.px_per_em; + let face_scale = 1.0 / face_metrics.px_per_em; + + // We get the target metrics from the primary face and this face depending + // on the specified `adjustment`. If this face doesn't explicitly define a + // metric, or if the value it defines is invalid, we fall through to other + // options in the order below. + // + // In order to make sure the value is valid, we compare it with the result + // of the estimator function, which rules out both null and invalid values. + let mut adj = adjustment; + let (primary_metric, face_metric) = loop { + match adj { + SizeAdjustment::IcWidth => { + if face_metrics.ic_width != Some(face_metrics.ic_width()) { + adj = SizeAdjustment::ExHeight; + continue; + } + break ( + primary_metrics.ic_width() * primary_scale, + face_metrics.ic_width() * face_scale, + ); + } + SizeAdjustment::ExHeight => { + if face_metrics.ex_height != Some(face_metrics.ex_height()) { + adj = SizeAdjustment::CapHeight; + continue; + } + break ( + primary_metrics.ex_height() * primary_scale, + face_metrics.ex_height() * face_scale, + ); + } + SizeAdjustment::CapHeight => { + if face_metrics.cap_height != Some(face_metrics.cap_height()) { + adj = SizeAdjustment::LineHeight; + continue; + } + break ( + primary_metrics.cap_height() * primary_scale, + face_metrics.cap_height() * face_scale, + ); + } + SizeAdjustment::LineHeight => { + break ( + primary_metrics.line_height() * primary_scale, + face_metrics.line_height() * face_scale, + ); + } + SizeAdjustment::None => unreachable!(), + } + }; + + primary_metric / face_metric + } + + /// Update the cell metrics for this collection, based on + /// the primary font and the modifiers in `metric_modifiers`. + /// + /// This requires a primary font (index `0`) to be present. + pub fn update_metrics(&mut self) -> Result<(), UpdateMetricsError> { + let primary_face = self + .get_face(Index::default()) + .map_err(|_| UpdateMetricsError::CannotLoadPrimaryFont)?; + + let fm = primary_face.get_metrics(); + self.primary_face_metrics = Some(fm); + + let mut metrics = Metrics::calc(fm); + + metrics.apply(&self.metric_modifiers); + + self.metrics = Some(metrics); + Ok(()) + } +} + +/// Options for adjusting the size of a face relative to the primary face. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SizeAdjustment { + /// Don't adjust the size for this face, use the original point size. + None, + /// Match ideograph character width with the primary face. + IcWidth, + /// Match ex height with the primary face. + ExHeight, + /// Match cap height with the primary face. + CapHeight, + /// Match line height with the primary face. + LineHeight, +} + +/// Load options are used to configure all the details a Collection +/// needs to load deferred faces. +#[derive(Clone)] +pub struct LoadOptions { + /// The library to use for loading faces. This is not owned by + /// the collection and can be used by multiple collections. + pub library: Library, + + /// The desired font size for all loaded faces. + pub size: DesiredSize, + + /// Freetype Load Flags to use when loading glyphs. This is a list of + /// bitfield constants that controls operations to perform during glyph + /// loading. Only a subset is exposed for configuration, for the whole set + /// of flags see `pkg.freetype.face.LoadFlags`. + pub freetype_load_flags: FreetypeLoadFlags, +} + +impl LoadOptions { + /// Options with the default size (12 points) and load flags. + pub fn new(library: Library) -> LoadOptions { + LoadOptions { + library, + size: DesiredSize::new(12.0), + freetype_load_flags: face::FREETYPE_LOAD_FLAGS_DEFAULT, + } + } + + /// The options to use for loading faces. + pub fn face_options(&self) -> face::Options { + face::Options { + size: self.size, + freetype_load_flags: self.freetype_load_flags, + } + } +} + +pub enum AnyFace { + /// Not yet loaded. + Deferred(DeferredFace), + + /// Loaded. + Loaded(Face), +} + +/// Factor to multiply the collection size by for a face, or else the size +/// adjustment that should be used to calculate it once the face is loaded. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ScaleFactor { + Adjustment(SizeAdjustment), + Scale(f64), +} + +/// A entry in a collection can be deferred or loaded. A deferred face +/// is not yet fully loaded and only represents the font descriptor +/// and usually uses less resources. A loaded face is fully parsed, +/// ready to rasterize, and usually uses more resources than a +/// deferred version. +/// +/// A face can also be a "fallback" variant that is still either +/// deferred or loaded. Today, there is only one difference between +/// fallback and non-fallback (or "explicit") faces: the handling +/// of emoji presentation. +/// +/// For explicit faces, when an explicit emoji presentation is +/// not requested, we will use any glyph for that codepoint found +/// even if the font presentation does not match the UCD +/// (Unicode Character Database) value. When an explicit presentation +/// is requested (via either VS15/V16), that is always honored. +/// The reason we do this is because we assume that if a user +/// explicitly chosen a font face (hence it is "explicit" and +/// not "fallback"), they want to use any glyphs possible within that +/// font face. Fallback fonts on the other hand are picked as a +/// last resort, so we should prefer exactness if possible. +pub struct Entry { + pub face: AnyFace, + + /// Whether this face is a fallback face, see + /// main doc comment on Entry for more info. + pub fallback: bool, + + /// Factor to multiply the collection size by for this face, or + /// else the size adjustment that should be used to calculate + /// once the face is loaded. + /// + /// This is computed when the face is loaded, based on a scale + /// factor computed for an adjustment from the primary face to + /// this one, which allows fallback fonts to be harmonized with + /// the primary font by matching one of the metrics between them. + pub scale_factor: ScaleFactor, +} + +impl Entry { + /// If this face is loaded, then this returns the `Face`, + /// otherwise returns None. + pub fn get_loaded(&mut self) -> Option<&mut Face> { + match &mut self.face { + AnyFace::Deferred(_) => None, + AnyFace::Loaded(face) => Some(face), + } + } + + /// True if the entry is deferred. + pub fn is_deferred(&self) -> bool { + matches!(self.face, AnyFace::Deferred(_)) + } + + /// True if this face satisfies the given codepoint and presentation. + pub fn has_codepoint(&self, cp: u32, p_mode: PresentationMode) -> bool { + let mut mode = p_mode; + loop { + match mode { + PresentationMode::Default(p) => { + mode = if self.fallback { + // Fallback fonts require explicit presentation matching. + PresentationMode::Explicit(p) + } else { + // Non-fallback fonts do not. + PresentationMode::Any + }; + } + PresentationMode::Explicit(p) => { + return match &self.face { + AnyFace::Deferred(v) => v.has_codepoint(cp, Some(p)), + AnyFace::Loaded(face) => { + let Some(index) = face.glyph_index(cp) else { + return false; + }; + match p { + Presentation::Text => !face.is_color_glyph(index), + Presentation::Emoji => face.is_color_glyph(index), + } + } + }; + } + PresentationMode::Any => { + return match &self.face { + AnyFace::Deferred(v) => v.has_codepoint(cp, None), + AnyFace::Loaded(face) => face.glyph_index(cp).is_some(), + }; + } + } + } + } +} + +pub enum EntryOrAlias { + Entry(Entry), + + /// An alias to another entry. This is used to share the same face, + /// avoid memory duplication. An alias must point to a non-alias entry. + Alias(Index), +} + +/// The requested presentation for a codepoint. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PresentationMode { + /// The codepoint has an explicit presentation that is required, + /// i.e. VS15/V16. + Explicit(Presentation), + + /// The codepoint has no explicit presentation and we should use + /// the presentation from the UCD. + Default(Presentation), + + /// The codepoint can be any presentation. + Any, +} + +/// The special-case fonts that we support. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u16)] +pub enum Special { + /// Sprite drawing, this is rendered JIT using 2D graphics APIs. + Sprite = Index::SPECIAL_START, +} + +/// This represents a specific font in the collection. +/// +/// The Zig version is a `packed struct(u16)` with a 3 bit style and 13 bit +/// index (up to 8192 fonts per style). Here the two parts are separate +/// fields, with [`Index::int`] producing the packed representation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Index { + pub style: Style, + pub idx: u16, +} + +impl Default for Index { + fn default() -> Self { + Index { + style: Style::Regular, + idx: 0, + } + } +} + +impl Index { + /// The number of bits we use for the index. + pub const IDX_BITS: u32 = 13; + + /// We start all special fonts at this index so they can be detected + /// (`maxInt(IndexInt)`). + pub const SPECIAL_START: u16 = (1 << Self::IDX_BITS) - 1; + + /// Initialize a special font index. + pub const fn init_special(v: Special) -> Index { + Index { + style: Style::Regular, + idx: v as u16, + } + } + + /// Convert to the packed int (style in the low 3 bits). + pub fn int(self) -> u16 { + (self.style as u16) | (self.idx << 3) + } + + /// Returns Some if this is a "special" index which doesn't map to + /// a real font face. We can still render it but there is no face for + /// this font. + pub fn special(self) -> Option { + if self.idx < Self::SPECIAL_START { + return None; + } + Some(Special::Sprite) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::face::{DesiredSize, Options}; + + fn opts_96(points: f32) -> Options { + Options::new(DesiredSize { + points, + xdpi: 96, + ydpi: 96, + }) + } + + fn add_opts(style: Style, adj: SizeAdjustment) -> AddOptions { + AddOptions { + style, + fallback: false, + size_adjustment: adj, + } + } + + fn approx(a: f64, b: f64) -> bool { + (a - b).abs() < 1e-3 + } + + fn assert_metrics_approx(expected: &Metrics, actual: &Metrics) { + assert_eq!(expected.cell_width, actual.cell_width); + assert_eq!(expected.cell_height, actual.cell_height); + assert_eq!(expected.cell_baseline, actual.cell_baseline); + assert_eq!(expected.underline_position, actual.underline_position); + assert_eq!(expected.underline_thickness, actual.underline_thickness); + assert_eq!( + expected.strikethrough_position, + actual.strikethrough_position + ); + assert_eq!( + expected.strikethrough_thickness, + actual.strikethrough_thickness + ); + assert_eq!(expected.overline_position, actual.overline_position); + assert_eq!(expected.overline_thickness, actual.overline_thickness); + assert_eq!(expected.box_thickness, actual.box_thickness); + assert_eq!(expected.cursor_height, actual.cursor_height); + assert!( + approx(expected.icon_height, actual.icon_height), + "icon_height" + ); + assert!( + approx(expected.icon_height_single, actual.icon_height_single), + "icon_height_single" + ); + assert!(approx(expected.face_width, actual.face_width), "face_width"); + assert!( + approx(expected.face_height, actual.face_height), + "face_height" + ); + assert!(approx(expected.face_y, actual.face_y), "face_y"); + } + + #[test] + fn index_layout() { + // We never want to take up more than a u16 since font indexes are + // everywhere. The maximum number of fonts for a style is 13 bits. + assert_eq!(2, std::mem::size_of::()); + assert_eq!(13, Index::IDX_BITS); + assert_eq!(8191, Index::SPECIAL_START); + let sprite = Index::init_special(Special::Sprite); + assert_eq!(Some(Special::Sprite), sprite.special()); + assert_eq!(None, Index::default().special()); + assert_eq!(Index::SPECIAL_START << 3, sprite.int()); + } + + #[test] + fn init() { + let c = Collection::new(); + drop(c); + } + + #[test] + fn add_full() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + + for _ in 0..(usize::from(Index::SPECIAL_START) - 1) { + c.add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + } + + let face = Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(); + assert_eq!( + Err(AddError::CollectionFull), + c.add(face, add_opts(Style::Regular, SizeAdjustment::None)) + ); + } + + #[test] + fn add_deferred_without_loading_options() { + let mut c = Collection::new(); + // The deferred face can be anything since it should never be accessed. + let deferred = DeferredFace::default(); + assert_eq!( + Err(AddDeferredError::DeferredLoadingUnavailable), + c.add_deferred(deferred, add_opts(Style::Regular, SizeAdjustment::None)) + ); + } + + #[test] + fn get_face() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + let idx = c + .add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + let p1 = std::ptr::from_ref::(c.get_face(idx).unwrap()); + let p2 = std::ptr::from_ref::(c.get_face(idx).unwrap()); + assert_eq!(p1, p2); + } + + #[test] + fn get_index() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + // Should find all visible ASCII + for i in 32u32..127 { + assert!( + c.get_index(i, Style::Regular, PresentationMode::Any) + .is_some() + ); + } + + // Should not find emoji + assert!( + c.get_index('🥸' as u32, Style::Regular, PresentationMode::Any) + .is_none() + ); + } + + #[test] + fn complete_styles() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + c.add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + let a = 'A' as u32; + assert!(c.get_index(a, Style::Bold, PresentationMode::Any).is_none()); + assert!( + c.get_index(a, Style::Italic, PresentationMode::Any) + .is_none() + ); + assert!( + c.get_index(a, Style::BoldItalic, PresentationMode::Any) + .is_none() + ); + c.complete_styles(FontSyntheticStyle::default()).unwrap(); + assert!(c.get_index(a, Style::Bold, PresentationMode::Any).is_some()); + assert!( + c.get_index(a, Style::Italic, PresentationMode::Any) + .is_some() + ); + assert!( + c.get_index(a, Style::BoldItalic, PresentationMode::Any) + .is_some() + ); + } + + #[test] + fn complete_styles_synthetic_disabled_uses_aliases() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + c.add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + c.complete_styles(FontSyntheticStyle { + bold: false, + italic: false, + bold_italic: false, + }) + .unwrap(); + for style in [Style::Bold, Style::Italic, Style::BoldItalic] { + assert_eq!(1, c.count(style)); + let idx = c + .get_index('A' as u32, style, PresentationMode::Any) + .unwrap(); + // The alias resolves to the regular entry's face. + let p1 = std::ptr::from_ref::(c.get_face(idx).unwrap()); + let p2 = std::ptr::from_ref::(c.get_face(Index::default()).unwrap()); + assert_eq!(p1, p2); + } + } + + #[test] + fn set_size() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + c.add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + assert_eq!(12.0, c.load_options.as_ref().unwrap().size.points); + c.set_size(DesiredSize::new(24.0)).unwrap(); + assert_eq!(24.0, c.load_options.as_ref().unwrap().size.points); + } + + #[test] + fn has_codepoint() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + let idx = c + .add( + Face::init(&lib, embedded::REGULAR.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + assert!(c.has_codepoint(idx, 'A' as u32, PresentationMode::Any)); + assert!(!c.has_codepoint(idx, '🥸' as u32, PresentationMode::Any)); + } + + #[test] + fn has_codepoint_emoji_default_graphical() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + let idx = c + .add( + Face::init(&lib, embedded::EMOJI.into(), opts_96(12.0)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + assert!(!c.has_codepoint(idx, 'A' as u32, PresentationMode::Any)); + assert!(c.has_codepoint(idx, '🥸' as u32, PresentationMode::Any)); + // TODO(fontmem): test explicit/implicit + } + + #[test] + fn metrics() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + let size = DesiredSize { + points: 12.0, + xdpi: 96, + ydpi: 96, + }; + let mut lo = LoadOptions::new(lib.clone()); + lo.size = size; + c.load_options = Some(lo); + + c.add( + Face::init(&lib, embedded::INCONSOLATA.into(), Options::new(size)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + c.update_metrics().unwrap(); + + let base = Metrics { + cell_width: 8, + // The cell height is 17 px because the calculation is + // + // ascender - descender + gap + // + // which, for inconsolata is + // + // 859 - -190 + 0 + // + // font units, at 1000 units per em that works out to 1.049 em, + // and 1em should be the point size * dpi scale, so 12 * (96/72) + // which is 16, and 16 * 1.049 = 16.784, which finally is rounded + // to 17. + // + // The icon height is (2 * cap_height + face_height) / 3 + // = (2 * 623 + 1049) / 3 = 765, and 16 * 0.765 = 12.24. + cell_height: 17, + cell_baseline: 3, + underline_position: 17, + underline_thickness: 1, + strikethrough_position: 10, + strikethrough_thickness: 1, + overline_position: 0, + overline_thickness: 1, + box_thickness: 1, + cursor_height: 17, + icon_height: 16.784, + icon_height_single: 12.24, + face_width: 8.0, + face_height: 16.784, + face_y: -0.04, + cursor_thickness: 1, + }; + assert_metrics_approx(&base, &c.metrics.unwrap()); + + // Resize should change metrics + c.set_size(DesiredSize { + points: 24.0, + xdpi: 96, + ydpi: 96, + }) + .unwrap(); + let big = Metrics { + cell_width: 16, + cell_height: 34, + cell_baseline: 6, + underline_position: 34, + underline_thickness: 2, + strikethrough_position: 19, + strikethrough_thickness: 2, + overline_position: 0, + overline_thickness: 2, + box_thickness: 2, + cursor_height: 34, + icon_height: 33.568, + icon_height_single: 24.48, + face_width: 16.0, + face_height: 33.568, + face_y: -0.08, + cursor_thickness: 1, + }; + assert_metrics_approx(&big, &c.metrics.unwrap()); + } + + // TODO: Also test CJK fallback sizing, we don't currently have a CJK test font. + #[test] + fn adjusted_sizes() { + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + let size = DesiredSize { + points: 12.0, + xdpi: 96, + ydpi: 96, + }; + let mut lo = LoadOptions::new(lib.clone()); + lo.size = size; + c.load_options = Some(lo); + + // Add our primary face. + c.add( + Face::init(&lib, embedded::INCONSOLATA.into(), Options::new(size)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + c.update_metrics().unwrap(); + + for metric in [SizeAdjustment::ExHeight, SizeAdjustment::CapHeight] { + let pick = |m: &FaceMetrics| match metric { + SizeAdjustment::ExHeight => m.ex_height.unwrap(), + SizeAdjustment::CapHeight => m.cap_height.unwrap(), + _ => unreachable!(), + }; + + // Add the fallback face with the chosen adjustment metric. + let fallback_idx = c + .add( + Face::init(&lib, embedded::MONASPACE_NEON.into(), Options::new(size)).unwrap(), + add_opts(Style::Regular, metric), + ) + .unwrap(); + + // The chosen metric should match. + { + let pm = c.get_face(Index::default()).unwrap().get_metrics(); + let fm = c.get_face(fallback_idx).unwrap().get_metrics(); + // We accept anything within half a pixel. + assert!((pick(&pm) - pick(&fm)).abs() <= 0.5); + } + + // Resize should keep that relationship. + c.set_size(DesiredSize { + points: 37.0, + xdpi: 96, + ydpi: 96, + }) + .unwrap(); + { + let pm = c.get_face(Index::default()).unwrap().get_metrics(); + let fm = c.get_face(fallback_idx).unwrap().get_metrics(); + assert!((pick(&pm) - pick(&fm)).abs() <= 0.5); + } + + // Reset size for the next iteration + c.set_size(size).unwrap(); + } + + { + // A reference metric of "none" should leave the size unchanged. + let fallback_idx = c + .add( + Face::init(&lib, embedded::MONASPACE_NEON.into(), Options::new(size)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::None), + ) + .unwrap(); + + let primary_points = c.get_face(Index::default()).unwrap().size.points; + assert_eq!( + primary_points, + c.get_face(fallback_idx).unwrap().size.points + ); + + // Resize should keep that. + c.set_size(DesiredSize { + points: 37.0, + xdpi: 96, + ydpi: 96, + }) + .unwrap(); + + let primary_points = c.get_face(Index::default()).unwrap().size.points; + assert_eq!( + primary_points, + c.get_face(fallback_idx).unwrap().size.points + ); + + // Reset collection size + c.set_size(size).unwrap(); + } + + // Add the symbol face. + let symbol_idx = c + .add( + Face::init(&lib, embedded::SYMBOLS_NERD_FONT.into(), Options::new(size)).unwrap(), + add_opts(Style::Regular, SizeAdjustment::ExHeight), + ) + .unwrap(); + + // Test fallback to line_height() (ex_height and cap_height not defined + // in symbols font). + { + let pm = c.get_face(Index::default()).unwrap().get_metrics(); + let sm = c.get_face(symbol_idx).unwrap().get_metrics(); + // We accept anything within half a pixel. + assert!((pm.line_height() - sm.line_height()).abs() <= 0.5); + } + } +} diff --git a/crates/ghostty-font/src/config_types.rs b/crates/ghostty-font/src/config_types.rs new file mode 100644 index 0000000..30b6f43 --- /dev/null +++ b/crates/ghostty-font/src/config_types.rs @@ -0,0 +1,59 @@ +//! Small local mirrors of the `ghostty-config` types that the font engine +//! consumes. +//! +//! TODO(integration): `ghostty-config` should map its own +//! `FreetypeLoadFlags`, `FontSyntheticStyle` and `FontShapingBreak` onto +//! these (or this crate should re-export the config ones once the config +//! crate exists in the dependency graph). + +/// See `freetype-load-flags` in the config. Defaults match upstream. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FreetypeLoadFlags { + pub hinting: bool, + pub force_autohint: bool, + pub monochrome: bool, + pub autohint: bool, + pub light: bool, +} + +impl Default for FreetypeLoadFlags { + fn default() -> Self { + Self { + hinting: true, + force_autohint: false, + monochrome: false, + autohint: true, + light: true, + } + } +} + +/// See `font-synthetic-style` in the config. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FontSyntheticStyle { + pub bold: bool, + pub italic: bool, + pub bold_italic: bool, +} + +impl Default for FontSyntheticStyle { + fn default() -> Self { + Self { + bold: true, + italic: true, + bold_italic: true, + } + } +} + +/// See `font-shaping-break` in the config. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FontShapingBreak { + pub cursor: bool, +} + +impl Default for FontShapingBreak { + fn default() -> Self { + Self { cursor: true } + } +} diff --git a/crates/ghostty-font/src/deferred_face.rs b/crates/ghostty-font/src/deferred_face.rs new file mode 100644 index 0000000..d00b5fb --- /dev/null +++ b/crates/ghostty-font/src/deferred_face.rs @@ -0,0 +1,170 @@ +//! A deferred face represents a single font face with all the information +//! necessary to load it, but defers loading the full face until it is +//! needed. +//! +//! This allows us to have many fallback fonts to look for glyphs, but +//! only load them if they're really needed. +//! +//! Port of `font/DeferredFace.zig` (fontconfig variant only). + +use crate::face::{self, Options, Variation}; +use crate::fontconfig as fc; +use crate::{Face, Library, Presentation}; + +#[derive(Debug, thiserror::Error)] +pub enum LoadError { + #[error(transparent)] + Fontconfig(#[from] fc::Error), + #[error(transparent)] + Face(#[from] face::freetype::FaceError), +} + +/// Fontconfig specific data. This is only present if building with fontconfig. +pub struct Fontconfig { + /// The pattern for this font. This must be the "render prepared" pattern. + /// (i.e. call FcFontRenderPrepare). The charset and langset used for + /// quick codepoint / presentation lookups are read from this pattern. + pub pattern: fc::Pattern, + + /// Variations to apply to this font. + pub variations: Vec, +} + +#[derive(Default)] +pub struct DeferredFace { + /// Fontconfig + pub fc: Option, +} + +impl DeferredFace { + pub fn from_fontconfig(fc: Fontconfig) -> DeferredFace { + DeferredFace { fc: Some(fc) } + } + + /// Returns the family name of the font. + pub fn family_name(&self) -> Result { + if let Some(fc) = &self.fc { + return Ok(fc.pattern.get_string(fc::Property::Family, 0)?); + } + Ok(String::new()) + } + + /// Returns the name of this face. + pub fn name(&self) -> Result { + if let Some(fc) = &self.fc { + return Ok(fc.pattern.get_string(fc::Property::FullName, 0)?); + } + Ok(String::new()) + } + + /// Load the deferred font face. + pub fn load(&mut self, lib: &Library, opts: Options) -> Result { + let fc = self + .fc + .as_ref() + .expect("deferred face has no fontconfig data"); + + // Filename and index for our face so we can load it + let filename = fc.pattern.get_string(fc::Property::File, 0)?; + let face_index = fc.pattern.get_integer(fc::Property::Index, 0)?; + + let mut face = Face::init_file(lib, &filename, face_index, opts)?; + face.set_variations(&fc.variations, opts)?; + Ok(face) + } + + /// Returns true if this face can satisfy the given codepoint and + /// presentation. If presentation is null, then it just checks if the + /// codepoint is present at all. + /// + /// This does not require the face to be loaded (fontconfig metadata is + /// used). + pub fn has_codepoint(&self, cp: u32, p: Option) -> bool { + // If we are using fontconfig, use the fontconfig metadata to + // avoid loading the face. + let Some(fc) = &self.fc else { + // This is unreachable because discovery mechanisms terminate, and + // if we're not using a discovery mechanism, the face MUST be loaded. + unreachable!("deferred face without fontconfig data"); + }; + + // Check if char exists + if !fc.pattern.charset_has_char(cp).unwrap_or(false) { + return false; + } + + // If we have a presentation, check it matches + if let Some(desired) = p { + let emoji_lang = "und-zsye"; + let actual = if fc.pattern.langset_has_lang(emoji_lang).unwrap_or(false) { + Presentation::Emoji + } else { + Presentation::Text + }; + + return desired == actual; + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::discovery::{Descriptor, Fontconfig}; + + // NOTE: this test needs the real FreeType `Face` (agent A); it cannot + // run while `Face` is a stub. + #[test] + fn fontconfig() { + // Load freetype + let lib = Library::init().unwrap(); + + // Get a deferred face from fontconfig + let mut def = { + let fc = Fontconfig::init(); + let mut it = fc + .discover(&Descriptor { + family: Some("monospace".into()), + size: 12.0, + ..Default::default() + }) + .unwrap(); + it.next().unwrap().unwrap() + }; + + // Verify we can get the name + let n = def.name().unwrap(); + assert_ne!("", n); + + // Load it and verify it works + let face = def + .load(&lib, Options::new(face::DesiredSize::new(12.0))) + .unwrap(); + assert!(face.glyph_index(' ' as u32).is_some()); + } + + #[test] + fn family_name_and_presentation_from_fontconfig() { + let fc = Fontconfig::init(); + let mut it = fc + .discover(&Descriptor { + family: Some("monospace".into()), + size: 12.0, + ..Default::default() + }) + .unwrap(); + let def = it.next().unwrap().unwrap(); + assert_ne!("", def.family_name().unwrap()); + // A monospace text font is not an emoji-presentation font. + assert!(!def.has_codepoint('A' as u32, Some(Presentation::Emoji))); + } + + #[test] + fn empty_deferred_face_has_empty_names() { + let def = DeferredFace::default(); + assert_eq!("", def.family_name().unwrap()); + assert_eq!("", def.name().unwrap()); + } +} diff --git a/crates/ghostty-font/src/discovery.rs b/crates/ghostty-font/src/discovery.rs new file mode 100644 index 0000000..7ed1821 --- /dev/null +++ b/crates/ghostty-font/src/discovery.rs @@ -0,0 +1,278 @@ +//! Font discovery. Port of `font/discovery.zig` (fontconfig backend only; +//! the CoreText discoverer is macOS-only and out of scope). + +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use crate::collection::Collection; +use crate::deferred_face::{DeferredFace, Fontconfig as DeferredFontconfig}; +use crate::face::Variation; +use crate::fontconfig as fc; + +/// Discover implementation for the compile options. +pub type Discover = Fontconfig; + +/// Descriptor is used to search for fonts. The only required field +/// is "family". The rest are ignored unless they're set to a non-zero +/// value. +#[derive(Debug, Clone, Default)] +pub struct Descriptor { + /// Font family to search for. This can be a fully qualified font + /// name such as "Fira Code", "monospace", "serif", etc. + /// + /// On systems that use fontconfig (Linux), this can be a full + /// fontconfig pattern, such as "Fira Code-14:bold". + pub family: Option, + + /// Specific font style to search for. This will filter the style + /// string the font advertises. The "bold/italic" booleans later in this + /// struct filter by the style trait the font has, not the string, so + /// these can be used in conjunction or not. + pub style: Option, + + /// A codepoint that this font must be able to render. + pub codepoint: u32, + + /// Font size in points that the font should support. For conversion + /// to pixels, we will use 72 DPI for Mac and 96 DPI for everything else. + /// (If pixel conversion is necessary, i.e. emoji fonts) + pub size: f32, + + /// True if we want to search specifically for a font that supports + /// specific styles. + pub bold: bool, + pub italic: bool, + pub monospace: bool, + + /// Variation axes to apply to the font. This also impacts searching + /// for fonts since fonts with the ability to set these variations + /// will be preferred, but not guaranteed. + pub variations: Vec, +} + +impl Hash for Descriptor { + /// Hash the descriptor with the given hasher. Covers the same fields + /// as the Zig implementation. + fn hash(&self, state: &mut H) { + self.family.hash(state); + self.style.hash(state); + self.codepoint.hash(state); + self.size.to_bits().hash(state); + self.bold.hash(state); + self.italic.hash(state); + self.monospace.hash(state); + self.variations.len().hash(state); + for variation in &self.variations { + variation.id.hash(state); + + // This is not correct, but we don't currently depend on the + // hash value being different based on decimal values of variations. + (variation.value as i64).hash(state); + } + } +} + +impl Descriptor { + /// Returns a hash code that can be used to uniquely identify this + /// action. + pub fn hashcode(&self) -> u64 { + // `DefaultHasher::new()` uses fixed keys, so this is deterministic. + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + self.hash(&mut hasher); + hasher.finish() + } + + /// Convert to Fontconfig pattern to use for lookup. The pattern does + /// not have defaults filled/substituted (Fontconfig thing) so callers + /// must still do this. + pub fn to_fc_pattern(&self) -> fc::Pattern { + let mut pat = fc::Pattern::create(); + if let Some(family) = &self.family { + assert!(pat.add_string(fc::Property::Family, family)); + } + if let Some(style) = &self.style { + assert!(pat.add_string(fc::Property::Style, style)); + } + if self.codepoint > 0 { + let mut cs = fc::CharSet::create(); + assert!(cs.add_char(self.codepoint)); + assert!(pat.add_charset(fc::Property::Charset, &cs)); + } + if self.size > 0.0 { + assert!(pat.add_integer(fc::Property::Size, self.size.round() as i32)); + } + if self.bold { + assert!(pat.add_integer(fc::Property::Weight, fc::Weight::Bold as i32)); + } + if self.italic { + assert!(pat.add_integer(fc::Property::Slant, fc::Slant::Italic as i32)); + } + + // For fontconfig, we always add monospace in the pattern. Since + // fontconfig sorts by closeness to the pattern, this doesn't fully + // exclude non-monospace but helps prefer it. + assert!(pat.add_integer(fc::Property::Spacing, fc::Spacing::Mono as i32)); + + pat + } +} + +pub struct Fontconfig { + fc_config: Arc, +} + +#[derive(Debug, thiserror::Error)] +pub enum DiscoverError { + #[error("fontconfig failed")] + FontConfigFailed, + #[error(transparent)] + Fontconfig(#[from] fc::Error), +} + +impl Fontconfig { + pub fn init() -> Fontconfig { + // safe to call multiple times and concurrently + fc::init(); + Fontconfig { + fc_config: Arc::new( + fc::Config::init_load_config_and_fonts() + .expect("FcInitLoadConfigAndFonts returned null"), + ), + } + } + + /// Discover fonts from a descriptor. This returns an iterator that can + /// be used to build up the deferred fonts. + pub fn discover(&self, desc: &Descriptor) -> Result { + // Build our pattern that we'll search for + let mut pat = desc.to_fc_pattern(); + assert!(self.fc_config.substitute_with_pat_pattern(&mut pat)); + pat.default_substitute(); + + // Search + let set = self + .fc_config + .font_sort(&mut pat, false) + .map_err(|_| DiscoverError::FontConfigFailed)?; + + Ok(DiscoverIterator { + config: Arc::clone(&self.fc_config), + pattern: pat, + set, + variations: desc.variations.clone(), + i: 0, + }) + } + + pub fn discover_fallback( + &self, + _collection: &Collection, + desc: &Descriptor, + ) -> Result { + self.discover(desc) + } +} + +pub struct DiscoverIterator { + config: Arc, + pattern: fc::Pattern, + set: fc::FontSet, + variations: Vec, + i: usize, +} + +impl DiscoverIterator { + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Result, fc::Error> { + if self.i >= self.set.len() { + return Ok(None); + } + + // Get the copied pattern from our fontset that has the + // attributes configured for rendering. + let font_pattern = self + .config + .font_render_prepare(&mut self.pattern, &self.set, self.i)?; + + // The charset and langset must exist (they are used for lookups). + font_pattern.charset_has_char(0)?; + font_pattern.langset_has_lang("")?; + + // Increment after we return + self.i += 1; + + Ok(Some(DeferredFace::from_fontconfig(DeferredFontconfig { + pattern: font_pattern, + variations: self.variations.clone(), + }))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn descriptor_hash() { + let d = Descriptor::default(); + assert_ne!(d.hashcode(), 0); + } + + #[test] + fn descriptor_hash_family_names() { + let d1 = Descriptor { + family: Some("A".into()), + ..Default::default() + }; + let d2 = Descriptor { + family: Some("B".into()), + ..Default::default() + }; + assert_ne!(d1.hashcode(), d2.hashcode()); + } + + #[test] + fn descriptor_hash_is_deterministic_and_covers_fields() { + let a = Descriptor { + family: Some("A".into()), + bold: true, + ..Default::default() + }; + assert_eq!(a.hashcode(), a.clone().hashcode()); + let mut b = a.clone(); + b.italic = true; + assert_ne!(a.hashcode(), b.hashcode()); + } + + #[test] + fn fontconfig() { + let fc = Fontconfig::init(); + let _it = fc + .discover(&Descriptor { + family: Some("monospace".into()), + size: 12.0, + ..Default::default() + }) + .unwrap(); + } + + #[test] + fn fontconfig_codepoint() { + let fc = Fontconfig::init(); + let mut it = fc + .discover(&Descriptor { + codepoint: 'A' as u32, + size: 12.0, + ..Default::default() + }) + .unwrap(); + + // The first result should have the codepoint. Later ones may not + // because fontconfig returns all fonts sorted. + let face = it.next().unwrap().unwrap(); + assert!(face.has_codepoint('A' as u32, None)); + + // Should have other codepoints too + assert!(face.has_codepoint('B' as u32, None)); + } +} diff --git a/crates/ghostty-font/src/embedded.rs b/crates/ghostty-font/src/embedded.rs new file mode 100644 index 0000000..070db07 --- /dev/null +++ b/crates/ghostty-font/src/embedded.rs @@ -0,0 +1,55 @@ +//! Fonts that can be embedded with Ghostty. Note they are only actually +//! embedded in the binary if they are referenced by the code, so fonts +//! used for tests will not result in the final binary being larger. +//! +//! Be careful to ensure that any fonts you embed are licensed for +//! redistribution and include their license as necessary (see `res/`). + +/// Default fonts that we prefer for Ghostty. +pub static VARIABLE: &[u8] = include_bytes!("../res/JetBrainsMono-Variable.ttf"); +pub static VARIABLE_ITALIC: &[u8] = include_bytes!("../res/JetBrainsMono-Italic-Variable.ttf"); + +/// Symbols-only nerd font. +pub static SYMBOLS_NERD_FONT: &[u8] = include_bytes!("../res/SymbolsNerdFont-Regular.ttf"); + +/// Static jetbrains mono faces, currently unused (except by tests). +pub static REGULAR: &[u8] = include_bytes!("../res/JetBrainsMono-Regular.ttf"); +pub static BOLD: &[u8] = include_bytes!("../res/JetBrainsMono-Bold.ttf"); +pub static ITALIC: &[u8] = include_bytes!("../res/JetBrainsMono-Italic.ttf"); +pub static BOLD_ITALIC: &[u8] = include_bytes!("../res/JetBrainsMono-BoldItalic.ttf"); + +/// Emoji fonts +pub static EMOJI: &[u8] = include_bytes!("../res/NotoColorEmoji.ttf"); +pub static EMOJI_TEXT: &[u8] = include_bytes!("../res/NotoEmoji-Regular.ttf"); + +// Fonts below are ONLY used for testing. + +/// Fonts with general properties +pub static ARABIC: &[u8] = include_bytes!("../res/KawkabMono-Regular.ttf"); + +/// A font for testing which is patched with nerd font symbols. +pub static TEST_NERD_FONT: &[u8] = include_bytes!("../res/JetBrainsMonoNerdFont-Regular.ttf"); + +/// Specific font families below: +pub static CODE_NEW_ROMAN: &[u8] = include_bytes!("../res/CodeNewRoman-Regular.otf"); +pub static INCONSOLATA: &[u8] = include_bytes!("../res/Inconsolata-Regular.ttf"); +pub static GEIST_MONO: &[u8] = include_bytes!("../res/GeistMono-Regular.ttf"); +pub static JETBRAINS_MONO: &[u8] = include_bytes!("../res/JetBrainsMonoNoNF-Regular.ttf"); +pub static JULIA_MONO: &[u8] = include_bytes!("../res/JuliaMono-Regular.ttf"); + +/// Cozette is a unique font because it embeds some emoji characters +/// but has a text presentation. +pub static COZETTE: &[u8] = include_bytes!("../res/CozetteVector.ttf"); + +/// Monaspace has weird ligature behaviors we want to test in our shapers +/// so we embed it here. +pub static MONASPACE_NEON: &[u8] = include_bytes!("../res/MonaspaceNeon-Regular.otf"); + +/// Terminus TTF is a scalable font with bitmap glyphs at various sizes. +pub static TERMINUS_TTF: &[u8] = include_bytes!("../res/TerminusTTF-Regular.ttf"); + +/// Spleen is a monospaced bitmap font available in multiple formats. +/// Used for testing bitmap font support across different file formats. +pub static SPLEEN_BDF: &[u8] = include_bytes!("../res/spleen-8x16.bdf"); +pub static SPLEEN_PCF: &[u8] = include_bytes!("../res/spleen-8x16.pcf"); +pub static SPLEEN_OTB: &[u8] = include_bytes!("../res/spleen-8x16.otb"); diff --git a/crates/ghostty-font/src/face.rs b/crates/ghostty-font/src/face.rs new file mode 100644 index 0000000..d706a11 --- /dev/null +++ b/crates/ghostty-font/src/face.rs @@ -0,0 +1,872 @@ +//! Face types shared by all face implementations. Port of `font/face.zig`. +//! +//! The concrete FreeType face lives in [`freetype`] (`font/face/freetype.zig`). + +// `(a + b) / 2` is kept verbatim from upstream (f64::midpoint rounds differently). +#![allow(clippy::manual_midpoint)] + +use std::sync::Arc; + +use crate::metrics::Metrics; + +pub mod freetype; + +pub use crate::config_types::FreetypeLoadFlags; +pub use freetype::Face; + +/// If a DPI can't be calculated, this DPI is used. This is probably +/// wrong on modern devices so it is highly recommended you get the DPI +/// using whatever platform method you can. +pub const DEFAULT_DPI: u16 = 96; + +pub const FREETYPE_LOAD_FLAGS_DEFAULT: FreetypeLoadFlags = FreetypeLoadFlags { + hinting: true, + force_autohint: false, + monochrome: false, + autohint: true, + light: true, +}; + +/// Font file bytes. FreeType does not copy memory faces, so whoever creates +/// a face must keep the data alive; this type makes that explicit +/// (cheap to clone, either static or reference counted). +#[derive(Clone)] +pub enum FontData { + Static(&'static [u8]), + Shared(Arc<[u8]>), +} + +impl FontData { + pub fn as_slice(&self) -> &[u8] { + match self { + FontData::Static(s) => s, + FontData::Shared(s) => s, + } + } +} + +impl AsRef<[u8]> for FontData { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl std::fmt::Debug for FontData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FontData({} bytes)", self.as_slice().len()) + } +} + +impl From<&'static [u8]> for FontData { + fn from(v: &'static [u8]) -> Self { + FontData::Static(v) + } +} + +impl From> for FontData { + fn from(v: Vec) -> Self { + FontData::Shared(v.into()) + } +} + +impl From> for FontData { + fn from(v: Arc<[u8]>) -> Self { + FontData::Shared(v) + } +} + +/// Options for initializing a font face. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Options { + pub size: DesiredSize, + pub freetype_load_flags: FreetypeLoadFlags, +} + +impl Options { + pub fn new(size: DesiredSize) -> Self { + Self { + size, + freetype_load_flags: FREETYPE_LOAD_FLAGS_DEFAULT, + } + } +} + +/// The desired size for loading a font. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct DesiredSize { + /// Desired size in points + pub points: f32, + + /// The DPI of the screen so we can convert points to pixels. + pub xdpi: u16, + pub ydpi: u16, +} + +impl DesiredSize { + pub fn new(points: f32) -> Self { + Self { + points, + xdpi: DEFAULT_DPI, + ydpi: DEFAULT_DPI, + } + } + + /// Converts points to pixels + pub fn pixels(self) -> f32 { + // 1 point = 1/72 inch + (self.points * f32::from(self.ydpi)) / 72.0 + } +} + +/// A font variation setting. The best documentation for this I know of +/// is actually the CSS font-variation-settings property on MDN: +/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Variation { + pub id: VariationId, + pub value: f64, +} + +/// A four byte OpenType variation axis tag (`wght`, `slnt`, ...). The +/// numeric value is the big-endian interpretation of the 4 characters +/// (Zig: `packed struct(u32) { d, c, b, a }`, i.e. `a` is the high byte). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VariationId { + pub a: u8, + pub b: u8, + pub c: u8, + pub d: u8, +} + +impl VariationId { + pub fn new(v: &[u8; 4]) -> VariationId { + VariationId { + a: v[0], + b: v[1], + c: v[2], + d: v[3], + } + } + + /// The raw 32-bit tag value. + pub fn as_u32(self) -> u32 { + u32::from_be_bytes([self.a, self.b, self.c, self.d]) + } + + pub fn from_u32(v: u32) -> VariationId { + let [a, b, c, d] = v.to_be_bytes(); + VariationId { a, b, c, d } + } + + /// Converts the ID to a string. + pub fn str(self) -> [u8; 4] { + [self.a, self.b, self.c, self.d] + } +} + +/// The size and position of a glyph. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct GlyphSize { + pub width: f64, + pub height: f64, + pub x: f64, + pub y: f64, +} + +/// Additional options for rendering glyphs. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RenderOptions { + /// The metrics that are defining the grid layout. These are usually + /// the metrics of the primary font face. The grid metrics are used + /// by the font face to better layout the glyph in situations where + /// the font is not exactly the same size as the grid. + pub grid_metrics: Metrics, + + /// The number of grid cells this glyph will take up. This can be used + /// optionally by the rasterizer to better layout the glyph. + /// (`?u2` in Zig, so 0..=3.) + pub cell_width: Option, + + /// Constraint and alignment properties for the glyph. The rasterizer + /// should call the `constrain` function on this with the original size + /// and bearings of the glyph to get remapped values that the glyph + /// should be scaled/moved to. + pub constraint: Constraint, + + /// The number of cells, horizontally that the glyph is free to take up + /// when resized and aligned by `constraint`. This is usually 1, but if + /// there's whitespace to the right of the cell then it can be 2. + /// (`u2` in Zig.) + pub constraint_width: u8, + + /// Thicken the glyph. This draws the glyph with a thicker stroke width. + /// This is purely an aesthetic setting. + /// + /// This only works with CoreText currently. + pub thicken: bool, + + /// "Strength" of the thickening, between `0` and `255`. + /// Only has an effect when `thicken` is enabled. + /// + /// `0` does not correspond to *no* thickening, + /// just the *lightest* thickening available. + /// + /// CoreText only. + pub thicken_strength: u8, +} + +impl RenderOptions { + /// Options with the given grid metrics and all other fields defaulted + /// (`cell_width = null`, `constraint = .none`, `constraint_width = 1`, + /// `thicken = false`, `thicken_strength = 255`). + pub fn new(grid_metrics: Metrics) -> Self { + Self { + grid_metrics, + cell_width: None, + constraint: Constraint::NONE, + constraint_width: 1, + thicken: false, + thicken_strength: 255, + } + } +} + +/// See the `constraint` field of [`RenderOptions`]. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Constraint { + /// Sizing rule. + pub size: ConstraintSize, + + /// Vertical alignment rule. + pub align_vertical: Align, + /// Horizontal alignment rule. + pub align_horizontal: Align, + + /// Top padding when resizing. + pub pad_top: f64, + /// Left padding when resizing. + pub pad_left: f64, + /// Right padding when resizing. + pub pad_right: f64, + /// Bottom padding when resizing. + pub pad_bottom: f64, + + // Size and bearings of the glyph relative + // to the bounding box of its scale group. + pub relative_width: f64, + pub relative_height: f64, + pub relative_x: f64, + pub relative_y: f64, + + /// Maximum aspect ratio (width/height) to allow when stretching. + pub max_xy_ratio: Option, + + /// Maximum number of cells horizontally to use. (`u2` in Zig.) + pub max_constraint_width: u8, + + /// What to use as the height metric when constraining the glyph and + /// the constraint width is 1, + pub height: Height, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ConstraintSize { + /// Don't change the size of this glyph. + None, + /// Scale the glyph down if needed to fit within the bounds, + /// preserving aspect ratio. + Fit, + /// Scale the glyph up or down to exactly match the bounds, + /// preserving aspect ratio. + Cover, + /// Scale the glyph down if needed to fit within the bounds, + /// preserving aspect ratio. If the glyph doesn't cover a + /// single cell, scale up. If the glyph exceeds a single + /// cell but is within the bounds, do nothing. + /// (Nerd Font specific rule.) + FitCover1, + /// Stretch the glyph to exactly fit the bounds in both + /// directions, disregarding aspect ratio. + Stretch, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Align { + /// Don't move the glyph on this axis. + None, + /// Move the glyph so that its leading (bottom/left) + /// edge aligns with the leading edge of the axis. + Start, + /// Move the glyph so that its trailing (top/right) + /// edge aligns with the trailing edge of the axis. + End, + /// Move the glyph so that it is centered on this axis. + Center, + /// Move the glyph so that it is centered on this axis, + /// but always with respect to the first cell even for + /// multi-cell constraints. (Nerd Font specific rule.) + Center1, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Height { + /// Use the full line height of the primary face for + /// constraining this glyph. + Cell, + /// Use the icon height from the grid metrics for + /// constraining this glyph. Unlike `cell`, the value of + /// this height depends on both the constraint width and the + /// affected by the `adjust-icon-height` config option. + Icon, +} + +impl Default for Constraint { + fn default() -> Self { + Self::NONE + } +} + +impl Constraint { + /// Don't constrain the glyph in any way. + pub const NONE: Constraint = Constraint { + size: ConstraintSize::None, + align_vertical: Align::None, + align_horizontal: Align::None, + pad_top: 0.0, + pad_left: 0.0, + pad_right: 0.0, + pad_bottom: 0.0, + relative_width: 1.0, + relative_height: 1.0, + relative_x: 0.0, + relative_y: 0.0, + max_xy_ratio: None, + max_constraint_width: 2, + height: Height::Cell, + }; + + /// Returns true if the constraint does anything. If it doesn't, + /// because it neither sizes nor positions the glyph, then this + /// returns false. + #[inline] + pub fn does_anything(&self) -> bool { + self.size != ConstraintSize::None + || self.align_horizontal != Align::None + || self.align_vertical != Align::None + } + + /// Apply this constraint to the provided glyph + /// size, given the available width and height. + /// + /// `constraint_width` is the number of cells horizontally available for + /// this glyph. + pub fn constrain( + &self, + glyph: GlyphSize, + metrics: &Metrics, + constraint_width: u8, + ) -> GlyphSize { + if !self.does_anything() { + return glyph; + } + + if self.size == ConstraintSize::Stretch { + // Stretched glyphs are usually meant to align across cell + // boundaries, which works best if they're scaled and + // aligned to the grid rather than the face. This is most + // easily done by inserting this little fib in the metrics. + let mut m = *metrics; + m.face_width = f64::from(m.cell_width); + m.face_height = f64::from(m.cell_height); + m.face_y = 0.0; + + // Negative padding for stretched glyphs is a band-aid to + // avoid gaps due to pixel rounding, but at the cost of + // unsightly overlap artifacts. Since we scale and align to + // the grid rather than the face, we don't need it. + let mut c = *self; + c.pad_bottom = c.pad_bottom.max(0.0); + c.pad_top = c.pad_top.max(0.0); + c.pad_left = c.pad_left.max(0.0); + c.pad_right = c.pad_right.max(0.0); + + return c.constrain_inner(glyph, &m, constraint_width); + } + self.constrain_inner(glyph, metrics, constraint_width) + } + + fn constrain_inner( + &self, + glyph: GlyphSize, + metrics: &Metrics, + constraint_width: u8, + ) -> GlyphSize { + // For extra wide font faces, never stretch glyphs across two cells. + // This mirrors font_patcher. + let min_constraint_width: u8 = if self.size == ConstraintSize::Stretch + && metrics.face_width > 0.9 * metrics.face_height + { + 1 + } else { + self.max_constraint_width.min(constraint_width) + }; + + // The bounding box for the glyph's scale group. + // Scaling and alignment rules are calculated for + // this box and then applied to the glyph. + let mut group = { + let group_width = glyph.width / self.relative_width; + let group_height = glyph.height / self.relative_height; + GlyphSize { + width: group_width, + height: group_height, + x: glyph.x - (group_width * self.relative_x), + y: glyph.y - (group_height * self.relative_y), + } + }; + + // Apply prescribed scaling, preserving the + // center bearings of the group bounding box + let (width_factor, height_factor) = + self.scale_factors(group, metrics, min_constraint_width); + let center_x = group.x + (group.width / 2.0); + let center_y = group.y + (group.height / 2.0); + group.width *= width_factor; + group.height *= height_factor; + group.x = center_x - (group.width / 2.0); + group.y = center_y - (group.height / 2.0); + + // NOTE: font_patcher jumps through a lot of hoops at this + // point to ensure that the glyph remains within the target + // bounding box after rounding to font definition units. + // This is irrelevant here as we're not rounding, we're + // staying in f64 and heading straight to rendering. + + // Apply prescribed alignment + group.y = self.aligned_y(group, metrics); + group.x = self.aligned_x(group, metrics, min_constraint_width); + + // Transfer the scaling and alignment back to the glyph and return. + GlyphSize { + width: width_factor * glyph.width, + height: height_factor * glyph.height, + x: group.x + (group.width * self.relative_x), + y: group.y + (group.height * self.relative_y), + } + } + + /// Return width and height scaling factors for this scaling group. + fn scale_factors( + &self, + group: GlyphSize, + metrics: &Metrics, + min_constraint_width: u8, + ) -> (f64, f64) { + if self.size == ConstraintSize::None { + return (1.0, 1.0); + } + + let multi_cell = min_constraint_width > 1; + + let pad_width_factor = f64::from(min_constraint_width) - (self.pad_left + self.pad_right); + let pad_height_factor = 1.0 - (self.pad_bottom + self.pad_top); + + let target_width = pad_width_factor * metrics.face_width; + let target_height = pad_height_factor + * match self.height { + Height::Cell => metrics.face_height, + // Like font-patcher, the icon constraint height depends on the + // constraint width. Unlike font-patcher, the multi-cell + // icon_height may be different from face_height due to the + // `adjust-icon-height` config option. + Height::Icon => { + if multi_cell { + metrics.icon_height + } else { + metrics.icon_height_single + } + } + }; + + let mut width_factor = target_width / group.width; + let mut height_factor = target_height / group.height; + + match self.size { + ConstraintSize::None => unreachable!(), + ConstraintSize::Fit => { + // Scale down to fit if needed + height_factor = 1.0_f64.min(width_factor).min(height_factor); + width_factor = height_factor; + } + ConstraintSize::Cover => { + // Scale to cover + height_factor = width_factor.min(height_factor); + width_factor = height_factor; + } + ConstraintSize::FitCover1 => { + // Scale down to fit or up to cover at least one cell + // NOTE: This is similar to font_patcher's "pa" mode, + // however, font_patcher will only do the upscaling + // part if the constraint width is 1, resulting in + // some icons becoming smaller when the constraint + // width increases. You'd see icons shrinking when + // opening up a space after them. This makes no + // sense, so we've fixed the rule such that these + // icons are scaled to the same size for multi-cell + // constraints as they would be for single-cell. + height_factor = width_factor.min(height_factor); + if multi_cell && height_factor > 1.0 { + // Call back into this function with + // constraint width 1 to get single-cell scale + // factors. We use the height factor as width + // could have been modified by max_xy_ratio. + let (_, single_height_factor) = self.scale_factors(group, metrics, 1); + height_factor = 1.0_f64.max(single_height_factor); + } + width_factor = height_factor; + } + ConstraintSize::Stretch => {} + } + + // Reduce aspect ratio if required + if let Some(ratio) = self.max_xy_ratio + && group.width * width_factor > group.height * height_factor * ratio + { + width_factor = group.height * height_factor * ratio / group.width; + } + + (width_factor, height_factor) + } + + /// Return vertical bearing for aligning this group + fn aligned_y(&self, group: GlyphSize, metrics: &Metrics) -> f64 { + if self.size == ConstraintSize::None && self.align_vertical == Align::None { + // If we don't have any constraints affecting the vertical axis, + // we don't touch vertical alignment. + return group.y; + } + // We use face_height and offset by face_y, rather than + // using cell_height directly, to account for the asymmetry + // of the pixel cell around the face (a consequence of + // aligning the baseline with a pixel boundary rather than + // vertically centering the face). + let pad_bottom_dy = self.pad_bottom * metrics.face_height; + let pad_top_dy = self.pad_top * metrics.face_height; + let start_y = metrics.face_y + pad_bottom_dy; + let end_y = metrics.face_y + (metrics.face_height - group.height - pad_top_dy); + let center_y = (start_y + end_y) / 2.0; + match self.align_vertical { + // NOTE: Even if there is no prescribed alignment, we ensure + // that the group doesn't protrude outside the padded cell, + // since this is implied by every available size constraint. If + // the group is too high we fall back to centering, though if we + // hit the .none prong we always have self.size != .none, so + // this should never happen. + Align::None => { + if end_y < start_y { + center_y + } else { + start_y.max(group.y.min(end_y)) + } + } + Align::Start => start_y, + Align::End => end_y, + Align::Center | Align::Center1 => center_y, + } + } + + /// Return horizontal bearing for aligning this group + fn aligned_x(&self, group: GlyphSize, metrics: &Metrics, min_constraint_width: u8) -> f64 { + if self.size == ConstraintSize::None && self.align_horizontal == Align::None { + // If we don't have any constraints affecting the horizontal + // axis, we don't touch horizontal alignment. + return group.x; + } + // For multi-cell constraints, we align relative to the span + // from the left edge of the first cell to the right edge of + // the last face cell assuming it's left-aligned within the + // rounded and adjusted pixel cell. Any horizontal offset to + // center the face within the grid cell is the responsibility + // of the backend-specific rendering code, and should be done + // after applying constraints. + let full_face_span = metrics.face_width + + f64::from((u32::from(min_constraint_width) - 1) * metrics.cell_width); + let pad_left_dx = self.pad_left * metrics.face_width; + let pad_right_dx = self.pad_right * metrics.face_width; + let start_x = pad_left_dx; + let end_x = full_face_span - group.width - pad_right_dx; + match self.align_horizontal { + // NOTE: Even if there is no prescribed alignment, we ensure + // that the glyph doesn't protrude outside the padded cell, + // since this is implied by every available size constraint. The + // left-side bound has priority if the group is too wide, though + // if we hit the .none prong we always have self.size != .none, + // so this should never happen. + Align::None => start_x.max(group.x.min(end_x)), + Align::Start => start_x, + Align::End => start_x.max(end_x), + Align::Center => start_x.max((start_x + end_x) / 2.0), + // NOTE: .center1 implements the font_patcher rule of centering + // in the first cell even for multi-cell constraints. Since glyphs + // are not allowed to protrude to the left, this results in the + // left-alignment like .start when the glyph is wider than a cell. + Align::Center1 => { + let end1_x = metrics.face_width - group.width - pad_right_dx; + start_x.max((start_x + end1_x) / 2.0) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn variation_id_wght_should_be_2003265652() { + let id = VariationId::new(b"wght"); + assert_eq!(2003265652, id.as_u32()); + assert_eq!(b"wght", &id.str()); + } + + #[test] + fn variation_id_slnt_should_be_1936486004() { + let id = VariationId { + a: b's', + b: b'l', + c: b'n', + d: b't', + }; + assert_eq!(1936486004, id.as_u32()); + assert_eq!(b"slnt", &id.str()); + assert_eq!(VariationId::from_u32(1936486004), id); + } + + #[test] + fn desired_size_pixels() { + let s = DesiredSize { + points: 12.0, + xdpi: 96, + ydpi: 144, + }; + assert!((s.pixels() - 24.0).abs() < 1e-6); + assert_eq!(DesiredSize::new(10.0).ydpi, DEFAULT_DPI); + } + + /// `comparison.expectApproxEqual` for f64: relative tolerance of + /// `sqrt(eps)` (Zig `std.math.approxEqRel`). + fn approx_eq(expected: f64, actual: f64) -> bool { + if expected == actual { + return true; + } + if expected.is_nan() || actual.is_nan() { + return false; + } + (expected - actual).abs() <= expected.abs().max(actual.abs()) * f64::EPSILON.sqrt() + } + + fn expect_approx_equal(expected: GlyphSize, actual: GlyphSize) { + assert!( + approx_eq(expected.width, actual.width) + && approx_eq(expected.height, actual.height) + && approx_eq(expected.x, actual.x) + && approx_eq(expected.y, actual.y), + "expected approximately {expected:?}, found {actual:?}" + ); + } + + #[test] + fn constraints() { + use crate::nerd_font_attributes::get_constraint; + + // Hardcoded data matches metrics from CoreText at size 12 and DPI 96. + + // Define grid metrics (matches font-family = JetBrains Mono) + let metrics = Metrics { + cell_width: 10, + cell_height: 22, + cell_baseline: 5, + underline_position: 19, + underline_thickness: 1, + strikethrough_position: 12, + strikethrough_thickness: 1, + overline_position: 0, + overline_thickness: 1, + box_thickness: 1, + cursor_thickness: 1, + cursor_height: 22, + icon_height: 21.12, + icon_height_single: 44.48 / 3.0, + face_width: 9.6, + face_height: 21.12, + face_y: 0.2, + }; + + // ASCII (no constraint). + { + let constraint = Constraint::NONE; + + // BBox of 'x' from JetBrains Mono. + let glyph_x = GlyphSize { + width: 6.784, + height: 15.28, + x: 1.408, + y: 4.84, + }; + + // Any constraint width: do nothing. + for constraint_width in [1, 2] { + expect_approx_equal( + glyph_x, + constraint.constrain(glyph_x, &metrics, constraint_width), + ); + } + } + + // Symbol (same constraint as hardcoded in Renderer.addGlyph). + { + let constraint = Constraint { + size: ConstraintSize::Fit, + ..Constraint::NONE + }; + + // BBox of '■' (0x25A0 black square) from Iosevka. + // NOTE: This glyph is designed to span two cells. + let glyph_25a0 = GlyphSize { + width: 10.272, + height: 10.272, + x: 2.864, + y: 5.304, + }; + + // Constraint width 1: scale down and shift to fit a single cell. + expect_approx_equal( + GlyphSize { + width: metrics.face_width, + height: metrics.face_width, + x: 0.0, + y: 5.64, + }, + constraint.constrain(glyph_25a0, &metrics, 1), + ); + + // Constraint width 2: do nothing. + expect_approx_equal(glyph_25a0, constraint.constrain(glyph_25a0, &metrics, 2)); + } + + // Emoji (same constraint as hardcoded in SharedGrid.renderGlyph). + { + let constraint = Constraint { + size: ConstraintSize::Cover, + align_horizontal: Align::Center, + align_vertical: Align::Center, + pad_left: 0.025, + pad_right: 0.025, + ..Constraint::NONE + }; + + // BBox of '🥸' (0x1F978) from Apple Color Emoji. + let glyph_1f978 = GlyphSize { + width: 20.0, + height: 20.0, + x: 0.46, + y: 1.0, + }; + + // Constraint width 2: scale to cover two cells with padding, center; + expect_approx_equal( + GlyphSize { + width: 18.72, + height: 18.72, + x: 0.44, + y: 1.4, + }, + constraint.constrain(glyph_1f978, &metrics, 2), + ); + } + + // Nerd Font default. + { + let constraint = get_constraint(0xea61).unwrap(); + + // Verify that this is the constraint we expect. + assert_eq!(ConstraintSize::FitCover1, constraint.size); + assert_eq!(Height::Icon, constraint.height); + assert_eq!(Align::Center1, constraint.align_horizontal); + assert_eq!(Align::Center1, constraint.align_vertical); + + // BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only. + // NOTE: This icon is part of a group, so the + // constraint applies to a larger bounding box. + let glyph_ea61 = GlyphSize { + width: 9.015625, + height: 13.015625, + x: 3.015625, + y: 3.76525, + }; + + // Constraint width 1: scale and shift group to fit a single cell. + expect_approx_equal( + GlyphSize { + width: 7.2125, + height: 10.4125, + x: 0.8125, + y: 5.950695224719102, + }, + constraint.constrain(glyph_ea61, &metrics, 1), + ); + + // Constraint width 2: no scaling; left-align and vertically center group. + expect_approx_equal( + GlyphSize { + width: glyph_ea61.width, + height: glyph_ea61.height, + x: 1.015625, + y: 4.7483690308988775, + }, + constraint.constrain(glyph_ea61, &metrics, 2), + ); + } + + // Nerd Font stretch. + { + let constraint = get_constraint(0xe0c0).unwrap(); + + // Verify that this is the constraint we expect. + assert_eq!(ConstraintSize::Stretch, constraint.size); + assert_eq!(Height::Cell, constraint.height); + assert_eq!(Align::Start, constraint.align_horizontal); + assert_eq!(Align::Center1, constraint.align_vertical); + + // BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only. + let glyph_e0c0 = GlyphSize { + width: 16.796875, + height: 16.46875, + x: -0.796875, + y: 1.7109375, + }; + + // Constraint width 1: stretch and position to exactly cover one cell. + expect_approx_equal( + GlyphSize { + width: f64::from(metrics.cell_width), + height: f64::from(metrics.cell_height), + x: 0.0, + y: 0.0, + }, + constraint.constrain(glyph_e0c0, &metrics, 1), + ); + + // Constraint width 2: stretch and position to exactly cover two cells. + expect_approx_equal( + GlyphSize { + width: f64::from(2 * metrics.cell_width), + height: f64::from(metrics.cell_height), + x: 0.0, + y: 0.0, + }, + constraint.constrain(glyph_e0c0, &metrics, 2), + ); + } + } +} diff --git a/crates/ghostty-font/src/face/freetype.rs b/crates/ghostty-font/src/face/freetype.rs new file mode 100644 index 0000000..f9c284b --- /dev/null +++ b/crates/ghostty-font/src/face/freetype.rs @@ -0,0 +1,1448 @@ +//! Face represents a single font face. A single font face has a single set +//! of properties associated with it such as style, weight, etc. +//! +//! A Face isn't typically meant to be used directly. It is usually used +//! via a Family in order to store it in an Atlas. +//! +//! Port of `font/face/freetype.zig`. + +use std::ffi::CString; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; + +use super::{DesiredSize, FontData, FreetypeLoadFlags, Options, RenderOptions, Variation}; +use crate::atlas::{self, Atlas}; +use crate::glyph::Glyph; +use crate::library::Library; +use crate::metrics::FaceMetrics; + +pub(crate) mod ft; +mod resize; + +pub use ft::HbFont; + +#[derive(Debug, thiserror::Error)] +pub enum FaceError { + #[error("freetype error {0}")] + Freetype(i32), + #[error("harfbuzz font creation failed")] + HarfbuzzFailed, + #[error("invalid font path")] + InvalidPath, +} + +#[derive(Debug, thiserror::Error)] +pub enum RenderError { + #[error(transparent)] + Atlas(#[from] atlas::Error), + #[error("wrong atlas")] + WrongAtlas, + #[error("freetype error {0}")] + Freetype(i32), + #[error("bitmap handling error")] + BitmapHandlingError, + #[error("glyph resize failed")] + GlyphResizeFailed, + #[error("unsupported glyph format")] + UnsupportedGlyphFormat, + /// Zig panics ("unsupported pixel mode") in this situation. + #[error("unsupported pixel mode {0}")] + UnsupportedPixelMode(u32), +} + +/// Set to true to apply a synthetic italic to the face. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct Synthetic { + pub italic: bool, + pub bold: bool, +} + +/// Convert 26.6 fixed point to f64 (`F26Dot6.to(f64)`). +fn f26dot6_to_f64(v: i64) -> f64 { + // The Zig code bit-casts the (truncated) i32 value. + f64::from(v as i32) / 64.0 +} + +/// `F26Dot6.from(f64)` as the raw 26.6 integer. +fn f64_to_f26dot6(v: f64) -> i32 { + (v * 64.0).round() as i32 +} + +fn lock(m: &Mutex<()>) -> MutexGuard<'_, ()> { + m.lock().unwrap_or_else(PoisonError::into_inner) +} + +pub struct Face { + /// Freetype load flags for this font face. + pub load_flags: FreetypeLoadFlags, + + /// Set quirks.disableDefaultFontFeatures + pub quirks_disable_default_font_features: bool, + + /// Set to true to apply a synthetic italic to the face. + pub synthetic: Synthetic, + + /// The current size this font is set to. + pub size: DesiredSize, + + // NOTE: field order matters for drop order. The harfbuzz font holds its + // own FreeType reference and the font data must outlive every face + // reference. + /// Our font face. + #[allow(clippy::struct_field_names)] + face: ft::FtFace, + + /// Harfbuzz font corresponding to this face. + hb_font: HbFont, + + /// This mutex MUST be held while doing anything with the + /// glyph slot on the freetype face, because this struct + /// may be shared across multiple surfaces. + /// + /// This means that anywhere where `load_glyph` is called, this + /// mutex must be held. (Deviation: faces that share an `FT_Face` + /// through `synthetic_bold`/`synthetic_italic` also share this mutex.) + ft_mutex: Arc>, + + /// Our Library + lib: Library, + + /// Keeps in-memory font data alive (FreeType does not copy it). + _data: Option, +} + +impl std::fmt::Debug for Face { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Face") + .field("size", &self.size) + .field("synthetic", &self.synthetic) + .finish_non_exhaustive() + } +} + +// NOTE: `Face` is `Send + Sync` automatically: the unsafe impls live on the +// FFI handle wrappers in `ft.rs`. Mutable FreeType state (size, glyph slot) +// is only touched through `&mut self` or while holding `ft_mutex`. + +impl Face { + /// Initialize a new font face from a file. + pub fn init_file( + lib: &Library, + path: &str, + index: i32, + opts: Options, + ) -> Result { + let cpath = CString::new(path).map_err(|_| FaceError::InvalidPath)?; + let face = { + let _g = lib.lock(); + ft::FtFace::new_file(&lib.inner.lib, &cpath, index).map_err(FaceError::Freetype)? + }; + Face::init_face(lib, face, None, opts) + } + + /// Initialize a new font face with the given source in-memory. + pub fn init(lib: &Library, source: FontData, opts: Options) -> Result { + let face = { + let _g = lib.lock(); + ft::FtFace::new_memory(&lib.inner.lib, source.as_slice(), 0) + .map_err(FaceError::Freetype)? + }; + Face::init_face(lib, face, Some(source), opts) + } + + fn init_face( + lib: &Library, + face: ft::FtFace, + data: Option, + opts: Options, + ) -> Result { + Face::init_face_with_mutex(lib, face, data, opts, Arc::new(Mutex::new(()))) + } + + fn init_face_with_mutex( + lib: &Library, + face: ft::FtFace, + data: Option, + opts: Options, + ft_mutex: Arc>, + ) -> Result { + face.select_unicode_charmap().map_err(FaceError::Freetype)?; + set_size_(&face, opts.size)?; + + let hb_font = face.create_hb_font().ok_or(FaceError::HarfbuzzFailed)?; + + let mut result = Face { + load_flags: opts.freetype_load_flags, + quirks_disable_default_font_features: false, + synthetic: Synthetic::default(), + size: opts.size, + face, + hb_font, + ft_mutex, + lib: lib.clone(), + _data: data, + }; + result.quirks_disable_default_font_features = quirks_disable_default_font_features(&result); + + // We output information about available variation axes, if they exist. + if log::log_enabled!(log::Level::Debug) && result.face.has_multiple_masters() { + log::debug!("variation axes font={}", result.name()); + if let Ok(axes) = result.face.mm_axes(&result.lib.inner.lib) { + for axis in axes { + let id = super::VariationId::from_u32(axis.tag); + log::debug!( + "variation axis: id={} min={} max={} def={}", + String::from_utf8_lossy(&id.str()), + axis.minimum >> 16, + axis.maximum >> 16, + axis.def >> 16, + ); + } + } + } + + Ok(result) + } + + fn lock_ft(&self) -> MutexGuard<'_, ()> { + lock(&self.ft_mutex) + } + + /// Returns the font name (family entry of the SFNT name table), or an + /// empty string if there is none. + #[allow(clippy::chunks_exact_to_as_chunks)] + pub fn name(&self) -> String { + let count = self.face.sfnt_name_count(); + + // We look for the font family entry. + for i in 0..count { + let Some(entry) = self.face.sfnt_name(i) else { + continue; + }; + if entry.name_id == ft::TT_NAME_ID_FONT_FAMILY { + let string = &entry.string; + // There are other encodings that are something other than UTF-8 + // but this is one we've seen "in the wild" so far. + if entry.platform_id == ft::TT_PLATFORM_MICROSOFT + && entry.encoding_id == ft::TT_MS_ID_UNICODE_CS + && string.len() % 2 == 0 + && string.len() <= 1024 + { + // UTF-16 big endian. + let units: Vec = string + .chunks_exact(2) + .map(|c| u16::from_be_bytes([c[0], c[1]])) + .collect(); + if let Ok(s) = String::from_utf16(&units) { + return s; + } + } + return String::from_utf8_lossy(string).into_owned(); + } + } + + String::new() + } + + /// Return a new face that is the same as this but also has synthetic + /// bold applied. + pub fn synthetic_bold(&self, opts: Options) -> Result { + // Increase face ref count + let face = self.face.reference().map_err(FaceError::Freetype)?; + + let mut f = Face::init_face_with_mutex( + &self.lib, + face, + self._data.clone(), + opts, + Arc::clone(&self.ft_mutex), + )?; + f.synthetic = self.synthetic; + f.synthetic.bold = true; + + Ok(f) + } + + /// Return a new face that is the same as this but has a transformation + /// matrix applied to italicize it. + pub fn synthetic_italic(&self, opts: Options) -> Result { + // Increase face ref count + let face = self.face.reference().map_err(FaceError::Freetype)?; + + let mut f = Face::init_face_with_mutex( + &self.lib, + face, + self._data.clone(), + opts, + Arc::clone(&self.ft_mutex), + )?; + f.synthetic = self.synthetic; + f.synthetic.italic = true; + + Ok(f) + } + + /// Resize the font in-place. If this succeeds, the caller is responsible + /// for clearing any glyph caches, font atlas data, etc. + pub fn set_size(&mut self, opts: Options) -> Result<(), FaceError> { + let _g = lock(&self.ft_mutex); + set_size_(&self.face, opts.size)?; + self.size = opts.size; + Ok(()) + } + + /// Set the variation axes for this font. This will modify this font + /// in-place. + pub fn set_variations(&mut self, vs: &[Variation], _opts: Options) -> Result<(), FaceError> { + // If this font doesn't support variations, we can't do anything. + if !self.face.has_multiple_masters() || vs.is_empty() { + return Ok(()); + } + + // Freetype requires that we send ALL coordinates in at once so the + // first thing we have to do is get all the vars and put them into + // an array. + let axes = self + .face + .mm_axes(&self.lib.inner.lib) + .map_err(FaceError::Freetype)?; + + // To avoid allocations, we cap the number of variation axes we can + // support. This is arbitrary but Firefox caps this at 16 so I + // feel like that's probably safe... and we do double cause its + // cheap. + let n = axes.len().min(32); + let mut coords = self + .face + .var_design_coordinates(n) + .map_err(FaceError::Freetype)?; + + // Now we go through each axis and see if its set. This is slow + // but there usually aren't many axes and usually not many set + // variations, either. + for (i, axis) in axes.iter().take(n).enumerate() { + for v in vs { + if axis.tag == v.id.as_u32() { + coords[i] = (v.value * 65536.0) as i64; + break; + } + } + } + + // Set them! + let _g = lock(&self.ft_mutex); + self.face + .set_var_design_coordinates(&coords) + .map_err(FaceError::Freetype) + } + + /// Returns the glyph index for the given Unicode code point. If this + /// face doesn't support this glyph, null is returned. + pub fn glyph_index(&self, cp: u32) -> Option { + self.face.char_index(cp) + } + + /// Returns true if this font is colored. This can be used by callers to + /// determine what kind of atlas to pass in. + pub fn has_color(&self) -> bool { + self.face.has_color() + } + + /// Returns true if the given glyph ID is colorized. + pub fn is_color_glyph(&self, glyph_id: u32) -> bool { + let _g = self.lock_ft(); + + // Load the glyph and see what pixel mode it renders with. + // All modes other than BGRA are non-color. + // If the glyph fails to load, just return false. + // + // NO_SVG set to true because we don't currently support rendering + // SVG glyphs under FreeType, since that requires bundling another + // dependency to handle rendering the SVG. + let mut flags = ft::LOAD_RENDER | ft::FT_LOAD_NO_SVG; + if self.face.has_color() { + flags |= ft::LOAD_COLOR; + } + if self.face.load_glyph(glyph_id, flags).is_err() { + return false; + } + + self.face.slot_info().pixel_mode == ft::PIXEL_MODE_BGRA + } + + /// Set the load flags to use when loading a glyph for measurement or + /// rendering. + fn glyph_load_flags(&self, constrained: bool) -> i32 { + // Hinting should only be enabled if the configured load flags specify + // it and the provided constraint doesn't actually do anything, since + // if it does, then it'll mess up the hinting anyway when it moves or + // resizes the glyph. + let do_hinting = self.load_flags.hinting && !constrained; + + // If our glyph has color, we want to render the color + let mut flags = 0; + if self.face.has_color() { + flags |= ft::LOAD_COLOR; + } + // We don't render, because we'll invoke the render + // manually after applying constraints further down. + + // use options from config + if !do_hinting { + flags |= ft::LOAD_NO_HINTING; + } + if self.load_flags.force_autohint { + flags |= ft::LOAD_FORCE_AUTOHINT; + } + if !self.load_flags.autohint { + flags |= ft::LOAD_NO_AUTOHINT; + } + + // If we're gonna be rendering this glyph in monochrome, + // then we should use the monochrome hinter as well, or + // else it won't look very good at all. + // + // Otherwise if the user asked for light hinting we + // use that, otherwise we just use the normal target. + flags |= if self.load_flags.monochrome { + ft::TARGET_MONO + } else if self.load_flags.light { + ft::TARGET_LIGHT + } else { + ft::TARGET_NORMAL + }; + + // NO_SVG set to true because we don't currently support rendering + // SVG glyphs under FreeType, since that requires bundling another + // dependency to handle rendering the SVG. + flags | ft::FT_LOAD_NO_SVG + } + + /// Get a rect that represents the position and size of the loaded glyph. + fn get_glyph_size(&self) -> super::GlyphSize { + let slot = self.face.slot_info(); + + // If we're dealing with an outline glyph then we get the + // outline's bounding box instead of using the built-in + // metrics, since that's more precise and allows better + // cell-fitting. + if slot.format == ft::GLYPH_FORMAT_OUTLINE { + // Get the glyph's bounding box before we transform it at all. + // We use this rather than the metrics, since it's more precise. + let (x_min, y_min, x_max, y_max) = self.face.slot_outline_bbox(); + + return super::GlyphSize { + x: f26dot6_to_f64(x_min), + y: f26dot6_to_f64(y_min), + width: f26dot6_to_f64(x_max - x_min), + height: f26dot6_to_f64(y_max - y_min), + }; + } + + super::GlyphSize { + x: f26dot6_to_f64(slot.hori_bearing_x), + y: f26dot6_to_f64(slot.hori_bearing_y - slot.metrics_height), + width: f26dot6_to_f64(slot.metrics_width), + height: f26dot6_to_f64(slot.metrics_height), + } + } + + /// Render a glyph using the glyph index. The rendered glyph is stored in the + /// given texture atlas. + pub fn render_glyph( + &self, + atlas: &mut Atlas, + glyph_index: u32, + opts: RenderOptions, + ) -> Result { + let _g = self.lock_ft(); + + // Load the glyph. + self.face + .load_glyph( + glyph_index, + self.glyph_load_flags(opts.constraint.does_anything()), + ) + .map_err(RenderError::Freetype)?; + let slot = self.face.slot_info(); + + // For synthetic bold, we embolden the glyph. + if self.synthetic.bold { + // We need to scale the embolden amount based on the font size. + // This is a heuristic I found worked well across a variety of + // founts: 1 pixel per 64 units of height. + let font_height = self.face.size_metrics().height as f64; + let ratio: f64 = 64.0 / 2048.0; + let amount = (font_height * ratio).ceil(); + self.face.slot_embolden(amount as i64); + } + + // We get a rect that represents the position + // and size of the glyph before constraints. + let rect = self.get_glyph_size(); + + // If our glyph is smaller than a quarter pixel in either axis + // then it has no outlines or they're too small to render. + // + // In this case we just return 0-sized glyph struct. + if rect.width < 0.25 || rect.height < 0.25 { + return Ok(Glyph::default()); + } + + let metrics = opts.grid_metrics; + let cell_width = f64::from(metrics.cell_width); + let cell_height = f64::from(metrics.cell_height); + + // Next we apply any constraints to get the final size of the glyph. + let constraint = opts.constraint; + + // We need to add the baseline position before passing to the constrain + // function since it operates on cell-relative positions, not baseline. + let cell_baseline = f64::from(metrics.cell_baseline); + + let glyph_size = constraint.constrain( + super::GlyphSize { + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y + cell_baseline, + }, + &metrics, + opts.constraint_width, + ); + + let mut width = glyph_size.width; + let mut height = glyph_size.height; + let mut x = glyph_size.x; + let mut y = glyph_size.y; + + // We center all glyphs within the pixel-rounded and adjusted + // cell width if it's larger than the face width, so that they + // aren't weirdly off to the left. + // + // We don't do this if the glyph has a stretch constraint, + // since in that case the position was already calculated with the + // new cell width in mind. + if constraint.size != super::ConstraintSize::Stretch && metrics.face_width < cell_width { + // We add half the difference to re-center. + // + // NOTE: We round this to a whole-pixel amount because under + // FreeType, the outlines will be hinted, which isn't + // the case under CoreText. If we move the outlines by + // a non-whole-pixel amount, it completely ruins the + // hinting. + x += ((cell_width - metrics.face_width) / 2.0).round(); + } + + // If this is a bitmap glyph, it will always render as full pixels, + // not fractional pixels, so we need to quantize its position and + // size accordingly to align to full pixels so we get good results. + if slot.format == ft::GLYPH_FORMAT_BITMAP { + width = cell_width - (cell_width - width - x).round() - x.round(); + height = cell_height - (cell_height - height - y).round() - y.round(); + x = x.round(); + y = y.round(); + } + + // Now we can render the glyph. + let mut bitmap: ft::Bitmap; + let bitmap_left: i32; + let bitmap_top: i32; + match slot.format { + ft::GLYPH_FORMAT_OUTLINE => { + // Manually adjust the glyph outline with this transform. + // + // This offers better precision than using the freetype transform + // matrix, since that has 16.16 coefficients, and also I was having + // weird issues that I can only assume where due to freetype doing + // some bad caching or something when I did this using the matrix. + let scale_x = width / rect.width; + let scale_y = height / rect.height; + let skew: f64 = if self.synthetic.italic { + // We skew by 12 degrees to synthesize italics. + 12.0_f64.to_radians().tan() + } else { + 0.0 + }; + + self.face.slot_map_outline_points(|ox, oy| { + // Convert to f64 for processing + let mut px = f26dot6_to_f64(ox); + let mut py = f26dot6_to_f64(oy); + + // Subtract original bearings + px -= rect.x; + py -= rect.y; + + // Scale + px *= scale_x; + py *= scale_y; + + // Add new bearings + px += x; + py += y - cell_baseline; + + // Skew + px += py * skew; + + // Convert back and store + (f64_to_f26dot6(px), f64_to_f26dot6(py)) + }); + + self.face + .slot_render(self.load_flags.monochrome) + .map_err(RenderError::Freetype)?; + + // Copy the glyph's bitmap, making sure + // that it's 8bpp and densely packed. + bitmap = self + .face + .slot_bitmap_converted(&self.lib.inner.lib) + .map_err(|_| RenderError::BitmapHandlingError)?; + let after = self.face.slot_info(); + bitmap_left = after.bitmap_left; + bitmap_top = after.bitmap_top; + } + + ft::GLYPH_FORMAT_BITMAP => { + // If our glyph has a non-color bitmap, we need + // to convert it to dense 8bpp so that the scale + // operation works correctly. + let glyph_bitmap = match slot.pixel_mode { + ft::PIXEL_MODE_BGRA | ft::PIXEL_MODE_GRAY => self.face.slot_bitmap(), + _ => self + .face + .slot_bitmap_converted(&self.lib.inner.lib) + .map_err(|_| RenderError::BitmapHandlingError)?, + }; + + // The resize below interprets pixels using the atlas depth, so + // a mismatch would read garbage. + match glyph_bitmap.pixel_mode { + ft::PIXEL_MODE_GRAY if atlas.format != atlas::Format::Grayscale => { + return Err(RenderError::WrongAtlas); + } + ft::PIXEL_MODE_BGRA if atlas.format != atlas::Format::Bgra => { + return Err(RenderError::WrongAtlas); + } + _ => {} + } + + // Round our target width and height + // as the size for our scaled bitmap. + let w = width.round().max(0.0) as u32; + let h = height.round().max(0.0) as u32; + let depth = u32::from(atlas.format.depth()); + let pitch = w * depth; + + // Resize + let buf = resize::resize_uint8( + &glyph_bitmap.buffer, + glyph_bitmap.width as usize, + glyph_bitmap.rows as usize, + glyph_bitmap.pitch.unsigned_abs() as usize, + w as usize, + h as usize, + depth as usize, + ) + // This should never fail because this is a + // fairly straightforward in-memory operation... + .ok_or(RenderError::GlyphResizeFailed)?; + + bitmap = ft::Bitmap { + buffer: buf, + width: w, + rows: h, + pitch: pitch as i32, + pixel_mode: glyph_bitmap.pixel_mode, + num_grays: glyph_bitmap.num_grays, + }; + + // Update the bearings to account for the new positioning. + bitmap_top = (y - cell_baseline + height).floor() as i32; + bitmap_left = x.floor() as i32; + } + + f => { + // Glyph formats are tags, so we can + // output a semi-readable error here. + log::error!( + "Can't render glyph with unsupported glyph format \"{}\"", + String::from_utf8_lossy(&f.to_be_bytes()) + ); + return Err(RenderError::UnsupportedGlyphFormat); + } + } + + // If this is a color glyph but we're trying to render it to the + // grayscale atlas, or vice versa, then we throw and error. Maybe + // in the future we could convert, but for now it should be fine. + match bitmap.pixel_mode { + ft::PIXEL_MODE_GRAY => { + if atlas.format != atlas::Format::Grayscale { + return Err(RenderError::WrongAtlas); + } + } + ft::PIXEL_MODE_BGRA => { + if atlas.format != atlas::Format::Bgra { + return Err(RenderError::WrongAtlas); + } + } + mode => { + log::warn!("glyph={glyph_index} pixel mode={mode}"); + return Err(RenderError::UnsupportedPixelMode(mode)); + } + } + + // Our whole-pixel bearings for the final glyph. + // The fractional portion will be included in the rasterized position. + // + // For the Y position, FreeType's `bitmap_top` is the distance from the + // baseline to the top of the glyph, but we need the distance from the + // bottom of the cell to the bottom of the glyph, so first we add the + // baseline to get the distance from the bottom of the cell to the top + // of the glyph, then we subtract the height of the glyph to get the + // bottom. + let px_x: i32 = bitmap_left; + let px_y: i32 = bitmap_top + metrics.cell_baseline as i32 - bitmap.rows as i32; + + let px_width = bitmap.width; + let px_height = bitmap.rows; + let len = bitmap.pitch.unsigned_abs() as usize * bitmap.rows as usize; + + // If our bitmap is grayscale, make sure to multiply all pixel + // values by the right factor to bring `num_grays` up to 256. + // + // This is necessary because FT_Bitmap_Convert doesn't do this, + // it just sets num_grays to the correct number and uses the + // original smaller pixel values. + if bitmap.pixel_mode == ft::PIXEL_MODE_GRAY && bitmap.num_grays < 256 { + let factor = (255 / (i32::from(bitmap.num_grays) - 1)) as u8; + for p in &mut bitmap.buffer[..len] { + *p = p.wrapping_mul(factor); + } + bitmap.num_grays = 256; + } + + // Must have non-empty bitmap because we return earlier if zero. + // We assume the rest of this that it is non-zero so this is important. + debug_assert!(px_width > 0 && px_height > 0); + + // If this doesn't match then something is wrong. + debug_assert_eq!( + px_width * u32::from(atlas.format.depth()), + bitmap.pitch.unsigned_abs() + ); + + // Allocate our texture atlas region and copy our bitmap in to it. + let region = atlas.reserve(px_width, px_height)?; + atlas.set(region, &bitmap.buffer[..len]); + + // This should be the distance from the bottom of + // the cell to the top of the glyph's bounding box. + let offset_y: i32 = px_y + px_height as i32; + + // This should be the distance from the left of + // the cell to the left of the glyph's bounding box. + let offset_x: i32 = px_x; + + Ok(Glyph { + width: px_width, + height: px_height, + offset_x, + offset_y, + atlas_x: region.x, + atlas_y: region.y, + }) + } + + /// Convert 16.6 pixel format to pixels based on the scale factor of the + /// current font size. + #[allow(dead_code)] + fn units_to_px_y(&self, units: i32) -> i32 { + // FT_MulFix(units, y_scale) >> 6 + let scale = self.face.size_metrics().y_scale; + let prod = i64::from(units) * scale; + (((prod + 0x8000 + (prod >> 63)) >> 16) >> 6) as i32 + } + + /// Get the `FaceMetrics` for this face. + pub fn get_metrics(&self) -> FaceMetrics { + let face = &self.face; + + let size_metrics = face.size_metrics(); + + // This code relies on this assumption, and it should always be + // true since we don't do any non-uniform scaling on the font ever. + debug_assert_eq!(size_metrics.x_ppem, size_metrics.y_ppem); + + // Read the 'head' table out of the font data. + let head_units_per_em = face.head_units_per_em(); + + // Read the 'post' table out of the font data. + let post_ = face.post(); + + // Read the 'OS/2' table out of the font data. + let os2_ = face.os2(); + + // Read the 'hhea' table out of the font data. + let hhea_ = face.hhea(); + + // Whether the font is in a scalable format. We need to know this + // because many of the metrics provided by FreeType are invalid for + // non-scalable fonts. + let is_scalable = face.is_scalable(); + + // We get the UPM from the head table. + // + // If we have no head, but it is a scalable face, take the UPM from + // FreeType's units_per_EM, otherwise we'll assume that UPM == PPEM. + let units_per_em: u16 = if let Some(u) = head_units_per_em { + u + } else if is_scalable { + face.units_per_em() + } else { + size_metrics.y_ppem + }; + let px_per_em = f64::from(size_metrics.y_ppem); + let px_per_unit = px_per_em / f64::from(units_per_em); + + let (ascent, descent, line_gap): (f64, f64, f64) = 'vertical_metrics: { + let Some(hhea) = hhea_ else { + // If we couldn't get the hhea table, rely on metrics from FreeType. + let ascender = f26dot6_to_f64(size_metrics.ascender); + let descender = f26dot6_to_f64(size_metrics.descender); + let height = f26dot6_to_f64(size_metrics.height); + break 'vertical_metrics ( + ascender, + descender, + // We compute the line gap by adding the (negative) descender + // and subtracting the (positive) ascender from the line height + // to get the remaining gap size. + // + // NOTE: This might always be 0... but it doesn't hurt to do. + height + descender - ascender, + ); + }; + + let hhea_ascent = f64::from(hhea.ascender); + let hhea_descent = f64::from(hhea.descender); + let hhea_line_gap = f64::from(hhea.line_gap); + + // If our font has no OS/2 table, then we just + // blindly use the metrics from the hhea table. + let Some(os2) = os2_ else { + break 'vertical_metrics ( + hhea_ascent * px_per_unit, + hhea_descent * px_per_unit, + hhea_line_gap * px_per_unit, + ); + }; + + let os2_ascent = f64::from(os2.s_typo_ascender); + let os2_descent = f64::from(os2.s_typo_descender); + let os2_line_gap = f64::from(os2.s_typo_line_gap); + + // If the font says to use typo metrics, trust it. + // (The USE_TYPO_METRICS bit is bit 7) + if os2.fs_selection & (1 << 7) != 0 { + break 'vertical_metrics ( + os2_ascent * px_per_unit, + os2_descent * px_per_unit, + os2_line_gap * px_per_unit, + ); + } + + // Otherwise we prefer the height metrics from 'hhea' if they + // are available, or else OS/2 sTypo* metrics, and if all else + // fails then we use OS/2 usWin* metrics. + // + // This is not "standard" behavior, but it's our best bet to + // account for fonts being... just weird. It's pretty much what + // FreeType does to get its generic ascent and descent metrics. + + if hhea.ascender != 0 || hhea.descender != 0 { + break 'vertical_metrics ( + hhea_ascent * px_per_unit, + hhea_descent * px_per_unit, + hhea_line_gap * px_per_unit, + ); + } + + if os2_ascent != 0.0 || os2_descent != 0.0 { + break 'vertical_metrics ( + os2_ascent * px_per_unit, + os2_descent * px_per_unit, + os2_line_gap * px_per_unit, + ); + } + + let win_ascent = f64::from(os2.us_win_ascent); + let win_descent = f64::from(os2.us_win_descent); + ( + win_ascent * px_per_unit, + // usWinDescent is *positive* -> down unlike sTypoDescender + // and hhea.Descender, so we flip its sign to fix this. + -win_descent * px_per_unit, + 0.0, + ) + }; + + let (underline_position, underline_thickness): (Option, Option) = + if let Some(post) = post_ { + // Some fonts have degenerate 'post' tables where the underline + // thickness (and often position) are 0. We consider them null + // if this is the case and use our own fallbacks when we calculate. + let has_broken_underline = post.underline_thickness == 0; + + // If the underline position isn't 0 then we do use it, + // even if the thickness is't properly specified. + let pos = if has_broken_underline && post.underline_position == 0 { + None + } else { + Some(f64::from(post.underline_position) * px_per_unit) + }; + + let thick = if has_broken_underline { + None + } else { + Some(f64::from(post.underline_thickness) * px_per_unit) + }; + + (pos, thick) + } else { + (None, None) + }; + + // Similar logic to the underline above. + let (strikethrough_position, strikethrough_thickness): (Option, Option) = + if let Some(os2) = os2_ { + let has_broken_strikethrough = os2.y_strikeout_size == 0; + + let pos = if has_broken_strikethrough && os2.y_strikeout_position == 0 { + None + } else { + Some(f64::from(os2.y_strikeout_position) * px_per_unit) + }; + + let thick = if has_broken_strikethrough { + None + } else { + Some(f64::from(os2.y_strikeout_size) * px_per_unit) + }; + + (pos, thick) + } else { + (None, None) + }; + + // Cell width is calculated by calculating the widest width of the + // visible ASCII characters. Usually 'M' is widest but we just take + // whatever is widest. + // + // ASCII height is calculated as the height of the overall bounding + // box of the same characters. + // + // If we fail to load any visible ASCII we just use max_advance from + // the metrics provided by FreeType, and set ascii_height to null as + // it's optional. + let (cell_width, ascii_height): (f64, Option) = { + let _g = self.lock_ft(); + + let mut max: f64 = 0.0; + let mut top: f64 = 0.0; + let mut bottom: f64 = 0.0; + for c in b' '..127u8 { + if let Some(glyph_index) = face.char_index(u32::from(c)) + && face + .load_glyph(glyph_index, self.glyph_load_flags(false)) + .is_ok() + { + max = f26dot6_to_f64(face.slot_info().advance_x).max(max); + let rect = self.get_glyph_size(); + top = (rect.y + rect.height).max(top); + bottom = rect.y.min(bottom); + } + } + + // If we couldn't get valid measurements, just use + // FreeType's max_advance and null, respectively. + if max == 0.0 { + max = f26dot6_to_f64(size_metrics.max_advance); + } + let estimate = top - bottom; + let rect_height = if estimate <= 0.0 { + None + } else { + Some(estimate) + }; + + (max, rect_height) + }; + + // We use the cap and ex heights specified by the font if they're + // available, otherwise we try to measure the `H` and `x` glyphs. + let (cap_height, ex_height): (Option, Option) = 'heights: { + if let Some(os2) = os2_ { + // The OS/2 table does not include these metrics in version 1. + if os2.version >= 2 { + break 'heights ( + Some(f64::from(os2.s_cap_height) * px_per_unit), + Some(f64::from(os2.sx_height) * px_per_unit), + ); + } + } + + let measure = |ch: char| -> Option { + let _g = self.lock_ft(); + let glyph_index = face.char_index(ch as u32)?; + face.load_glyph(glyph_index, self.glyph_load_flags(false)) + .ok()?; + Some(self.get_glyph_size().height) + }; + (measure('H'), measure('x')) + }; + + // Measure "水" (CJK water ideograph, U+6C34) for our ic width. + let ic_width: Option = 'ic_width: { + let _g = self.lock_ft(); + + let Some(glyph) = face.char_index('水' as u32) else { + break 'ic_width None; + }; + + if face + .load_glyph(glyph, self.glyph_load_flags(false)) + .is_err() + { + break 'ic_width None; + } + + // If the advance of the glyph is less than the width of the actual + // glyph then we just treat it as invalid since it's probably wrong + // and using it for size normalization will instead make the font + // way too big. + // + // This can sometimes happen if there's a CJK font that has been + // patched with the nerd fonts patcher and it butchers the advance + // values so the advance ends up half the width of the actual glyph. + let ft_glyph_width = self.get_glyph_size().width; + let advance = f26dot6_to_f64(face.slot_info().advance_x); + if ft_glyph_width > advance { + log::warn!( + "(getMetrics) Width of glyph '水' for font \"{}\" is greater than its advance ({} > {}), discarding ic_width metric.", + self.name(), + ft_glyph_width, + advance, + ); + break 'ic_width None; + } + + Some(advance) + }; + + FaceMetrics { + px_per_em, + + cell_width, + + ascent, + descent, + line_gap, + + underline_position, + underline_thickness, + + strikethrough_position, + strikethrough_thickness, + + cap_height, + ex_height, + ascii_height, + ic_width, + } + } + + /// Copy the font table data for the given tag. + pub fn copy_table(&self, tag: &[u8; 4]) -> Result>, FaceError> { + self.face.load_sfnt_table(*tag).map_err(FaceError::Freetype) + } + + /// The harfbuzz font corresponding to this face (for shapers). + pub fn hb_font(&self) -> &HbFont { + &self.hb_font + } + + /// The library this face was created with. + pub fn library(&self) -> &Library { + &self.lib + } +} + +/// If true, the default font features should be disabled for the given face. +/// +/// Port of `quirks.disableDefaultFontFeatures`: this used to check specific +/// font names but the logic was integrated directly into the shaping +/// algorithm, so it is always false. +fn quirks_disable_default_font_features(_face: &Face) -> bool { + false +} + +fn set_size_(face: &ft::FtFace, size: DesiredSize) -> Result<(), FaceError> { + // If we have fixed sizes, we just have to try to pick the one closest + // to what the user requested. Otherwise, we can choose an arbitrary + // pixel size. + if face.is_scalable() { + let size_26dot6 = (size.points * 64.0).round() as i32; + face.set_char_size(0, size_26dot6, size.xdpi, size.ydpi) + .map_err(FaceError::Freetype) + } else { + select_size_nearest(face, size.pixels().round() as u32) + } +} + +/// Selects the fixed size in the loaded face that is closest to the +/// requested pixel size. +fn select_size_nearest(face: &ft::FtFace, size: u32) -> Result<(), FaceError> { + let mut best_i: i32 = 0; + let mut best_diff: i32 = 0; + for i in 0..face.num_fixed_sizes() { + let width = face.fixed_size_width(i); + let diff = size as i32 - width; + if i == 0 || diff < best_diff { + best_diff = diff; + best_i = i; + } + } + + face.select_size(best_i).map_err(FaceError::Freetype) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Metrics, embedded}; + + fn opts(points: f32, dpi: u16) -> Options { + Options::new(DesiredSize { + points, + xdpi: dpi, + ydpi: dpi, + }) + } + + fn init(lib: &Library, data: &'static [u8], points: f32, dpi: u16) -> Face { + Face::init(lib, data.into(), opts(points, dpi)).unwrap() + } + + #[test] + fn face_name() { + let lib = Library::init().unwrap(); + + { + let face = init(&lib, embedded::VARIABLE, 14.0, 96); + assert_eq!("JetBrains Mono", face.name()); + } + + { + let face = init(&lib, embedded::INCONSOLATA, 14.0, 96); + assert_eq!("Inconsolata", face.name()); + } + } + + #[test] + fn render_ascii_and_resize() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Grayscale); + let mut ft_font = init(&lib, embedded::INCONSOLATA, 12.0, 96); + + // Generate all visible ASCII + for i in 32u8..127 { + ft_font + .render_glyph( + &mut atlas, + ft_font.glyph_index(u32::from(i)).unwrap(), + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + } + + // Test resizing + let g1 = ft_font + .render_glyph( + &mut atlas, + ft_font.glyph_index('A' as u32).unwrap(), + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + assert_eq!(11, g1.height); + + ft_font.set_size(opts(24.0, 96)).unwrap(); + let g2 = ft_font + .render_glyph( + &mut atlas, + ft_font.glyph_index('A' as u32).unwrap(), + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + assert_eq!(21, g2.height); + } + + #[test] + fn color_emoji() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Bgra); + let ft_font = init(&lib, embedded::EMOJI, 12.0, 96); + + ft_font + .render_glyph( + &mut atlas, + ft_font.glyph_index('🥸' as u32).unwrap(), + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + + // Make sure this glyph has color + assert!(ft_font.has_color()); + let glyph_id = ft_font.glyph_index('🥸' as u32).unwrap(); + assert!(ft_font.is_color_glyph(glyph_id)); + } + + #[test] + fn mono_to_bgra() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Bgra); + let ft_font = init(&lib, embedded::EMOJI, 12.0, 72); + + // glyph 3 is mono in Noto + ft_font + .render_glyph( + &mut atlas, + 3, + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + } + + #[test] + fn svg_font_table() { + let lib = Library::init().unwrap(); + let face = init(&lib, embedded::JULIA_MONO, 12.0, 72); + + let table = face.copy_table(b"SVG ").unwrap().unwrap(); + assert_eq!(430, table.len()); + } + + const TERMINUS_I: &str = "\ +........\n\ +........\n\ +...#....\n\ +...#....\n\ +........\n\ +..##....\n\ +...#....\n\ +...#....\n\ +...#....\n\ +...#....\n\ +...#....\n\ +..###...\n\ +........\n\ +........\n\ +........\n\ +........"; + // Including the newline + const TERMINUS_I_PITCH: usize = 9; + + fn check_pattern(atlas: &Atlas, glyph: &Glyph, pattern: &str, pitch: usize) { + let pat = pattern.as_bytes(); + for y in 0..glyph.height as usize { + for x in 0..glyph.width as usize { + let pixel = pat[y * pitch + x]; + assert_eq!( + if pixel == b'#' { 255 } else { 0 }, + atlas.data[(glyph.atlas_y as usize + y) * atlas.size as usize + + (glyph.atlas_x as usize + x)], + "pixel ({x},{y})" + ); + } + } + } + + #[test] + fn bitmap_glyph() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Grayscale); + + // Any glyph at 12pt @ 96 DPI is a bitmap + let ft_font = init(&lib, embedded::TERMINUS_TTF, 12.0, 96); + + // glyph 77 = 'i' + let glyph = ft_font + .render_glyph( + &mut atlas, + 77, + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + + // should render crisp + assert_eq!(8, glyph.width); + assert_eq!(16, glyph.height); + check_pattern(&atlas, &glyph, TERMINUS_I, TERMINUS_I_PITCH); + } + + // Expected pixel pattern for Spleen 8x16 'A' (glyph index from char 'A') + // Derived from BDF BITMAP data: 00,00,7C,C6,C6,C6,FE,C6,C6,C6,C6,C6,00,00,00,00 + const SPLEEN_A: &str = "\ +........\n\ +........\n\ +.#####..\n\ +##...##.\n\ +##...##.\n\ +##...##.\n\ +#######.\n\ +##...##.\n\ +##...##.\n\ +##...##.\n\ +##...##.\n\ +##...##.\n\ +........\n\ +........\n\ +........\n\ +........"; + // Including the newline + const SPLEEN_A_PITCH: usize = 9; + // Test parameters for bitmap font tests + const SPLEEN_TEST_POINT_SIZE: f32 = 12.0; + const SPLEEN_TEST_DPI: u16 = 96; + + fn spleen_test(data: &'static [u8]) { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Grayscale); + + // Spleen 8x16 is a pure bitmap font at 16px height + let ft_font = init(&lib, data, SPLEEN_TEST_POINT_SIZE, SPLEEN_TEST_DPI); + + // Get glyph index for 'A' + let glyph_index = ft_font.glyph_index('A' as u32).expect("glyph not found"); + + let glyph = ft_font + .render_glyph( + &mut atlas, + glyph_index, + RenderOptions::new(Metrics::calc(ft_font.get_metrics())), + ) + .unwrap(); + + // Verify dimensions match Spleen 8x16 + assert_eq!(8, glyph.width); + assert_eq!(16, glyph.height); + + // Verify pixel-perfect rendering + check_pattern(&atlas, &glyph, SPLEEN_A, SPLEEN_A_PITCH); + } + + #[test] + fn bitmap_glyph_bdf() { + spleen_test(embedded::SPLEEN_BDF); + } + + #[test] + fn bitmap_glyph_pcf() { + spleen_test(embedded::SPLEEN_PCF); + } + + #[test] + fn bitmap_glyph_otb() { + spleen_test(embedded::SPLEEN_OTB); + } + + // ---- extra tests ---- + + #[test] + fn variation_axes_can_be_set() { + let lib = Library::init().unwrap(); + let mut face = init(&lib, embedded::VARIABLE, 12.0, 96); + let o = opts(12.0, 96); + face.set_variations( + &[Variation { + id: super::super::VariationId::new(b"wght"), + value: 800.0, + }], + o, + ) + .unwrap(); + // A non-variable font ignores variations. + let mut face = init(&lib, embedded::INCONSOLATA, 12.0, 96); + face.set_variations( + &[Variation { + id: super::super::VariationId::new(b"wght"), + value: 800.0, + }], + o, + ) + .unwrap(); + } + + #[test] + fn synthetic_bold_and_italic_render() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Grayscale); + let face = init(&lib, embedded::INCONSOLATA, 12.0, 96); + let o = opts(12.0, 96); + let bold = face.synthetic_bold(o).unwrap(); + let italic = face.synthetic_italic(o).unwrap(); + assert!(bold.synthetic.bold && !bold.synthetic.italic); + assert!(italic.synthetic.italic && !italic.synthetic.bold); + let ro = RenderOptions::new(Metrics::calc(face.get_metrics())); + let gi = face.glyph_index('A' as u32).unwrap(); + let g0 = face.render_glyph(&mut atlas, gi, ro).unwrap(); + let g1 = bold.render_glyph(&mut atlas, gi, ro).unwrap(); + let g2 = italic.render_glyph(&mut atlas, gi, ro).unwrap(); + assert!(g1.width >= g0.width); + assert!(g2.width >= g0.width); + // The original face is still usable after the copies are dropped. + drop(bold); + drop(italic); + assert_eq!("Inconsolata", face.name()); + } + + #[test] + fn wrong_atlas_is_an_error() { + let lib = Library::init().unwrap(); + let mut atlas = Atlas::new(512, atlas::Format::Bgra); + let face = init(&lib, embedded::INCONSOLATA, 12.0, 96); + let r = face.render_glyph( + &mut atlas, + face.glyph_index('A' as u32).unwrap(), + RenderOptions::new(Metrics::calc(face.get_metrics())), + ); + assert!(matches!(r, Err(RenderError::WrongAtlas))); + } + + #[test] + fn metrics_are_sane() { + let lib = Library::init().unwrap(); + let face = init(&lib, embedded::VARIABLE, 12.0, 96); + let m = face.get_metrics(); + assert!(m.px_per_em > 0.0); + assert!(m.cell_width > 0.0); + assert!(m.ascent > 0.0 && m.descent < 0.0); + assert!(m.cap_height.is_some() && m.ex_height.is_some()); + } + + #[test] + fn hb_font_is_available() { + let lib = Library::init().unwrap(); + let face = init(&lib, embedded::INCONSOLATA, 12.0, 96); + assert!(!face.hb_font().as_ptr().is_null()); + } + + #[test] + fn init_file_from_disk() { + let lib = Library::init().unwrap(); + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/res/Inconsolata-Regular.ttf"); + let face = Face::init_file(&lib, path, 0, opts(12.0, 96)).unwrap(); + assert_eq!("Inconsolata", face.name()); + assert!(Face::init_file(&lib, "/nonexistent/font.ttf", 0, opts(12.0, 96)).is_err()); + } +} diff --git a/crates/ghostty-font/src/face/freetype/ft.rs b/crates/ghostty-font/src/face/freetype/ft.rs new file mode 100644 index 0000000..78c7326 --- /dev/null +++ b/crates/ghostty-font/src/face/freetype/ft.rs @@ -0,0 +1,676 @@ +//! Thin, safe-ish wrappers over the raw `freetype-sys` / `harfbuzz-sys` +//! bindings. All FFI in the FreeType face lives here so that the rest of the +//! port (`freetype.rs`) is safe Rust. +//! +//! Callers are responsible for serialising access to a face's glyph slot +//! (see `Face::ft_mutex`); nothing in this module locks. +#![allow(unsafe_code)] +// c_long is i64 on Linux but not everywhere; keep the casts portable. +#![allow(clippy::unnecessary_cast)] + +use std::ffi::{CStr, c_int, c_long, c_uint, c_ulong, c_void}; +use std::ptr::{self, NonNull}; + +use freetype_sys as sys; + +/// FreeType error code (0 is never used for an error). +pub type FtError = i32; + +// --------------------------------------------------------------------------- +// Declarations missing from `freetype-sys`. + +unsafe extern "C" { + fn FT_Bitmap_Init(abitmap: *mut sys::FT_Bitmap); + fn FT_Bitmap_Convert( + library: sys::FT_Library, + source: *const sys::FT_Bitmap, + target: *mut sys::FT_Bitmap, + alignment: c_int, + ) -> sys::FT_Error; + fn FT_Bitmap_Done(library: sys::FT_Library, bitmap: *mut sys::FT_Bitmap) -> sys::FT_Error; +} + +/// `FT_LOAD_NO_SVG` +pub const FT_LOAD_NO_SVG: i32 = 1 << 24; + +/// Prefix of `TT_Header` (only fields up to `Units_Per_EM` are needed). +#[repr(C)] +struct TtHeader { + table_version: c_long, + font_revision: c_long, + checksum_adjust: c_long, + magic_number: c_long, + flags: u16, + units_per_em: u16, +} + +/// Prefix of `TT_HoriHeader` (only fields up to `Line_Gap` are needed). +#[repr(C)] +struct TtHoriHeader { + version: c_long, + ascender: i16, + descender: i16, + line_gap: i16, +} + +// --------------------------------------------------------------------------- + +/// An owned FT_Library. +pub struct Lib { + handle: sys::FT_Library, +} + +// SAFETY: FT_Library is only used from behind the `Library` mutex for +// face creation/destruction; FreeType libraries may be moved between threads. +unsafe impl Send for Lib {} +// SAFETY: see above; shared use is serialised by `Library::mutex`. +unsafe impl Sync for Lib {} + +impl Lib { + pub fn new() -> Result { + let mut handle: sys::FT_Library = ptr::null_mut(); + // SAFETY: `handle` is a valid out pointer. + let err = unsafe { sys::FT_Init_FreeType(&raw mut handle) }; + if err != 0 { + return Err(err); + } + Ok(Lib { handle }) + } +} + +impl Drop for Lib { + fn drop(&mut self) { + // SAFETY: `handle` was returned by FT_Init_FreeType and is freed once. + unsafe { + sys::FT_Done_FreeType(self.handle); + } + } +} + +/// Copy of the `FT_Size_Metrics` fields we use. +#[derive(Debug, Clone, Copy)] +pub struct SizeMetrics { + pub x_ppem: u16, + pub y_ppem: u16, + pub y_scale: i64, + pub ascender: i64, + pub descender: i64, + pub height: i64, + pub max_advance: i64, +} + +/// Copy of the `TT_OS2` fields we use. +#[derive(Debug, Clone, Copy)] +pub struct Os2 { + pub version: u16, + pub fs_selection: u16, + pub s_typo_ascender: i16, + pub s_typo_descender: i16, + pub s_typo_line_gap: i16, + pub us_win_ascent: u16, + pub us_win_descent: u16, + pub y_strikeout_size: i16, + pub y_strikeout_position: i16, + pub sx_height: i16, + pub s_cap_height: i16, +} + +/// Copy of the `TT_Postscript` fields we use. +#[derive(Debug, Clone, Copy)] +pub struct Post { + pub underline_position: i16, + pub underline_thickness: i16, +} + +/// Copy of the `TT_HoriHeader` fields we use. +#[derive(Debug, Clone, Copy)] +pub struct Hhea { + pub ascender: i16, + pub descender: i16, + pub line_gap: i16, +} + +/// A `FT_SfntName` entry (string copied). +#[derive(Debug, Clone)] +pub struct SfntName { + pub platform_id: u16, + pub encoding_id: u16, + pub name_id: u16, + pub string: Vec, +} + +/// A variation axis of a multiple-master / variable font. +#[derive(Debug, Clone, Copy)] +pub struct VarAxis { + pub tag: u32, + pub minimum: i64, + pub def: i64, + pub maximum: i64, +} + +/// A copy of an `FT_Bitmap` (bytes included). +#[derive(Debug, Clone, Default)] +pub struct Bitmap { + pub buffer: Vec, + pub rows: u32, + pub width: u32, + pub pitch: i32, + pub num_grays: u16, + pub pixel_mode: u32, +} + +/// Copy of the glyph-slot fields we use. +#[derive(Debug, Clone, Copy)] +pub struct SlotInfo { + pub format: u32, + pub metrics_width: i64, + pub metrics_height: i64, + pub hori_bearing_x: i64, + pub hori_bearing_y: i64, + pub advance_x: i64, + pub bitmap_left: i32, + pub bitmap_top: i32, + pub pixel_mode: u32, +} + +pub const GLYPH_FORMAT_OUTLINE: u32 = sys::FT_GLYPH_FORMAT_OUTLINE; +pub const GLYPH_FORMAT_BITMAP: u32 = sys::FT_GLYPH_FORMAT_BITMAP; +pub const PIXEL_MODE_GRAY: u32 = sys::FT_PIXEL_MODE_GRAY; +pub const PIXEL_MODE_BGRA: u32 = sys::FT_PIXEL_MODE_BGRA; + +/// Target load/render modes. +pub const TARGET_NORMAL: i32 = sys::FT_LOAD_TARGET_NORMAL; +pub const TARGET_LIGHT: i32 = sys::FT_LOAD_TARGET_LIGHT; +pub const TARGET_MONO: i32 = sys::FT_LOAD_TARGET_MONO; + +/// Load flag bits. +pub const LOAD_NO_HINTING: i32 = sys::FT_LOAD_NO_HINTING; +pub const LOAD_RENDER: i32 = sys::FT_LOAD_RENDER; +pub const LOAD_FORCE_AUTOHINT: i32 = sys::FT_LOAD_FORCE_AUTOHINT; +pub const LOAD_NO_AUTOHINT: i32 = sys::FT_LOAD_NO_AUTOHINT; +pub const LOAD_COLOR: i32 = sys::FT_LOAD_COLOR; + +pub const RENDER_NORMAL: c_uint = sys::FT_RENDER_MODE_NORMAL; +pub const RENDER_MONO: c_uint = sys::FT_RENDER_MODE_MONO; + +pub const TT_NAME_ID_FONT_FAMILY: u16 = 1; +pub const TT_PLATFORM_MICROSOFT: u16 = 3; +pub const TT_MS_ID_UNICODE_CS: u16 = 1; + +/// An owned reference to an `FT_Face` (released with `FT_Done_Face`). +pub struct FtFace { + ptr: NonNull, +} + +// SAFETY: FT_Face is moved between threads freely; concurrent use is +// serialised by the owner (`Face::ft_mutex`). +unsafe impl Send for FtFace {} +// SAFETY: see above. +unsafe impl Sync for FtFace {} + +impl Drop for FtFace { + fn drop(&mut self) { + // SAFETY: we own one reference to the face. + unsafe { + sys::FT_Done_Face(self.ptr.as_ptr()); + } + } +} + +impl FtFace { + fn from_raw(raw: sys::FT_Face) -> Result { + NonNull::new(raw) + .map(|ptr| FtFace { ptr }) + .ok_or(sys::FT_Err_Invalid_Handle) + } + + /// Open a face from memory. The memory must outlive the face. + pub fn new_memory(lib: &Lib, data: &[u8], index: i32) -> Result { + let mut raw: sys::FT_Face = ptr::null_mut(); + // SAFETY: `data` is valid for the call; the caller keeps it alive for + // the lifetime of the face. + let err = unsafe { + sys::FT_New_Memory_Face( + lib.handle, + data.as_ptr(), + data.len() as c_long, + index as c_long, + &raw mut raw, + ) + }; + if err != 0 { + return Err(err); + } + FtFace::from_raw(raw) + } + + pub fn new_file(lib: &Lib, path: &CStr, index: i32) -> Result { + let mut raw: sys::FT_Face = ptr::null_mut(); + // SAFETY: `path` is NUL terminated. + let err = + unsafe { sys::FT_New_Face(lib.handle, path.as_ptr(), index as c_long, &raw mut raw) }; + if err != 0 { + return Err(err); + } + FtFace::from_raw(raw) + } + + /// Create another owning handle to the same face (`FT_Reference_Face`). + pub fn reference(&self) -> Result { + // SAFETY: valid face. + let err = unsafe { sys::FT_Reference_Face(self.ptr.as_ptr()) }; + if err != 0 { + return Err(err); + } + Ok(FtFace { ptr: self.ptr }) + } + + pub fn as_ptr(&self) -> sys::FT_Face { + self.ptr.as_ptr() + } + + fn rec(&self) -> &sys::FT_FaceRec { + // SAFETY: the pointer is valid while we hold a reference. + unsafe { self.ptr.as_ref() } + } + + pub fn select_unicode_charmap(&self) -> Result<(), FtError> { + // SAFETY: valid face. + let err = unsafe { sys::FT_Select_Charmap(self.as_ptr(), sys::FT_ENCODING_UNICODE) }; + if err == 0 { Ok(()) } else { Err(err) } + } + + pub fn set_char_size( + &self, + char_width: i32, + char_height: i32, + xdpi: u16, + ydpi: u16, + ) -> Result<(), FtError> { + // SAFETY: valid face. + let err = unsafe { + sys::FT_Set_Char_Size( + self.as_ptr(), + char_width as c_long, + char_height as c_long, + c_uint::from(xdpi), + c_uint::from(ydpi), + ) + }; + if err == 0 { Ok(()) } else { Err(err) } + } + + pub fn select_size(&self, idx: i32) -> Result<(), FtError> { + // SAFETY: valid face. + let err = unsafe { sys::FT_Select_Size(self.as_ptr(), idx) }; + if err == 0 { Ok(()) } else { Err(err) } + } + + pub fn num_fixed_sizes(&self) -> i32 { + self.rec().num_fixed_sizes + } + + /// Width of the `i`th available fixed size. + pub fn fixed_size_width(&self, i: i32) -> i32 { + debug_assert!(i >= 0 && i < self.num_fixed_sizes()); + // SAFETY: `i` is within `num_fixed_sizes`. + i32::from(unsafe { (*self.rec().available_sizes.add(i as usize)).width }) + } + + fn flags(&self) -> c_long { + self.rec().face_flags + } + + pub fn is_scalable(&self) -> bool { + self.flags() & sys::FT_FACE_FLAG_SCALABLE != 0 + } + + pub fn has_color(&self) -> bool { + self.flags() & sys::FT_FACE_FLAG_COLOR != 0 + } + + pub fn has_multiple_masters(&self) -> bool { + self.flags() & sys::FT_FACE_FLAG_MULTIPLE_MASTERS != 0 + } + + pub fn units_per_em(&self) -> u16 { + self.rec().units_per_EM + } + + pub fn char_index(&self, cp: u32) -> Option { + // SAFETY: valid face. + let i = unsafe { sys::FT_Get_Char_Index(self.as_ptr(), c_ulong::from(cp)) }; + if i == 0 { None } else { Some(i) } + } + + pub fn load_glyph(&self, glyph_index: u32, flags: i32) -> Result<(), FtError> { + // SAFETY: valid face; glyph slot access is serialised by the caller. + let err = unsafe { sys::FT_Load_Glyph(self.as_ptr(), glyph_index, flags) }; + if err == 0 { Ok(()) } else { Err(err) } + } + + pub fn size_metrics(&self) -> SizeMetrics { + // SAFETY: a face always has an active size once a size has been set. + let m = unsafe { (*self.rec().size).metrics }; + SizeMetrics { + x_ppem: m.x_ppem, + y_ppem: m.y_ppem, + y_scale: m.y_scale as i64, + ascender: m.ascender as i64, + descender: m.descender as i64, + height: m.height as i64, + max_advance: m.max_advance as i64, + } + } + + fn sfnt_table(&self, tag: u32) -> *mut c_void { + // SAFETY: valid face; the returned pointer is owned by the face. + unsafe { sys::FT_Get_Sfnt_Table(self.as_ptr(), tag) } + } + + /// `Units_Per_EM` from the `head` table. + pub fn head_units_per_em(&self) -> Option { + let p = self.sfnt_table(sys::FT_SFNT_HEAD).cast::(); + // SAFETY: non-null pointers refer to a `TT_Header` owned by the face. + unsafe { p.as_ref() }.map(|h| h.units_per_em) + } + + pub fn post(&self) -> Option { + let p = self + .sfnt_table(sys::FT_SFNT_POST) + .cast::(); + // SAFETY: non-null pointers refer to a `TT_Postscript` owned by the face. + unsafe { p.as_ref() }.map(|p| Post { + underline_position: p.underlinePosition, + underline_thickness: p.underlineThickness, + }) + } + + /// The OS/2 table; `None` if absent or if FreeType flags it as invalid + /// (version 0xFFFF). + pub fn os2(&self) -> Option { + let p = self.sfnt_table(sys::FT_SFNT_OS2).cast::(); + // SAFETY: non-null pointers refer to a `TT_OS2` owned by the face. + let o = unsafe { p.as_ref() }?; + if o.version == 0xFFFF { + return None; + } + Some(Os2 { + version: o.version, + fs_selection: o.fsSelection, + s_typo_ascender: o.sTypoAscender, + s_typo_descender: o.sTypoDescender, + s_typo_line_gap: o.sTypoLineGap, + us_win_ascent: o.usWinAscent, + us_win_descent: o.usWinDescent, + y_strikeout_size: o.yStrikeoutSize, + y_strikeout_position: o.yStrikeoutPosition, + sx_height: o.sxHeight, + s_cap_height: o.sCapHeight, + }) + } + + pub fn hhea(&self) -> Option { + let p = self.sfnt_table(sys::FT_SFNT_HHEA).cast::(); + // SAFETY: non-null pointers refer to a `TT_HoriHeader` owned by the face. + unsafe { p.as_ref() }.map(|h| Hhea { + ascender: h.ascender, + descender: h.descender, + line_gap: h.line_gap, + }) + } + + pub fn sfnt_name_count(&self) -> usize { + // SAFETY: valid face. + unsafe { sys::FT_Get_Sfnt_Name_Count(self.as_ptr()) as usize } + } + + pub fn sfnt_name(&self, i: usize) -> Option { + let mut name = std::mem::MaybeUninit::::uninit(); + // SAFETY: out pointer is valid for a FT_SfntName. + let err = unsafe { sys::FT_Get_Sfnt_Name(self.as_ptr(), i as c_uint, name.as_mut_ptr()) }; + if err != 0 { + return None; + } + // SAFETY: initialised by the successful call above. + let name = unsafe { name.assume_init() }; + let string = if name.string.is_null() { + Vec::new() + } else { + // SAFETY: FreeType guarantees `string_len` readable bytes. + unsafe { std::slice::from_raw_parts(name.string, name.string_len as usize) }.to_vec() + }; + Some(SfntName { + platform_id: name.platform_id, + encoding_id: name.encoding_id, + name_id: name.name_id, + string, + }) + } + + /// Copy of an SFNT table. `Ok(None)` if the table has zero length. + pub fn load_sfnt_table(&self, tag: [u8; 4]) -> Result>, FtError> { + let tag_c = c_ulong::from(u32::from_be_bytes(tag)); + let mut len: c_ulong = 0; + // SAFETY: a null buffer asks only for the length. + let err = unsafe { + sys::FT_Load_Sfnt_Table(self.as_ptr(), tag_c, 0, ptr::null_mut(), &raw mut len) + }; + if err != 0 { + return Err(err); + } + if len == 0 { + return Ok(None); + } + let mut buf = vec![0u8; len as usize]; + // SAFETY: `buf` has `len` bytes. + let err = unsafe { + sys::FT_Load_Sfnt_Table(self.as_ptr(), tag_c, 0, buf.as_mut_ptr(), &raw mut len) + }; + if err != 0 { + return Err(err); + } + Ok(Some(buf)) + } + + /// The variation axes of this face (`FT_Get_MM_Var`). + pub fn mm_axes(&self, lib: &Lib) -> Result, FtError> { + let mut mm: *mut sys::FT_MM_Var = ptr::null_mut(); + // SAFETY: out pointer is valid. + let err = unsafe { sys::FT_Get_MM_Var(self.as_ptr(), &raw mut mm) }; + if err != 0 { + return Err(err); + } + // SAFETY: `mm` is valid until FT_Done_MM_Var; `axis` has `num_axis` entries. + let axes = unsafe { + let m = &*mm; + (0..m.num_axis as usize) + .map(|i| { + let a = &*m.axis.add(i); + VarAxis { + tag: a.tag as u32, + minimum: a.minimum as i64, + def: a.def as i64, + maximum: a.maximum as i64, + } + }) + .collect::>() + }; + // SAFETY: `mm` came from FT_Get_MM_Var on this library. + unsafe { + sys::FT_Done_MM_Var(lib.handle, mm); + } + Ok(axes) + } + + pub fn var_design_coordinates(&self, n: usize) -> Result, FtError> { + let mut coords: Vec = vec![0; n]; + // SAFETY: buffer has `n` entries. + let err = unsafe { + sys::FT_Get_Var_Design_Coordinates(self.as_ptr(), n as c_uint, coords.as_mut_ptr()) + }; + if err != 0 { + return Err(err); + } + Ok(coords.into_iter().map(|c| c as i64).collect()) + } + + pub fn set_var_design_coordinates(&self, coords: &[i64]) -> Result<(), FtError> { + let c: Vec = coords.iter().map(|&v| v as c_long).collect(); + // SAFETY: buffer has `c.len()` entries. + let err = unsafe { + sys::FT_Set_Var_Design_Coordinates(self.as_ptr(), c.len() as c_uint, c.as_ptr()) + }; + if err == 0 { Ok(()) } else { Err(err) } + } + + // -- glyph slot --------------------------------------------------------- + + fn slot(&self) -> &sys::FT_GlyphSlotRec { + // SAFETY: a face always has a glyph slot; access is serialised by caller. + unsafe { &*self.rec().glyph } + } + + pub fn slot_info(&self) -> SlotInfo { + let s = self.slot(); + SlotInfo { + format: s.format, + metrics_width: s.metrics.width as i64, + metrics_height: s.metrics.height as i64, + hori_bearing_x: s.metrics.horiBearingX as i64, + hori_bearing_y: s.metrics.horiBearingY as i64, + advance_x: s.advance.x as i64, + bitmap_left: s.bitmap_left, + bitmap_top: s.bitmap_top, + pixel_mode: s.bitmap.pixel_mode as u8 as u32, + } + } + + /// Outline bounding box `(xMin, yMin, xMax, yMax)` in 26.6 units. + pub fn slot_outline_bbox(&self) -> (i64, i64, i64, i64) { + let mut bbox = std::mem::MaybeUninit::::zeroed(); + // SAFETY: valid outline in the slot; out pointer valid. + unsafe { + sys::FT_Outline_Get_BBox(&raw const self.slot().outline, bbox.as_mut_ptr()); + let b = bbox.assume_init(); + (b.xMin as i64, b.yMin as i64, b.xMax as i64, b.yMax as i64) + } + } + + pub fn slot_embolden(&self, strength: i64) { + // SAFETY: the slot's outline is modified in place; access is serialised. + unsafe { + let slot = self.rec().glyph; + sys::FT_Outline_Embolden(&raw mut (*slot).outline, strength as c_long); + } + } + + /// Rewrites each outline point (26.6 coordinates) in place. + pub fn slot_map_outline_points(&self, mut f: impl FnMut(i64, i64) -> (i32, i32)) { + // SAFETY: `points` has `n_points` entries; access is serialised. + unsafe { + let slot = self.rec().glyph; + let outline = &mut (*slot).outline; + if outline.points.is_null() { + return; + } + let pts = + std::slice::from_raw_parts_mut(outline.points, outline.n_points.max(0) as usize); + for p in pts { + let (x, y) = f(p.x as i64, p.y as i64); + p.x = c_long::from(x); + p.y = c_long::from(y); + } + } + } + + pub fn slot_render(&self, mono: bool) -> Result<(), FtError> { + let mode = if mono { RENDER_MONO } else { RENDER_NORMAL }; + // SAFETY: valid glyph slot. + let err = unsafe { sys::FT_Render_Glyph(self.rec().glyph, mode) }; + if err == 0 { Ok(()) } else { Err(err) } + } + + /// Copy the slot's bitmap as-is. + pub fn slot_bitmap(&self) -> Bitmap { + let b = &self.slot().bitmap; + Bitmap { + buffer: copy_bitmap_bytes(b), + rows: b.rows as u32, + width: b.width as u32, + pitch: b.pitch, + num_grays: b.num_grays as u16, + pixel_mode: b.pixel_mode as u8 as u32, + } + } + + /// `FT_Bitmap_Convert(slot.bitmap, 1)`: a dense 8bpp copy of the slot's + /// bitmap. + pub fn slot_bitmap_converted(&self, lib: &Lib) -> Result { + let mut target = std::mem::MaybeUninit::::uninit(); + // SAFETY: initialises `target`; converts into it; freed with FT_Bitmap_Done. + unsafe { + FT_Bitmap_Init(target.as_mut_ptr()); + let mut target = target.assume_init(); + if FT_Bitmap_Convert( + lib.handle, + &raw const self.slot().bitmap, + &raw mut target, + 1, + ) != 0 + { + FT_Bitmap_Done(lib.handle, &raw mut target); + return Err(sys::FT_Err_Invalid_Argument); + } + let out = Bitmap { + buffer: copy_bitmap_bytes(&target), + rows: target.rows as u32, + width: target.width as u32, + pitch: target.pitch, + num_grays: target.num_grays as u16, + pixel_mode: target.pixel_mode as u8 as u32, + }; + FT_Bitmap_Done(lib.handle, &raw mut target); + Ok(out) + } + } + + /// Creates a harfbuzz font for this face (`hb_ft_font_create_referenced`). + pub fn create_hb_font(&self) -> Option { + // SAFETY: valid face; harfbuzz takes its own FreeType reference. + let p = unsafe { harfbuzz_sys::freetype::hb_ft_font_create_referenced(self.as_ptr()) }; + NonNull::new(p).map(HbFont) + } +} + +fn copy_bitmap_bytes(b: &sys::FT_Bitmap) -> Vec { + let len = b.pitch.unsigned_abs() as usize * b.rows as usize; + if b.buffer.is_null() || len == 0 { + return Vec::new(); + } + // SAFETY: FreeType bitmaps own `|pitch| * rows` bytes at `buffer`. + unsafe { std::slice::from_raw_parts(b.buffer, len) }.to_vec() +} + +/// A harfbuzz font owned by a [`Face`](super::Face). +pub struct HbFont(NonNull); + +// SAFETY: hb_font_t is thread-safe for shaping when not mutated; Ghostty uses +// it behind the collection lock exactly like upstream. +unsafe impl Send for HbFont {} +// SAFETY: see above. +unsafe impl Sync for HbFont {} + +impl HbFont { + /// The raw `hb_font_t` pointer (valid while the `Face` lives). + pub fn as_ptr(&self) -> *mut harfbuzz_sys::hb_font_t { + self.0.as_ptr() + } +} + +impl Drop for HbFont { + fn drop(&mut self) { + // SAFETY: we own one reference to the hb font. + unsafe { harfbuzz_sys::hb_font_destroy(self.0.as_ptr()) }; + } +} diff --git a/crates/ghostty-font/src/face/freetype/resize.rs b/crates/ghostty-font/src/face/freetype/resize.rs new file mode 100644 index 0000000..3c50d76 --- /dev/null +++ b/crates/ghostty-font/src/face/freetype/resize.rs @@ -0,0 +1,145 @@ +//! A small separable image resampler that mirrors the defaults of +//! `stbir_resize_uint8` (stb_image_resize v0.9x): 8-bit linear data, no alpha +//! handling, edge clamping, Catmull-Rom when upsampling and Mitchell when +//! downsampling, with the filter support scaled by the resize ratio. + +fn catmull_rom(x: f32) -> f32 { + let x = x.abs(); + if x < 1.0 { + 1.0 - x * x * (2.5 - 1.5 * x) + } else if x < 2.0 { + 2.0 - x * (4.0 + x * (0.5 * x - 2.5)) + } else { + 0.0 + } +} + +fn mitchell(x: f32) -> f32 { + let x = x.abs(); + if x < 1.0 { + (16.0 + x * x * (21.0 * x - 36.0)) / 18.0 + } else if x < 2.0 { + (32.0 + x * (-60.0 + x * (36.0 - 7.0 * x))) / 18.0 + } else { + 0.0 + } +} + +/// Filter taps for one axis: for each output pixel the first input pixel and +/// its normalised weights. +fn contributors(in_size: usize, out_size: usize) -> Vec<(i64, Vec)> { + let scale = out_size as f32 / in_size as f32; + let upsample = scale > 1.0; + let mut out = Vec::with_capacity(out_size); + for n in 0..out_size { + let out_center = n as f32 + 0.5; + let in_center = out_center / scale; + // Radius of the filter support in input pixels. + let radius = if upsample { 2.0 } else { 2.0 / scale }; + let first = (in_center - radius + 0.5).floor() as i64; + let last = (in_center + radius - 0.5).floor() as i64; + let mut weights = Vec::with_capacity((last - first + 1).max(0) as usize); + for i in first..=last { + let d = (i as f32 + 0.5) - in_center; + let w = if upsample { + catmull_rom(d) + } else { + mitchell(d * scale) + }; + weights.push(w); + } + let sum: f32 = weights.iter().sum(); + if sum != 0.0 { + for w in &mut weights { + *w /= sum; + } + } + out.push((first, weights)); + } + out +} + +/// Equivalent of `stbir_resize_uint8`. Returns `None` on invalid sizes. +pub fn resize_uint8( + input: &[u8], + in_w: usize, + in_h: usize, + in_stride: usize, + out_w: usize, + out_h: usize, + channels: usize, +) -> Option> { + if in_w == 0 || in_h == 0 || out_w == 0 || out_h == 0 || channels == 0 { + return None; + } + if input.len() < in_stride * (in_h - 1) + in_w * channels { + return None; + } + + // Horizontal pass: in_h rows x out_w columns, f32. + let hx = contributors(in_w, out_w); + let mut tmp = vec![0f32; in_h * out_w * channels]; + for y in 0..in_h { + let row = &input[y * in_stride..y * in_stride + in_w * channels]; + for (x, (first, weights)) in hx.iter().enumerate() { + for c in 0..channels { + let mut acc = 0f32; + for (k, w) in weights.iter().enumerate() { + let sx = (first + k as i64).clamp(0, in_w as i64 - 1) as usize; + acc += f32::from(row[sx * channels + c]) * w; + } + tmp[(y * out_w + x) * channels + c] = acc; + } + } + } + + // Vertical pass. + let vy = contributors(in_h, out_h); + let mut out = vec![0u8; out_h * out_w * channels]; + for (y, (first, weights)) in vy.iter().enumerate() { + for x in 0..out_w { + for c in 0..channels { + let mut acc = 0f32; + for (k, w) in weights.iter().enumerate() { + let sy = (first + k as i64).clamp(0, in_h as i64 - 1) as usize; + acc += tmp[(sy * out_w + x) * channels + c] * w; + } + out[(y * out_w + x) * channels + c] = (acc + 0.5).floor().clamp(0.0, 255.0) as u8; + } + } + } + Some(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn constant_image_stays_constant() { + let src = vec![200u8; 5 * 7]; + for (w, h) in [(3, 4), (10, 14), (5, 7), (1, 1)] { + let out = resize_uint8(&src, 5, 7, 5, w, h, 1).unwrap(); + assert_eq!(out.len(), w * h); + assert!(out.iter().all(|&p| (199..=201).contains(&p))); + } + } + + #[test] + fn rejects_empty() { + assert!(resize_uint8(&[], 0, 0, 0, 1, 1, 1).is_none()); + assert!(resize_uint8(&[0], 1, 1, 1, 0, 1, 1).is_none()); + } + + #[test] + fn four_channels_are_independent() { + let mut src = Vec::new(); + for _ in 0..4 { + src.extend_from_slice(&[10, 20, 30, 40]); + } + let out = resize_uint8(&src, 2, 2, 8, 4, 4, 4).unwrap(); + for px in out.chunks(4) { + assert_eq!(px, &[10, 20, 30, 40]); + } + } +} diff --git a/crates/ghostty-font/src/fontconfig.rs b/crates/ghostty-font/src/fontconfig.rs new file mode 100644 index 0000000..1904517 --- /dev/null +++ b/crates/ghostty-font/src/fontconfig.rs @@ -0,0 +1,438 @@ +//! Minimal safe wrappers over the fontconfig C API (the subset used by +//! font discovery). Equivalent of `pkg/fontconfig` in the Zig tree. +//! +//! All raw FFI is confined to this module. +#![allow(unsafe_code)] + +use std::ffi::{CStr, CString, c_int}; +use std::ptr::{self, NonNull}; +use std::sync::Arc; + +use fontconfig_sys as sys; +use fontconfig_sys::constants as fc; + +/// Errors from fontconfig calls (mirrors `fontconfig.Error`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum Error { + #[error("fontconfig: no match")] + NoMatch, + #[error("fontconfig: type mismatch")] + TypeMismatch, + #[error("fontconfig: no id")] + NoId, + #[error("fontconfig: out of memory")] + OutOfMemory, + #[error("fontconfig failed")] + FontconfigFailed, +} + +fn result_to_error(r: u32) -> Result<(), Error> { + match r { + sys::FcResultMatch => Ok(()), + sys::FcResultNoMatch => Err(Error::NoMatch), + sys::FcResultTypeMismatch => Err(Error::TypeMismatch), + sys::FcResultNoId => Err(Error::NoId), + _ => Err(Error::OutOfMemory), + } +} + +/// Font weight constants (`FC_WEIGHT_*`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum Weight { + Bold = fc::FC_WEIGHT_BOLD, +} + +/// Font slant constants (`FC_SLANT_*`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum Slant { + Italic = fc::FC_SLANT_ITALIC, +} + +/// Font spacing constants (`FC_*` spacing). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum Spacing { + Mono = fc::FC_MONO, +} + +/// Pattern property names. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Property { + Family, + Style, + FullName, + File, + Index, + Charset, + Lang, + Size, + Weight, + Slant, + Spacing, +} + +impl Property { + fn cstr(self) -> &'static CStr { + match self { + Property::Family => fc::FC_FAMILY, + Property::Style => fc::FC_STYLE, + Property::FullName => fc::FC_FULLNAME, + Property::File => fc::FC_FILE, + Property::Index => fc::FC_INDEX, + Property::Charset => fc::FC_CHARSET, + Property::Lang => fc::FC_LANG, + Property::Size => fc::FC_SIZE, + Property::Weight => fc::FC_WEIGHT, + Property::Slant => fc::FC_SLANT, + Property::Spacing => fc::FC_SPACING, + } + } +} + +/// Initialize fontconfig (safe to call multiple times and concurrently). +pub fn init() -> bool { + // SAFETY: FcInit has no preconditions. + unsafe { sys::FcInit() != 0 } +} + +/// An owned `FcCharSet`. +pub struct CharSet(NonNull); + +impl CharSet { + pub fn create() -> CharSet { + // SAFETY: no preconditions; null (OOM) is handled. + let p = unsafe { sys::FcCharSetCreate() }; + CharSet(NonNull::new(p).expect("FcCharSetCreate out of memory")) + } + + pub fn add_char(&mut self, cp: u32) -> bool { + // SAFETY: self.0 is a valid, uniquely owned charset. + unsafe { sys::FcCharSetAddChar(self.0.as_ptr(), cp) != 0 } + } + + pub fn has_char(&self, cp: u32) -> bool { + // SAFETY: self.0 is a valid charset. + unsafe { sys::FcCharSetHasChar(self.0.as_ptr(), cp) != 0 } + } +} + +impl Drop for CharSet { + fn drop(&mut self) { + // SAFETY: we own the charset and destroy it once. + unsafe { sys::FcCharSetDestroy(self.0.as_ptr()) } + } +} + +/// An owned `FcLangSet`. +pub struct LangSet(NonNull); + +impl LangSet { + pub fn create() -> LangSet { + // SAFETY: no preconditions. + let p = unsafe { sys::FcLangSetCreate() }; + LangSet(NonNull::new(p).expect("FcLangSetCreate out of memory")) + } + + pub fn add_lang(&mut self, lang: &str) -> bool { + let Ok(c) = CString::new(lang) else { + return false; + }; + // SAFETY: valid langset and NUL-terminated string. + unsafe { sys::FcLangSetAdd(self.0.as_ptr(), c.as_ptr().cast()) != 0 } + } + + pub fn has_lang(&self, lang: &str) -> bool { + has_lang_raw(self.0.as_ptr(), lang) + } +} + +impl Drop for LangSet { + fn drop(&mut self) { + // SAFETY: we own the langset and destroy it once. + unsafe { sys::FcLangSetDestroy(self.0.as_ptr()) } + } +} + +fn has_lang_raw(ls: *const sys::FcLangSet, lang: &str) -> bool { + let Ok(c) = CString::new(lang) else { + return false; + }; + // SAFETY: `ls` is a valid langset (callers guarantee), string is NUL-terminated. + unsafe { sys::FcLangSetHasLang(ls, c.as_ptr().cast()) == sys::FcLangEqual } +} + +/// An owned `FcPattern`. +pub struct Pattern(NonNull); + +// SAFETY: patterns are never mutated after construction in this crate +// (only read through &self), and fontconfig patterns are plain data. +unsafe impl Send for Pattern {} +// SAFETY: see above; all &self methods are read-only. +unsafe impl Sync for Pattern {} + +impl Pattern { + pub fn create() -> Pattern { + // SAFETY: no preconditions. + let p = unsafe { sys::FcPatternCreate() }; + Pattern(NonNull::new(p).expect("FcPatternCreate out of memory")) + } + + fn as_ptr(&self) -> *mut sys::FcPattern { + self.0.as_ptr() + } + + pub fn add_string(&mut self, prop: Property, v: &str) -> bool { + let Ok(c) = CString::new(v) else { + return false; + }; + // SAFETY: valid pattern; property and value are NUL-terminated (value is copied). + unsafe { + sys::FcPatternAddString(self.as_ptr(), prop.cstr().as_ptr(), c.as_ptr().cast()) != 0 + } + } + + pub fn add_integer(&mut self, prop: Property, v: i32) -> bool { + // SAFETY: valid pattern, NUL-terminated property. + unsafe { sys::FcPatternAddInteger(self.as_ptr(), prop.cstr().as_ptr(), v) != 0 } + } + + pub fn add_charset(&mut self, prop: Property, cs: &CharSet) -> bool { + // SAFETY: valid pattern and charset (fontconfig takes its own reference). + unsafe { sys::FcPatternAddCharSet(self.as_ptr(), prop.cstr().as_ptr(), cs.0.as_ptr()) != 0 } + } + + /// Get the string value at index `id`. + pub fn get_string(&self, prop: Property, id: u32) -> Result { + let mut out: *mut sys::FcChar8 = ptr::null_mut(); + // SAFETY: valid pattern; out is a valid out pointer. + let r = unsafe { + sys::FcPatternGetString( + self.as_ptr(), + prop.cstr().as_ptr(), + id as c_int, + &raw mut out, + ) + }; + result_to_error(r)?; + if out.is_null() { + return Err(Error::NoMatch); + } + // SAFETY: fontconfig returned a NUL-terminated string owned by the pattern. + let s = unsafe { CStr::from_ptr(out.cast()) }; + Ok(s.to_string_lossy().into_owned()) + } + + pub fn get_integer(&self, prop: Property, id: u32) -> Result { + let mut out: c_int = 0; + // SAFETY: valid pattern; out is a valid out pointer. + let r = unsafe { + sys::FcPatternGetInteger( + self.as_ptr(), + prop.cstr().as_ptr(), + id as c_int, + &raw mut out, + ) + }; + result_to_error(r)?; + Ok(out) + } + + /// Returns whether `cp` is in the charset property (index 0). + pub fn charset_has_char(&self, cp: u32) -> Result { + let mut out: *mut sys::FcCharSet = ptr::null_mut(); + // SAFETY: valid pattern; out is a valid out pointer. + let r = unsafe { + sys::FcPatternGetCharSet( + self.as_ptr(), + Property::Charset.cstr().as_ptr(), + 0, + &raw mut out, + ) + }; + result_to_error(r)?; + if out.is_null() { + return Err(Error::NoMatch); + } + // SAFETY: the charset is owned by the pattern, which outlives this call. + Ok(unsafe { sys::FcCharSetHasChar(out, cp) != 0 }) + } + + /// Returns whether the langset property (index 0) equals `lang`. + pub fn langset_has_lang(&self, lang: &str) -> Result { + let mut out: *mut sys::FcLangSet = ptr::null_mut(); + // SAFETY: valid pattern; out is a valid out pointer. + let r = unsafe { + sys::FcPatternGetLangSet( + self.as_ptr(), + Property::Lang.cstr().as_ptr(), + 0, + &raw mut out, + ) + }; + result_to_error(r)?; + if out.is_null() { + return Err(Error::NoMatch); + } + Ok(has_lang_raw(out, lang)) + } + + pub fn default_substitute(&mut self) { + // SAFETY: valid pattern. + unsafe { sys::FcDefaultSubstitute(self.as_ptr()) } + } +} + +impl Drop for Pattern { + fn drop(&mut self) { + // SAFETY: we own the pattern reference and release it once. + unsafe { sys::FcPatternDestroy(self.as_ptr()) } + } +} + +/// An owned `FcFontSet` as returned by `FcFontSort`. +pub struct FontSet(NonNull); + +impl FontSet { + pub fn len(&self) -> usize { + // SAFETY: valid font set. + let n = unsafe { (*self.0.as_ptr()).nfont }; + usize::try_from(n).unwrap_or(0) + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Raw pattern pointer at `i`, borrowed from the set. + fn font(&self, i: usize) -> *mut sys::FcPattern { + assert!(i < self.len()); + // SAFETY: bounds checked; `fonts` has `nfont` entries. + unsafe { *(*self.0.as_ptr()).fonts.add(i) } + } +} + +impl Drop for FontSet { + fn drop(&mut self) { + // SAFETY: we own the set and destroy it once. + unsafe { sys::FcFontSetDestroy(self.0.as_ptr()) } + } +} + +// SAFETY: the set is only read after construction. +unsafe impl Send for FontSet {} +// SAFETY: see above. +unsafe impl Sync for FontSet {} + +/// An owned `FcConfig`. +pub struct Config(NonNull); + +// SAFETY: fontconfig configs are internally reference counted and +// thread safe for the read operations we perform. +unsafe impl Send for Config {} +// SAFETY: see above. +unsafe impl Sync for Config {} + +impl Config { + /// `FcInitLoadConfigAndFonts`. + pub fn init_load_config_and_fonts() -> Option { + // SAFETY: no preconditions. + NonNull::new(unsafe { sys::FcInitLoadConfigAndFonts() }).map(Config) + } + + /// `FcConfigSubstituteWithPat(config, pat, NULL, FcMatchPattern)`. + pub fn substitute_with_pat_pattern(&self, pat: &mut Pattern) -> bool { + // SAFETY: valid config and pattern; null pattern-for-match is allowed. + unsafe { + sys::FcConfigSubstituteWithPat( + self.0.as_ptr(), + pat.as_ptr(), + ptr::null_mut(), + sys::FcMatchPattern, + ) != 0 + } + } + + /// `FcFontSort`. Returns the sorted set when the result is a match. + pub fn font_sort(&self, pat: &mut Pattern, trim: bool) -> Result { + let mut result: sys::FcResult = 0; + // SAFETY: valid config/pattern; null charset out-pointer is allowed. + let fs = unsafe { + sys::FcFontSort( + self.0.as_ptr(), + pat.as_ptr(), + c_int::from(trim), + ptr::null_mut(), + &raw mut result, + ) + }; + let fs = NonNull::new(fs).map(FontSet); + if result != sys::FcResultMatch { + return Err(Error::FontconfigFailed); + } + fs.ok_or(Error::FontconfigFailed) + } + + /// `FcFontRenderPrepare(config, pat, set[i])`. + pub fn font_render_prepare( + &self, + pat: &mut Pattern, + set: &FontSet, + i: usize, + ) -> Result { + // SAFETY: valid config/pattern/font pattern; result is a new owned pattern. + let p = unsafe { sys::FcFontRenderPrepare(self.0.as_ptr(), pat.as_ptr(), set.font(i)) }; + NonNull::new(p).map(Pattern).ok_or(Error::FontconfigFailed) + } +} + +impl Drop for Config { + fn drop(&mut self) { + // SAFETY: we own the config reference and release it once. + unsafe { sys::FcConfigDestroy(self.0.as_ptr()) } + } +} + +/// Shared handle to a config. +pub type SharedConfig = Arc; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn langset_create() { + let fs = LangSet::create(); + assert!(!fs.has_lang("und-zsye")); + } + + #[test] + fn langset_has_lang_exact_match() { + let mut fs = LangSet::create(); + assert!(fs.add_lang("en-US")); + assert!(fs.has_lang("en-US")); + let mut emoji = LangSet::create(); + assert!(emoji.add_lang("und-zsye")); + assert!(emoji.has_lang("und-zsye")); + assert!(!emoji.has_lang("en-US")); + } + + #[test] + fn charset_add_has() { + let mut cs = CharSet::create(); + assert!(!cs.has_char('A' as u32)); + assert!(cs.add_char('A' as u32)); + assert!(cs.has_char('A' as u32)); + } + + #[test] + fn pattern_roundtrip() { + let mut p = Pattern::create(); + assert!(p.add_string(Property::Family, "Foo")); + assert!(p.add_integer(Property::Index, 3)); + assert_eq!("Foo", p.get_string(Property::Family, 0).unwrap()); + assert_eq!(3, p.get_integer(Property::Index, 0).unwrap()); + assert_eq!(Err(Error::NoMatch), p.get_string(Property::File, 0)); + } +} diff --git a/crates/ghostty-font/src/lib.rs b/crates/ghostty-font/src/lib.rs index ed5d362..361752a 100644 --- a/crates/ghostty-font/src/lib.rs +++ b/crates/ghostty-font/src/lib.rs @@ -1,14 +1,82 @@ -//! Font engine. Currently contains the metrics, atlas, glyph and sprite-font -//! modules; the rest of the font engine (faces, shaping, collections, ...) is -//! ported later. +//! Font engine: metrics, atlas, sprite font, faces (FreeType), discovery +//! (fontconfig), collections, codepoint resolution, shaping (HarfBuzz) and +//! the shared glyph grid. Port of `ZIG/src/font/` for the Linux backend +//! (`fontconfig_freetype`). #![cfg_attr(test, allow(clippy::unwrap_used))] pub mod atlas; +pub mod backend; +pub mod codepoint_map; +pub mod codepoint_resolver; +pub mod collection; +pub mod config_types; +pub mod deferred_face; +pub mod discovery; +pub mod embedded; +pub mod face; +pub mod fontconfig; pub mod glyph; +pub mod library; pub mod metrics; +pub mod nerd_font_attributes; +pub mod opentype; +pub mod shape; +pub mod shaper; +pub mod shared_grid; pub mod sprite; pub use atlas::Atlas; +pub use backend::{Backend, OPTIONS as options}; + +pub use codepoint_map::CodepointMap; +pub use codepoint_resolver::CodepointResolver; +pub use collection::Collection; +pub use deferred_face::DeferredFace; +pub use discovery::{Descriptor, Discover}; +pub use face::Face; pub use glyph::Glyph; +pub use library::Library; pub use metrics::Metrics; + +pub use shared_grid::SharedGrid; +pub use sprite::{Face as SpriteFace, Sprite}; + +/// The styles that a family can take. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(u8)] +pub enum Style { + Regular = 0, + Bold = 1, + Italic = 2, + BoldItalic = 3, +} + +impl Style { + /// All styles, in discriminant order (Zig: `std.meta.fields(Style)`). + pub const ALL: [Style; 4] = [ + Style::Regular, + Style::Bold, + Style::Italic, + Style::BoldItalic, + ]; +} + +/// The presentation for an emoji. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Presentation { + /// U+FE0E + Text = 0, + /// U+FE0F + Emoji = 1, +} + +/// A FontIndex that can be used to use the sprite font directly. +pub const SPRITE_INDEX: collection::Index = + collection::Index::init_special(collection::Special::Sprite); + +/// The default font size adjustment we use when loading fallback fonts. +/// +/// TODO: Add user configuration for this instead of hard-coding it. +pub const DEFAULT_FALLBACK_ADJUSTMENT: collection::SizeAdjustment = + collection::SizeAdjustment::IcWidth; diff --git a/crates/ghostty-font/src/library.rs b/crates/ghostty-font/src/library.rs new file mode 100644 index 0000000..83a4b0b --- /dev/null +++ b/crates/ghostty-font/src/library.rs @@ -0,0 +1,67 @@ +//! A library represents the shared state that the underlying font +//! library implementation(s) require per-process. Port of `font/library.zig` +//! (the `FreetypeLibrary` variant). + +use std::sync::{Arc, Mutex}; + +use crate::face::freetype::ft; + +#[derive(Debug, thiserror::Error)] +pub enum LibraryError { + #[error("freetype error {0}")] + Freetype(i32), +} + +/// Shared FreeType library state. Cheap to clone. +#[derive(Clone)] +pub struct Library { + pub(crate) inner: Arc, +} + +pub(crate) struct LibraryInner { + pub(crate) lib: ft::Lib, + + /// Mutex to be held any time the library is + /// being used to create or destroy a face. + pub(crate) mutex: Mutex<()>, +} + +impl Library { + pub fn init() -> Result { + let lib = ft::Lib::new().map_err(LibraryError::Freetype)?; + Ok(Library { + inner: Arc::new(LibraryInner { + lib, + mutex: Mutex::new(()), + }), + }) + } + + /// Lock the library mutex (poisoning is ignored: the guarded state is + /// only FreeType's own). + pub(crate) fn lock(&self) -> std::sync::MutexGuard<'_, ()> { + self.inner + .mutex + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } +} + +impl std::fmt::Debug for Library { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Library") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init_and_clone() { + let lib = Library::init().unwrap(); + let lib2 = lib.clone(); + drop(lib); + drop(lib2.lock()); + } +} diff --git a/crates/ghostty-font/src/nerd_font_attributes.rs b/crates/ghostty-font/src/nerd_font_attributes.rs new file mode 100644 index 0000000..8678185 --- /dev/null +++ b/crates/ghostty-font/src/nerd_font_attributes.rs @@ -0,0 +1,3091 @@ +//! This is a generated table, produced mechanically from +//! `font/nerd_font_attributes.zig` (itself generated by nerd_font_codegen.py). +//! +//! This file provides info extracted from the nerd fonts patcher script, +//! specifying the scaling/positioning attributes of various glyphs. + +#![allow(clippy::excessive_precision)] + +use crate::face::{Align, Constraint, ConstraintSize, Height}; + +/// Get the constraints for the provided codepoint. +pub fn get_constraint(cp: u32) -> Option { + let i = RANGES.partition_point(|r| r.1 < cp); + let r = RANGES.get(i)?; + if cp >= r.0 && cp <= r.1 { + Some(CONSTRAINTS[usize::from(r.2)]) + } else { + None + } +} + +/// Inclusive `(first, last, index into CONSTRAINTS)`, sorted and disjoint. +static RANGES: [(u32, u32, u16); 413] = [ + (0x23fb, 0x23fe, 164), + (0x2630, 0x2630, 0), + (0x2665, 0x2665, 164), + (0x26a1, 0x26a1, 164), + (0x276c, 0x276d, 1), + (0x276e, 0x276f, 2), + (0x2770, 0x2771, 3), + (0x2b58, 0x2b58, 164), + (0xe000, 0xe00a, 164), + (0xe0a0, 0xe0a3, 4), + (0xe0b0, 0xe0b0, 5), + (0xe0b1, 0xe0b1, 6), + (0xe0b2, 0xe0b2, 7), + (0xe0b3, 0xe0b3, 8), + (0xe0b4, 0xe0b4, 9), + (0xe0b5, 0xe0b5, 10), + (0xe0b6, 0xe0b6, 11), + (0xe0b7, 0xe0b7, 12), + (0xe0b8, 0xe0b8, 13), + (0xe0b9, 0xe0b9, 14), + (0xe0ba, 0xe0ba, 15), + (0xe0bb, 0xe0bb, 16), + (0xe0bc, 0xe0bc, 13), + (0xe0bd, 0xe0bd, 14), + (0xe0be, 0xe0be, 15), + (0xe0bf, 0xe0bf, 16), + (0xe0c0, 0xe0c0, 17), + (0xe0c1, 0xe0c1, 18), + (0xe0c2, 0xe0c2, 19), + (0xe0c3, 0xe0c3, 20), + (0xe0c4, 0xe0c4, 21), + (0xe0c5, 0xe0c5, 22), + (0xe0c6, 0xe0c6, 23), + (0xe0c7, 0xe0c7, 24), + (0xe0c8, 0xe0c8, 17), + (0xe0ca, 0xe0ca, 19), + (0xe0cc, 0xe0cc, 25), + (0xe0cd, 0xe0cd, 26), + (0xe0ce, 0xe0ce, 27), + (0xe0cf, 0xe0cf, 4), + (0xe0d0, 0xe0d1, 27), + (0xe0d2, 0xe0d2, 28), + (0xe0d4, 0xe0d4, 29), + (0xe0d6, 0xe0d6, 30), + (0xe0d7, 0xe0d7, 31), + (0xe200, 0xe2a9, 164), + (0xe300, 0xe300, 32), + (0xe301, 0xe301, 33), + (0xe302, 0xe302, 34), + (0xe303, 0xe303, 35), + (0xe304, 0xe304, 36), + (0xe305, 0xe305, 37), + (0xe306, 0xe306, 38), + (0xe307, 0xe307, 39), + (0xe308, 0xe308, 40), + (0xe309, 0xe309, 41), + (0xe30a, 0xe30a, 42), + (0xe30b, 0xe30b, 43), + (0xe30c, 0xe30c, 44), + (0xe30d, 0xe30d, 45), + (0xe30e, 0xe30e, 46), + (0xe30f, 0xe30f, 47), + (0xe310, 0xe310, 48), + (0xe311, 0xe311, 49), + (0xe312, 0xe312, 50), + (0xe313, 0xe313, 51), + (0xe314, 0xe314, 52), + (0xe315, 0xe315, 53), + (0xe316, 0xe316, 54), + (0xe317, 0xe317, 55), + (0xe318, 0xe318, 56), + (0xe319, 0xe319, 57), + (0xe31a, 0xe31a, 58), + (0xe31b, 0xe31b, 59), + (0xe31c, 0xe31c, 60), + (0xe31d, 0xe31d, 61), + (0xe31e, 0xe31e, 62), + (0xe31f, 0xe31f, 63), + (0xe320, 0xe320, 64), + (0xe321, 0xe321, 65), + (0xe322, 0xe322, 66), + (0xe323, 0xe323, 67), + (0xe324, 0xe324, 68), + (0xe325, 0xe325, 69), + (0xe326, 0xe326, 70), + (0xe327, 0xe327, 71), + (0xe328, 0xe328, 72), + (0xe329, 0xe329, 73), + (0xe32a, 0xe32a, 74), + (0xe32b, 0xe32b, 75), + (0xe32c, 0xe32c, 76), + (0xe32d, 0xe32d, 77), + (0xe32e, 0xe32e, 78), + (0xe32f, 0xe32f, 79), + (0xe330, 0xe330, 80), + (0xe331, 0xe331, 81), + (0xe332, 0xe332, 82), + (0xe333, 0xe333, 83), + (0xe334, 0xe334, 84), + (0xe335, 0xe335, 85), + (0xe336, 0xe336, 86), + (0xe337, 0xe337, 87), + (0xe338, 0xe338, 88), + (0xe339, 0xe339, 89), + (0xe33a, 0xe33a, 90), + (0xe33b, 0xe33b, 91), + (0xe33c, 0xe33d, 92), + (0xe33e, 0xe33e, 93), + (0xe33f, 0xe33f, 94), + (0xe340, 0xe340, 95), + (0xe341, 0xe341, 96), + (0xe342, 0xe343, 164), + (0xe344, 0xe344, 97), + (0xe345, 0xe345, 98), + (0xe346, 0xe346, 164), + (0xe347, 0xe347, 99), + (0xe348, 0xe348, 164), + (0xe349, 0xe349, 100), + (0xe34a, 0xe34b, 164), + (0xe34c, 0xe34c, 101), + (0xe34d, 0xe34d, 102), + (0xe34e, 0xe34e, 164), + (0xe34f, 0xe34f, 103), + (0xe350, 0xe350, 164), + (0xe351, 0xe351, 104), + (0xe352, 0xe352, 105), + (0xe353, 0xe353, 106), + (0xe354, 0xe356, 107), + (0xe357, 0xe357, 108), + (0xe358, 0xe359, 107), + (0xe35a, 0xe35a, 109), + (0xe35b, 0xe35b, 110), + (0xe35c, 0xe35d, 164), + (0xe35e, 0xe35e, 58), + (0xe35f, 0xe35f, 42), + (0xe360, 0xe360, 111), + (0xe361, 0xe361, 71), + (0xe362, 0xe362, 112), + (0xe363, 0xe363, 113), + (0xe364, 0xe364, 114), + (0xe365, 0xe365, 46), + (0xe366, 0xe366, 115), + (0xe367, 0xe367, 73), + (0xe368, 0xe368, 164), + (0xe369, 0xe369, 116), + (0xe36a, 0xe36a, 164), + (0xe36b, 0xe36b, 117), + (0xe36c, 0xe36c, 118), + (0xe36d, 0xe36d, 119), + (0xe36e, 0xe36e, 120), + (0xe36f, 0xe36f, 121), + (0xe370, 0xe370, 122), + (0xe371, 0xe371, 123), + (0xe372, 0xe372, 124), + (0xe373, 0xe373, 125), + (0xe374, 0xe374, 126), + (0xe375, 0xe375, 127), + (0xe376, 0xe376, 128), + (0xe377, 0xe377, 129), + (0xe378, 0xe378, 130), + (0xe379, 0xe379, 131), + (0xe37a, 0xe37a, 132), + (0xe37b, 0xe37b, 133), + (0xe37c, 0xe37c, 164), + (0xe37d, 0xe37d, 134), + (0xe37e, 0xe37e, 135), + (0xe37f, 0xe37f, 136), + (0xe380, 0xe380, 137), + (0xe381, 0xe38d, 164), + (0xe38e, 0xe391, 138), + (0xe392, 0xe393, 139), + (0xe394, 0xe394, 138), + (0xe395, 0xe395, 140), + (0xe396, 0xe396, 141), + (0xe397, 0xe397, 142), + (0xe398, 0xe398, 143), + (0xe399, 0xe399, 144), + (0xe39a, 0xe39a, 145), + (0xe39b, 0xe39b, 146), + (0xe39c, 0xe39c, 147), + (0xe39d, 0xe39d, 148), + (0xe39e, 0xe39e, 149), + (0xe39f, 0xe39f, 150), + (0xe3a0, 0xe3a0, 151), + (0xe3a1, 0xe3a1, 152), + (0xe3a2, 0xe3a3, 153), + (0xe3a4, 0xe3a4, 154), + (0xe3a5, 0xe3a5, 153), + (0xe3a6, 0xe3a6, 154), + (0xe3a7, 0xe3a8, 153), + (0xe3a9, 0xe3a9, 108), + (0xe3aa, 0xe3aa, 155), + (0xe3ab, 0xe3ab, 156), + (0xe3ac, 0xe3ac, 157), + (0xe3ad, 0xe3ad, 158), + (0xe3ae, 0xe3ae, 159), + (0xe3af, 0xe3af, 160), + (0xe3b0, 0xe3b2, 161), + (0xe3b3, 0xe3b3, 160), + (0xe3b4, 0xe3b4, 164), + (0xe3b5, 0xe3bb, 160), + (0xe3bc, 0xe3c0, 164), + (0xe3c1, 0xe3c1, 162), + (0xe3c2, 0xe3c2, 163), + (0xe3c3, 0xe3e3, 164), + (0xe5fa, 0xe6b8, 164), + (0xe700, 0xe8ef, 164), + (0xea60, 0xea60, 164), + (0xea61, 0xea61, 165), + (0xea62, 0xea7c, 164), + (0xea7d, 0xea7d, 166), + (0xea7e, 0xea88, 164), + (0xea8a, 0xea8c, 164), + (0xea8f, 0xea98, 164), + (0xea99, 0xea99, 167), + (0xea9a, 0xea9a, 168), + (0xea9b, 0xea9b, 169), + (0xea9c, 0xea9c, 170), + (0xea9d, 0xea9d, 171), + (0xea9e, 0xea9f, 172), + (0xeaa0, 0xeaa0, 171), + (0xeaa1, 0xeaa1, 168), + (0xeaa2, 0xeaa2, 173), + (0xeaa3, 0xeab3, 164), + (0xeab4, 0xeab4, 174), + (0xeab5, 0xeab5, 175), + (0xeab6, 0xeab6, 176), + (0xeab7, 0xeab7, 177), + (0xeab8, 0xeac7, 164), + (0xeac9, 0xeac9, 164), + (0xeacc, 0xead3, 164), + (0xead4, 0xead5, 178), + (0xead6, 0xead6, 179), + (0xead7, 0xeb09, 164), + (0xeb0b, 0xeb42, 164), + (0xeb43, 0xeb43, 180), + (0xeb44, 0xeb4e, 164), + (0xeb50, 0xeb6d, 164), + (0xeb6e, 0xeb6e, 181), + (0xeb6f, 0xeb6f, 182), + (0xeb70, 0xeb70, 183), + (0xeb71, 0xeb71, 181), + (0xeb72, 0xeb89, 164), + (0xeb8a, 0xeb8a, 184), + (0xeb8b, 0xeb99, 164), + (0xeb9a, 0xeb9a, 185), + (0xeb9b, 0xebd4, 164), + (0xebd5, 0xebd5, 186), + (0xebd6, 0xebd6, 187), + (0xebd7, 0xec06, 164), + (0xec07, 0xec07, 188), + (0xec08, 0xec0a, 164), + (0xec0b, 0xec0b, 189), + (0xec0c, 0xec0c, 190), + (0xec0d, 0xec1e, 164), + (0xed00, 0xedff, 164), + (0xee00, 0xee00, 191), + (0xee01, 0xee01, 192), + (0xee02, 0xee02, 193), + (0xee03, 0xee03, 191), + (0xee04, 0xee04, 192), + (0xee05, 0xee05, 193), + (0xee06, 0xee06, 194), + (0xee07, 0xee07, 195), + (0xee08, 0xee08, 196), + (0xee09, 0xee09, 197), + (0xee0a, 0xee0a, 198), + (0xee0b, 0xee0b, 199), + (0xee0c, 0xefce, 164), + (0xf000, 0xf004, 164), + (0xf005, 0xf005, 200), + (0xf006, 0xf025, 164), + (0xf026, 0xf027, 201), + (0xf028, 0xf02a, 164), + (0xf02b, 0xf02b, 202), + (0xf02c, 0xf030, 164), + (0xf031, 0xf033, 203), + (0xf034, 0xf034, 164), + (0xf035, 0xf035, 204), + (0xf036, 0xf043, 164), + (0xf044, 0xf044, 205), + (0xf045, 0xf045, 164), + (0xf046, 0xf046, 206), + (0xf047, 0xf047, 164), + (0xf048, 0xf048, 207), + (0xf049, 0xf049, 208), + (0xf04a, 0xf04a, 207), + (0xf04b, 0xf04b, 209), + (0xf04c, 0xf04d, 210), + (0xf04e, 0xf04e, 207), + (0xf04f, 0xf04f, 211), + (0xf050, 0xf050, 208), + (0xf051, 0xf051, 207), + (0xf052, 0xf052, 212), + (0xf053, 0xf05f, 164), + (0xf060, 0xf061, 213), + (0xf062, 0xf062, 164), + (0xf063, 0xf063, 214), + (0xf064, 0xf076, 164), + (0xf077, 0xf077, 215), + (0xf078, 0xf078, 216), + (0xf079, 0xf07d, 164), + (0xf07e, 0xf07e, 217), + (0xf07f, 0xf088, 164), + (0xf089, 0xf089, 218), + (0xf08a, 0xf0a3, 164), + (0xf0a4, 0xf0a5, 219), + (0xf0a6, 0xf0d6, 164), + (0xf0d7, 0xf0d7, 220), + (0xf0d8, 0xf0d8, 221), + (0xf0d9, 0xf0d9, 222), + (0xf0da, 0xf0da, 223), + (0xf0db, 0xf0db, 164), + (0xf0dc, 0xf0dc, 224), + (0xf0dd, 0xf0dd, 225), + (0xf0de, 0xf0de, 226), + (0xf0df, 0xf0ff, 164), + (0xf100, 0xf101, 227), + (0xf102, 0xf102, 228), + (0xf103, 0xf103, 229), + (0xf104, 0xf105, 227), + (0xf106, 0xf107, 230), + (0xf108, 0xf12f, 164), + (0xf130, 0xf130, 231), + (0xf131, 0xf140, 164), + (0xf141, 0xf141, 232), + (0xf142, 0xf152, 164), + (0xf153, 0xf154, 206), + (0xf155, 0xf155, 164), + (0xf156, 0xf156, 233), + (0xf157, 0xf157, 234), + (0xf158, 0xf158, 206), + (0xf159, 0xf159, 235), + (0xf15a, 0xf174, 164), + (0xf175, 0xf175, 236), + (0xf176, 0xf176, 164), + (0xf177, 0xf178, 237), + (0xf179, 0xf181, 164), + (0xf182, 0xf182, 238), + (0xf183, 0xf220, 164), + (0xf221, 0xf221, 239), + (0xf222, 0xf222, 240), + (0xf223, 0xf223, 164), + (0xf224, 0xf226, 239), + (0xf227, 0xf227, 240), + (0xf228, 0xf228, 239), + (0xf229, 0xf229, 241), + (0xf22a, 0xf22a, 239), + (0xf22b, 0xf22b, 242), + (0xf22c, 0xf22c, 239), + (0xf22d, 0xf22d, 242), + (0xf22e, 0xf254, 164), + (0xf255, 0xf256, 243), + (0xf257, 0xf257, 244), + (0xf258, 0xf258, 245), + (0xf259, 0xf259, 164), + (0xf25a, 0xf25a, 243), + (0xf25b, 0xf25b, 246), + (0xf25c, 0xf381, 164), + (0xf400, 0xf415, 164), + (0xf416, 0xf416, 247), + (0xf417, 0xf423, 164), + (0xf424, 0xf424, 248), + (0xf425, 0xf430, 164), + (0xf431, 0xf431, 249), + (0xf432, 0xf432, 250), + (0xf433, 0xf433, 251), + (0xf434, 0xf434, 252), + (0xf435, 0xf437, 164), + (0xf438, 0xf438, 253), + (0xf439, 0xf43d, 164), + (0xf43e, 0xf43e, 254), + (0xf43f, 0xf442, 164), + (0xf443, 0xf443, 255), + (0xf444, 0xf445, 256), + (0xf446, 0xf449, 164), + (0xf44a, 0xf44a, 257), + (0xf44b, 0xf44b, 258), + (0xf44c, 0xf45b, 164), + (0xf45c, 0xf45c, 259), + (0xf45d, 0xf45f, 164), + (0xf460, 0xf460, 260), + (0xf461, 0xf461, 261), + (0xf462, 0xf466, 164), + (0xf467, 0xf467, 262), + (0xf468, 0xf46b, 164), + (0xf46c, 0xf46c, 263), + (0xf46d, 0xf46f, 164), + (0xf470, 0xf470, 264), + (0xf471, 0xf475, 164), + (0xf476, 0xf476, 265), + (0xf477, 0xf479, 164), + (0xf47a, 0xf47a, 266), + (0xf47b, 0xf47c, 267), + (0xf47d, 0xf47d, 268), + (0xf47e, 0xf47e, 269), + (0xf47f, 0xf48a, 164), + (0xf48b, 0xf48b, 270), + (0xf48c, 0xf492, 164), + (0xf493, 0xf493, 271), + (0xf494, 0xf499, 164), + (0xf49a, 0xf49a, 272), + (0xf49b, 0xf4c2, 164), + (0xf4c3, 0xf4c3, 256), + (0xf4c4, 0xf4ee, 164), + (0xf4ef, 0xf4ef, 273), + (0xf4f0, 0xf4f0, 274), + (0xf4f1, 0xf4f1, 275), + (0xf4f2, 0xf4f2, 273), + (0xf4f3, 0xf51c, 164), + (0xf51d, 0xf51d, 256), + (0xf51e, 0xf533, 164), + (0xf0001, 0xf1af0, 164), +]; + +static CONSTRAINTS: [Constraint; 276] = [ + Constraint { + size: ConstraintSize::Cover, + height: Height::Icon, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + pad_left: 0.05, + pad_right: 0.05, + pad_top: 0.05, + pad_bottom: 0.05, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7142857142857143, + relative_height: 0.8910614525139665, + relative_x: 0.1428571428571428, + relative_y: 0.0349162011173184, + pad_top: 0.15, + pad_bottom: 0.15, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9885714285714285, + relative_height: 0.8910614525139665, + relative_x: 0.0057142857142857, + relative_y: 0.0125698324022346, + pad_top: 0.15, + pad_bottom: 0.15, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + pad_top: 0.15, + pad_bottom: 0.15, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.03, + pad_right: -0.03, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.03, + pad_right: -0.03, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.03, + pad_right: -0.03, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.59), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + max_xy_ratio: Some(0.5), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.03, + pad_right: -0.03, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.59), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + max_xy_ratio: Some(0.5), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::End, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + max_xy_ratio: Some(0.86), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + max_xy_ratio: Some(0.86), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + max_xy_ratio: Some(0.78), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + max_xy_ratio: Some(0.78), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.01, + pad_right: -0.01, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.85), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + max_xy_ratio: Some(0.865), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.01, + pad_right: -0.01, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.01, + pad_right: -0.01, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + max_xy_ratio: Some(0.7), + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8984375000000000, + relative_y: 0.0986328125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8798828125000000, + relative_y: 0.1171875000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7646484375000000, + relative_y: 0.2314453125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8789062500000000, + relative_y: 0.1171875000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9755859375000000, + relative_y: 0.0244140625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9960937500000000, + relative_y: 0.0019531250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9863281250000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9951171875000000, + relative_y: 0.0039062500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9785156250000000, + relative_y: 0.0195312500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9736328125000000, + relative_y: 0.0214843750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9648437500000000, + relative_y: 0.0302734375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8437500000000000, + relative_y: 0.1513671875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8027343750000000, + relative_y: 0.1835937500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7753906250000000, + relative_y: 0.1083984375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9833984375000000, + relative_y: 0.0166015625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9716796875000000, + relative_y: 0.0263671875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6621093750000000, + relative_y: 0.0986328125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6425781250000000, + relative_y: 0.1171875000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5322265625000000, + relative_y: 0.2314453125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6416015625000000, + relative_y: 0.1181640625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7382812500000000, + relative_y: 0.0195312500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6787109375000000, + relative_y: 0.1357421875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7480468750000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7529296875000000, + relative_y: 0.0048828125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7314453125000000, + relative_y: 0.0263671875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7402343750000000, + relative_y: 0.0195312500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7294921875000000, + relative_y: 0.0283203125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6074218750000000, + relative_y: 0.1503906250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7363281250000000, + relative_y: 0.0224609375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7460937500000000, + relative_y: 0.0126953125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.2675781250000000, + relative_y: 0.3310546875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7363281250000000, + relative_y: 0.0986328125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7177734375000000, + relative_y: 0.1171875000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8085937500000000, + relative_y: 0.0253906250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7509765625000000, + relative_y: 0.0839843750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8281250000000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8349609375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8154296875000000, + relative_y: 0.0214843750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8144531250000000, + relative_y: 0.0195312500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8076171875000000, + relative_y: 0.0273437500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6845703125000000, + relative_y: 0.1503906250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8173828125000000, + relative_y: 0.0175781250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8105468750000000, + relative_y: 0.0263671875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5175781250000000, + relative_y: 0.2421875000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6992187500000000, + relative_y: 0.1005859375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6787109375000000, + relative_y: 0.1201171875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5654296875000000, + relative_y: 0.2324218750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7714843750000000, + relative_y: 0.0273437500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7148437500000000, + relative_y: 0.0830078125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7919921875000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7871093750000000, + relative_y: 0.0126953125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7714843750000000, + relative_y: 0.0263671875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7773437500000000, + relative_y: 0.0195312500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7714843750000000, + relative_y: 0.0283203125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6503906250000000, + relative_y: 0.1503906250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7753906250000000, + relative_y: 0.0234375000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7792968750000000, + relative_y: 0.0185546875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8445945945945946, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5283203125000000, + relative_y: 0.2324218750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5449218750000000, + relative_y: 0.2148437500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5273437500000000, + relative_y: 0.2324218750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.3293918918918919, + relative_y: 0.6706081081081081, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5200000000000000, + relative_y: 0.2707692307692308, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8307692307692308, + relative_y: 0.0861538461538462, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8327702702702703, + relative_y: 0.0050675675675676, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5307692307692308, + relative_y: 0.2092307692307692, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5332112630208333, + relative_y: 0.2040934244791667, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8307692307692308, + relative_y: 0.1246153846153846, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5307967032967034, + relative_y: 0.2615384615384616, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8659995118379302, + relative_y: 0.1340004881620698, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9890163534293386, + relative_y: 0.0002440810349036, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5751953125000000, + relative_y: 0.1142578125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6533203125000000, + relative_y: 0.1328125000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5215384615384615, + relative_y: 0.2846153846153846, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8308012820512821, + relative_y: 0.1230448717948718, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9935233160621761, + relative_y: 0.0025906735751295, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9961139896373057, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9935233160621761, + relative_y: 0.0012953367875648, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9987046632124352, + relative_y: 0.0012953367875648, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7695312500000000, + relative_y: 0.0302734375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9902343750000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7900390625000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8251953125000000, + relative_y: 0.0097656250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7832031250000000, + relative_y: 0.0166015625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4902343750000000, + relative_y: 0.2548828125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9333658774713205, + relative_y: 0.0266048328044911, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7076171875000000, + relative_y: 0.1083984375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8427734375000000, + relative_y: 0.0625000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7529721467391304, + relative_y: 0.0956606657608696, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6835937500000000, + relative_y: 0.1250000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8642578125000000, + relative_y: 0.0625000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6103515625000000, + relative_y: 0.1933593750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7949218750000000, + relative_y: 0.0576171875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8652343750000000, + relative_y: 0.0058593750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.3154296875000000, + relative_y: 0.2861328125000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6772460937500000, + relative_y: 0.1303710937500000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6992187500000000, + relative_y: 0.1337890625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7314453125000000, + relative_y: 0.1552734375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7314453125000000, + relative_y: 0.1542968750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5751953125000000, + relative_y: 0.1826171875000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5263671875000000, + relative_y: 0.2285156250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5751953125000000, + relative_y: 0.1835937500000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9003906250000000, + relative_y: 0.0957031250000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6015625000000000, + relative_y: 0.2324218750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5200000000000000, + relative_y: 0.2784615384615385, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5200000000000000, + relative_y: 0.2630769230769231, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4990253411306043, + relative_height: 0.9987012987012988, + relative_x: 0.4996751137102014, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4996751137102014, + relative_height: 0.9987012987012988, + relative_x: 0.4990253411306043, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5471085120207927, + relative_height: 0.9987012987012988, + relative_x: 0.4515919428200130, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5945419103313840, + relative_height: 0.9987012987012988, + relative_x: 0.4041585445094217, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6426250812215725, + relative_x: 0.3573749187784275, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6900584795321637, + relative_x: 0.3099415204678362, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7381416504223521, + relative_x: 0.2618583495776478, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7855750487329435, + relative_x: 0.2144249512670565, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9987004548408057, + relative_height: 0.9987012987012988, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8323586744639376, + relative_height: 0.9935064935064936, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7855750487329435, + relative_height: 0.9948051948051948, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7381416504223521, + relative_height: 0.9961038961038962, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6907082521117609, + relative_height: 0.9961038961038962, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6426250812215725, + relative_height: 0.9961038961038962, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5945419103313840, + relative_height: 0.9974025974025974, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4990253411306043, + relative_height: 0.9987012987012988, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4996751137102014, + relative_height: 0.9987012987012988, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9902343750000000, + relative_y: 0.0078125000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7900390625000000, + relative_y: 0.0058593750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8251953125000000, + relative_y: 0.0078125000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7519531250000000, + relative_y: 0.0068359375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6152343750000000, + relative_y: 0.2324218750000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9986072423398329, + relative_y: 0.0013927576601671, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9958217270194986, + relative_y: 0.0041782729805014, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6590187942396876, + relative_y: 0.1349768123016842, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7939956065413717, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7513020833333334, + relative_height: 0.9291573452647278, + relative_x: 0.0846354166666667, + relative_y: 0.0708426547352722, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8394854586129754, + relative_height: 0.8751387347391787, + relative_x: 0.0917225950782998, + relative_y: 0.0416204217536071, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9395973154362416, + relative_height: 0.4778024417314096, + relative_x: 0.0302013422818792, + relative_y: 0.2269700332963374, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7673378076062640, + relative_height: 0.8523862375138734, + relative_x: 0.1526845637583893, + relative_y: 0.0754716981132075, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8590604026845637, + relative_height: 0.7613762486126526, + relative_x: 0.0721476510067114, + relative_y: 0.0871254162042175, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8590604026845637, + relative_height: 0.7574916759156493, + relative_x: 0.0721476510067114, + relative_y: 0.0832408435072142, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4082774049217002, + relative_height: 0.5077691453940066, + relative_x: 0.2863534675615212, + relative_y: 0.2763596004439512, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5117449664429530, + relative_height: 0.4051054384017758, + relative_x: 0.2136465324384788, + relative_y: 0.3068812430632630, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8061116965226555, + relative_height: 0.9438247156716689, + relative_x: 0.0679662802950474, + relative_y: 0.0147523709167545, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9945482866043613, + relative_height: 0.5264797507788161, + relative_y: 0.2024922118380062, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5264797507788161, + relative_height: 0.9945482866043613, + relative_x: 0.2024922118380062, + relative_y: 0.0054517133956386, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5264797507788161, + relative_height: 0.9945482866043613, + relative_x: 0.2710280373831775, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9945482866043613, + relative_height: 0.5264797507788161, + relative_x: 0.0054517133956386, + relative_y: 0.2710280373831775, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7069825436408977, + relative_x: 0.1483790523690773, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8780760626398211, + relative_y: 0.0687919463087248, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7335766423357665, + relative_height: 0.9996188152778837, + relative_x: 0.1991657977059437, + relative_y: 0.0003811847221163, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4954604409857328, + relative_y: 0.2522697795071336, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4973958333333333, + relative_x: 0.2493489583333333, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4973958333333333, + relative_height: 0.9961089494163424, + relative_x: 0.2493489583333333, + relative_y: 0.0038910505836576, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.3468834688346883, + relative_height: 0.3353615785256410, + relative_x: 0.2642276422764228, + relative_y: 0.3313050881410256, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8740779768177028, + relative_height: 0.9438247156716689, + relative_x: 0.0679662802950474, + relative_y: 0.0147523709167545, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9322210636079249, + relative_height: 0.9318897917604415, + relative_y: 0.0681102082395584, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9996446423917936, + relative_y: 0.0003553576082064, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.3495911047345768, + relative_height: 0.3355179398148149, + relative_x: 0.2615335565120357, + relative_y: 0.3311487268518519, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9327424400417101, + relative_height: 0.9996188152778837, + relative_y: 0.0003811847221163, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8008342022940563, + relative_x: 0.1991657977059437, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::End, + align_vertical: Align::Center1, + relative_width: 0.8681172291296625, + relative_height: 0.8626692456479691, + relative_x: 0.1314387211367673, + relative_y: 0.0686653771760155, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8626692456479691, + relative_y: 0.0686653771760155, + pad_left: -0.05, + pad_right: -0.05, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Stretch, + max_constraint_width: 1, + align_horizontal: Align::Start, + align_vertical: Align::Center1, + relative_width: 0.8685612788632326, + relative_height: 0.8626692456479691, + relative_y: 0.0686653771760155, + pad_left: -0.025, + pad_right: -0.025, + pad_top: -0.005, + pad_bottom: -0.005, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7059415911379657, + relative_height: 0.2234524408656266, + relative_x: 0.1470292044310171, + relative_y: 0.7765475591343735, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5000000000000000, + relative_height: 0.7498741821841973, + relative_x: 0.5000000000000000, + relative_y: 0.2501258178158027, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6299093655589124, + relative_height: 0.8535480624056366, + relative_x: 0.3700906344410876, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4997483643683945, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6299093655589124, + relative_height: 0.8535480624056366, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::Cover, + max_constraint_width: 1, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5000000000000000, + relative_height: 0.7498741821841973, + relative_y: 0.2501258178158027, + pad_left: 0.015, + pad_right: 0.015, + pad_top: 0.015, + pad_bottom: 0.015, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9999664113932554, + relative_y: 0.0000335886067446, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9786184354605580, + relative_y: 0.0103951316192896, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9758052740827267, + relative_y: 0.0238869355863696, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9987922705314010, + relative_y: 0.0006038647342995, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9989935587761675, + relative_y: 0.0004025764895330, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9925925925925926, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8751322751322751, + relative_y: 0.0624338624338624, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8577706898990622, + relative_y: 0.0711892586341537, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8579450878868969, + relative_y: 0.0710148606463189, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9997041418532618, + relative_y: 0.0002958581467381, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8572940020656472, + relative_y: 0.0713404035569438, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7138835298072554, + relative_y: 0.1433479295317200, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9999748091795350, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8567975830815709, + relative_y: 0.0719033232628399, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9987915407854985, + relative_y: 0.0006042296072508, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5700483091787439, + relative_y: 0.2862318840579710, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5700483091787439, + relative_y: 0.1437198067632850, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4989429175475687, + relative_y: 0.2505285412262157, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9998488512696494, + relative_y: 0.0001511487303507, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7502645502645503, + relative_y: 0.1248677248677249, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4281400966183575, + relative_y: 0.2053140096618357, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.4281400966183575, + relative_y: 0.3472222222222222, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7140772371750631, + relative_y: 0.1333462732919255, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7140396210163651, + relative_y: 0.1333838894506235, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + relative_height: 0.4275362318840580, + relative_y: 0.0012077294685990, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + relative_height: 0.4287439613526570, + relative_y: 0.5712560386473430, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8573155985489722, + relative_y: 0.0713422007255139, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9286577992744861, + relative_y: 0.0713422007255139, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9286577992744861, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.5000000000000000, + relative_y: 0.2853688029020556, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9998602571268865, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.2593984962406015, + relative_y: 0.3696741854636592, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8752505446623093, + relative_y: 0.0623155929038282, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8756468797564688, + relative_y: 0.0624338624338624, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8756067947646895, + relative_y: 0.0623492063492063, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9989423585404548, + relative_y: 0.0005288207297726, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6250661025912215, + relative_y: 0.1877313590692755, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9998046921689268, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9994854643684076, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.8746819883943630, + relative_y: 0.0624017379870223, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9370837263813853, + relative_y: 0.0624017379870223, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6874767744332962, + relative_y: 0.1560043449675557, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9993997599039616, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7810124049619848, + relative_y: 0.0935945806894186, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.7498142113988452, + relative_y: 0.1247927742525582, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.9975006099019084, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_height: 0.6090604026845637, + relative_y: 0.2119686800894855, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5019531250000000, + relative_height: 0.5755033557046980, + relative_x: 0.2480468750000000, + relative_y: 0.2108501118568233, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6240234375000000, + relative_height: 0.7695749440715883, + relative_x: 0.2031250000000000, + relative_y: 0.1420581655480984, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6718750000000000, + relative_height: 0.7147651006711410, + relative_x: 0.1875000000000000, + relative_y: 0.1610738255033557, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6240234375000000, + relative_height: 0.7695749440715883, + relative_x: 0.2041015625000000, + relative_y: 0.0883668903803132, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6718750000000000, + relative_height: 0.7147651006711410, + relative_x: 0.1406250000000000, + relative_y: 0.1599552572706935, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.2436523437500000, + relative_height: 0.4560546875000000, + relative_x: 0.3813476562500000, + relative_y: 0.2719726562500000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5029296875000000, + relative_height: 0.5755033557046980, + relative_x: 0.2500000000000000, + relative_y: 0.2136465324384788, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7500000000000000, + relative_x: 0.1250000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5000000000000000, + relative_height: 0.5000000000000000, + relative_x: 0.2500000000000000, + relative_y: 0.2500000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.2436523437500000, + relative_height: 0.4560546875000000, + relative_x: 0.3750000000000000, + relative_y: 0.2719726562500000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4560546875000000, + relative_height: 0.2436523437500000, + relative_x: 0.2719726562500000, + relative_y: 0.3188476562500000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5019531250000000, + relative_height: 0.5749440715883669, + relative_x: 0.2480468750000000, + relative_y: 0.2114093959731544, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.3593750000000000, + relative_height: 0.6240234375000000, + relative_x: 0.3750000000000000, + relative_y: 0.1884765625000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6237816764132553, + relative_height: 0.9988851727982163, + relative_x: 0.1881091617933723, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5639648437500000, + relative_height: 0.5649414062500000, + relative_x: 0.2187500000000000, + relative_y: 0.2177734375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5039062500000000, + relative_height: 0.5771812080536913, + relative_x: 0.2490234375000000, + relative_y: 0.2091722595078300, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9926757812500000, + relative_height: 0.2690429687500000, + relative_y: 0.6865234375000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8732325694783033, + relative_x: 0.0633837152608484, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.5843079922027290, + relative_height: 0.9509476031215162, + relative_x: 0.2066276803118908, + relative_y: 0.0234113712374582, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.6250000000000000, + relative_height: 0.3593750000000000, + relative_x: 0.1875000000000000, + relative_y: 0.3281250000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.3593750000000000, + relative_height: 0.6240234375000000, + relative_x: 0.2656250000000000, + relative_y: 0.1875000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.4560546875000000, + relative_height: 0.2436523437500000, + relative_x: 0.2719726562500000, + relative_y: 0.3750000000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7187500000000000, + relative_height: 0.0937500000000000, + relative_x: 0.1250000000000000, + relative_y: 0.4687500000000000, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8313840155945419, + relative_height: 0.9509476031215162, + relative_x: 0.0843079922027290, + relative_y: 0.0234113712374582, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.8727450024378351, + relative_x: 0.0633837152608484, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.7142857142857143, + relative_x: 0.1428571428571428, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9642857142857143, + relative_height: 0.7407407407407407, + relative_y: 0.1111111111111111, + ..Constraint::NONE + }, + Constraint { + size: ConstraintSize::FitCover1, + height: Height::Icon, + align_horizontal: Align::Center1, + align_vertical: Align::Center1, + relative_width: 0.9642857142857143, + relative_height: 0.7407407407407407, + relative_x: 0.0357142857142857, + relative_y: 0.1111111111111111, + ..Constraint::NONE + }, +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn table_is_sorted_and_disjoint() { + for w in RANGES.windows(2) { + assert!(w[0].1 < w[1].0); + } + for r in &RANGES { + assert!(r.0 <= r.1); + assert!(usize::from(r.2) < CONSTRAINTS.len()); + } + } + + #[test] + fn lookups() { + let c = get_constraint(0xea61).unwrap(); + assert_eq!(c.size, ConstraintSize::FitCover1); + assert!(get_constraint(0x41).is_none()); + assert!(get_constraint(0x2630).is_some()); + assert!(get_constraint(0x276d).is_some()); + assert!(get_constraint(0x276e - 0x100).is_none()); + assert!(get_constraint(0xf4f1).is_some()); + assert!(get_constraint(0xf4f3).is_some()); + } +} diff --git a/crates/ghostty-font/src/opentype/head.rs b/crates/ghostty-font/src/opentype/head.rs new file mode 100644 index 0000000..16579c8 --- /dev/null +++ b/crates/ghostty-font/src/opentype/head.rs @@ -0,0 +1,121 @@ +//! Font Header Table (`head`). Port of `font/opentype/head.zig`. +//! +//! References: +//! - https://learn.microsoft.com/en-us/typography/opentype/spec/head +//! +//! Field names are in camelCase to match names in the spec. +#![allow(non_snake_case)] + +use super::sfnt::Fixed; +use super::{EndOfStream, Reader}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Head { + /// Major version number of the font header table — set to 1. + pub majorVersion: u16, + /// Minor version number of the font header table — set to 0. + pub minorVersion: u16, + /// Set by font manufacturer. + pub fontRevision: Fixed, + /// To compute: set it to 0, sum the entire font as uint32, then store + /// 0xB1B0AFBA - sum. + pub checksumAdjustment: u32, + /// Set to 0x5F0F3CF5. + pub magicNumber: u32, + /// Bit flags (see the OpenType spec for bit meanings). + pub flags: u16, + /// Set to a value from 16 to 16384. + pub unitsPerEm: u16, + /// Seconds since 12:00 midnight, January 1st, 1904, UTC. + pub created: i64, + /// Seconds since 12:00 midnight, January 1st, 1904, UTC. + pub modified: i64, + /// Minimum x coordinate across all glyph bounding boxes. + pub xMin: i16, + /// Minimum y coordinate across all glyph bounding boxes. + pub yMin: i16, + /// Maximum x coordinate across all glyph bounding boxes. + pub xMax: i16, + /// Maximum y coordinate across all glyph bounding boxes. + pub yMax: i16, + /// Bit 0: Bold; 1: Italic; 2: Underline; 3: Outline; 4: Shadow; + /// 5: Condensed; 6: Extended. + pub macStyle: u16, + /// Smallest readable size in pixels. + pub lowestRecPPEM: u16, + /// Deprecated (Set to 2). + pub fontDirectionHint: i16, + /// 0 for short offsets (Offset16), 1 for long (Offset32). + pub indexToLocFormat: i16, + /// 0 for current format. + pub glyphDataFormat: i16, +} + +impl Head { + /// Parse the table from raw data. + pub fn init(data: &[u8]) -> Result { + let mut r = Reader::new(data); + Ok(Head { + majorVersion: r.u16()?, + minorVersion: r.u16()?, + fontRevision: Fixed(r.i32()?), + checksumAdjustment: r.u32()?, + magicNumber: r.u32()?, + flags: r.u16()?, + unitsPerEm: r.u16()?, + created: r.i64()?, + modified: r.i64()?, + xMin: r.i16()?, + yMin: r.i16()?, + xMax: r.i16()?, + yMax: r.i16()?, + macStyle: r.u16()?, + lowestRecPPEM: r.u16()?, + fontDirectionHint: r.i16()?, + indexToLocFormat: r.i16()?, + glyphDataFormat: r.i16()?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::opentype::sfnt::Sfnt; + + #[test] + fn head() { + let font = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let table = font.get_table(b"head").unwrap(); + let head = Head::init(table).unwrap(); + assert_eq!( + Head { + majorVersion: 1, + minorVersion: 0, + fontRevision: Fixed::from_f64(0.05499267578125), + checksumAdjustment: 1007668681, + magicNumber: 1594834165, + flags: 7, + unitsPerEm: 2000, + created: 3797757830, + modified: 3797760444, + xMin: -1000, + yMin: -1058, + xMax: 3089, + yMax: 2400, + macStyle: 0, + lowestRecPPEM: 7, + fontDirectionHint: 2, + indexToLocFormat: 1, + glyphDataFormat: 0, + }, + head + ); + } + + #[test] + fn head_too_short() { + assert_eq!(Head::init(&[0; 10]), Err(EndOfStream)); + } +} diff --git a/crates/ghostty-font/src/opentype/hhea.rs b/crates/ghostty-font/src/opentype/hhea.rs new file mode 100644 index 0000000..c24eced --- /dev/null +++ b/crates/ghostty-font/src/opentype/hhea.rs @@ -0,0 +1,105 @@ +//! Horizontal Header Table (`hhea`). Port of `font/opentype/hhea.zig`. +//! +//! References: +//! - https://learn.microsoft.com/en-us/typography/opentype/spec/hhea +#![allow(non_snake_case)] + +use super::{EndOfStream, Reader}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Hhea { + /// Major version number of the horizontal header table — set to 1. + pub majorVersion: u16, + /// Minor version number of the horizontal header table — set to 0. + pub minorVersion: u16, + /// Typographic ascent (design units). + pub ascender: i16, + /// Typographic descent (design units). + pub descender: i16, + /// Typographic line gap (design units). + pub lineGap: i16, + /// Maximum advance width value in 'hmtx' table. + pub advanceWidthMax: u16, + /// Minimum left sidebearing value in 'hmtx' table. + pub minLeftSideBearing: i16, + /// Minimum right sidebearing value. + pub minRightSideBearing: i16, + /// Max(lsb + (xMax - xMin)). + pub xMaxExtent: i16, + /// Used to calculate the slope of the cursor (rise/run). + pub caretSlopeRise: i16, + pub caretSlopeRun: i16, + /// Set to 0 for non-slanted fonts. + pub caretOffset: i16, + pub reserved0: i16, + pub reserved1: i16, + pub reserved2: i16, + pub reserved3: i16, + /// 0 for current format. + pub metricDataFormat: i16, + /// Number of hMetric entries in 'hmtx' table. + pub numberOfHMetrics: u16, +} + +impl Hhea { + pub fn init(data: &[u8]) -> Result { + let mut r = Reader::new(data); + Ok(Hhea { + majorVersion: r.u16()?, + minorVersion: r.u16()?, + ascender: r.i16()?, + descender: r.i16()?, + lineGap: r.i16()?, + advanceWidthMax: r.u16()?, + minLeftSideBearing: r.i16()?, + minRightSideBearing: r.i16()?, + xMaxExtent: r.i16()?, + caretSlopeRise: r.i16()?, + caretSlopeRun: r.i16()?, + caretOffset: r.i16()?, + reserved0: r.i16()?, + reserved1: r.i16()?, + reserved2: r.i16()?, + reserved3: r.i16()?, + metricDataFormat: r.i16()?, + numberOfHMetrics: r.u16()?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::opentype::sfnt::Sfnt; + + #[test] + fn hhea() { + let font = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let table = font.get_table(b"hhea").unwrap(); + let hhea = Hhea::init(table).unwrap(); + assert_eq!( + Hhea { + majorVersion: 1, + minorVersion: 0, + ascender: 1900, + descender: -450, + lineGap: 0, + advanceWidthMax: 1200, + minLeftSideBearing: -1000, + minRightSideBearing: -1889, + xMaxExtent: 3089, + caretSlopeRise: 1, + caretSlopeRun: 0, + caretOffset: 0, + reserved0: 0, + reserved1: 0, + reserved2: 0, + reserved3: 0, + metricDataFormat: 0, + numberOfHMetrics: 2, + }, + hhea + ); + } +} diff --git a/crates/ghostty-font/src/opentype/mod.rs b/crates/ghostty-font/src/opentype/mod.rs new file mode 100644 index 0000000..60c19e7 --- /dev/null +++ b/crates/ghostty-font/src/opentype/mod.rs @@ -0,0 +1,57 @@ +//! OpenType table parsing. Port of `font/opentype.zig` and `font/opentype/*`. + +pub mod head; +pub mod hhea; +pub mod os2; +pub mod post; +pub mod sfnt; +pub mod svg; + +pub use head::Head; +pub use hhea::Hhea; +pub use os2::OS2; +pub use post::Post; +pub use svg::SVG; + +/// Error returned when a table is too short (Zig: `error.EndOfStream`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("end of stream")] +pub struct EndOfStream; + +/// Minimal big-endian cursor used by the table parsers. +#[derive(Debug, Clone)] +pub(crate) struct Reader<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> Reader<'a> { + pub(crate) fn new(data: &'a [u8]) -> Self { + Self { data, pos: 0 } + } + + pub(crate) fn bytes(&mut self) -> Result<[u8; N], EndOfStream> { + let end = self.pos.checked_add(N).ok_or(EndOfStream)?; + let s = self.data.get(self.pos..end).ok_or(EndOfStream)?; + self.pos = end; + let mut out = [0u8; N]; + out.copy_from_slice(s); + Ok(out) + } + + pub(crate) fn u16(&mut self) -> Result { + Ok(u16::from_be_bytes(self.bytes()?)) + } + pub(crate) fn i16(&mut self) -> Result { + Ok(i16::from_be_bytes(self.bytes()?)) + } + pub(crate) fn u32(&mut self) -> Result { + Ok(u32::from_be_bytes(self.bytes()?)) + } + pub(crate) fn i32(&mut self) -> Result { + Ok(i32::from_be_bytes(self.bytes()?)) + } + pub(crate) fn i64(&mut self) -> Result { + Ok(i64::from_be_bytes(self.bytes()?)) + } +} diff --git a/crates/ghostty-font/src/opentype/os2.rs b/crates/ghostty-font/src/opentype/os2.rs new file mode 100644 index 0000000..5accee6 --- /dev/null +++ b/crates/ghostty-font/src/opentype/os2.rs @@ -0,0 +1,286 @@ +//! OS/2 and Windows Metrics Table. Port of `font/opentype/os2.zig`. +//! +//! The Zig version has one extern struct per table version and a merged +//! `OS2` struct with optional trailing fields; here the per-version structs +//! are replaced by a single sequential parser that stops at the end of the +//! version-specific layout. +//! +//! References: +//! - https://learn.microsoft.com/en-us/typography/opentype/spec/os2 +#![allow(non_snake_case)] + +use super::{EndOfStream, Reader}; + +/// Font selection flags (`fsSelection`), bit 0 is the LSB. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FSSelection { + /// Font contains italic or oblique glyphs. + pub italic: bool, + /// Glyphs are underscored. + pub underscore: bool, + /// Glyphs have their foreground and background reversed. + pub negative: bool, + /// Outline (hollow) glyphs. + pub outlined: bool, + /// Glyphs are overstruck. + pub strikeout: bool, + /// Glyphs are emboldened. + pub bold: bool, + /// Glyphs are in the standard weight/style for the font. + pub regular: bool, + /// If set, it is strongly recommended that applications use + /// OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap as the + /// default line spacing for this font. + pub use_typo_metrics: bool, + /// The font has 'name' table strings consistent with a weight/width/slope + /// family without requiring use of name IDs 21 and 22. + pub wws: bool, + /// Font contains oblique glyphs. + pub oblique: bool, +} + +impl FSSelection { + pub fn from_u16(v: u16) -> Self { + let b = |n: u32| v & (1 << n) != 0; + Self { + italic: b(0), + underscore: b(1), + negative: b(2), + outlined: b(3), + strikeout: b(4), + bold: b(5), + regular: b(6), + use_typo_metrics: b(7), + wws: b(8), + oblique: b(9), + } + } + + pub fn to_u16(self) -> u16 { + let bits = [ + self.italic, + self.underscore, + self.negative, + self.outlined, + self.strikeout, + self.bold, + self.regular, + self.use_typo_metrics, + self.wws, + self.oblique, + ]; + bits.iter() + .enumerate() + .fold(0u16, |acc, (i, &set)| acc | (u16::from(set) << i)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum Os2Error { + #[error("end of stream")] + EndOfStream, + #[error("OS/2 version not supported")] + OS2VersionNotSupported, +} + +impl From for Os2Error { + fn from(_: EndOfStream) -> Self { + Os2Error::EndOfStream + } +} + +/// Merged view over all OS/2 table versions; fields that only exist in +/// later versions are `Option`s. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OS2 { + pub version: u16, + pub xAvgCharWidth: i16, + pub usWeightClass: u16, + pub usWidthClass: u16, + pub fsType: u16, + pub ySubscriptXSize: i16, + pub ySubscriptYSize: i16, + pub ySubscriptXOffset: i16, + pub ySubscriptYOffset: i16, + pub ySuperscriptXSize: i16, + pub ySuperscriptYSize: i16, + pub ySuperscriptXOffset: i16, + pub ySuperscriptYOffset: i16, + pub yStrikeoutSize: i16, + pub yStrikeoutPosition: i16, + pub sFamilyClass: i16, + pub panose: [u8; 10], + pub ulUnicodeRange1: u32, + pub ulUnicodeRange2: u32, + pub ulUnicodeRange3: u32, + pub ulUnicodeRange4: u32, + pub achVendID: [u8; 4], + pub fsSelection: FSSelection, + pub usFirstCharIndex: u16, + pub usLastCharIndex: u16, + pub sTypoAscender: i16, + pub sTypoDescender: i16, + pub sTypoLineGap: i16, + pub usWinAscent: u16, + pub usWinDescent: u16, + + // Version 1+ + pub ulCodePageRange1: Option, + pub ulCodePageRange2: Option, + + // Version 2+ + pub sxHeight: Option, + pub sCapHeight: Option, + pub usDefaultChar: Option, + pub usBreakChar: Option, + pub usMaxContext: Option, + + // Version 5 + pub usLowerOpticalPointSize: Option, + pub usUpperOpticalPointSize: Option, +} + +impl OS2 { + pub fn init(data: &[u8]) -> Result { + let mut r = Reader::new(data); + let version = r.u16()?; + if version > 5 { + return Err(Os2Error::OS2VersionNotSupported); + } + + let mut t = OS2 { + version, + xAvgCharWidth: r.i16()?, + usWeightClass: r.u16()?, + usWidthClass: r.u16()?, + fsType: r.u16()?, + ySubscriptXSize: r.i16()?, + ySubscriptYSize: r.i16()?, + ySubscriptXOffset: r.i16()?, + ySubscriptYOffset: r.i16()?, + ySuperscriptXSize: r.i16()?, + ySuperscriptYSize: r.i16()?, + ySuperscriptXOffset: r.i16()?, + ySuperscriptYOffset: r.i16()?, + yStrikeoutSize: r.i16()?, + yStrikeoutPosition: r.i16()?, + sFamilyClass: r.i16()?, + panose: r.bytes()?, + ulUnicodeRange1: r.u32()?, + ulUnicodeRange2: r.u32()?, + ulUnicodeRange3: r.u32()?, + ulUnicodeRange4: r.u32()?, + achVendID: r.bytes()?, + fsSelection: FSSelection::from_u16(r.u16()?), + usFirstCharIndex: r.u16()?, + usLastCharIndex: r.u16()?, + sTypoAscender: r.i16()?, + sTypoDescender: r.i16()?, + sTypoLineGap: r.i16()?, + usWinAscent: r.u16()?, + usWinDescent: r.u16()?, + ulCodePageRange1: None, + ulCodePageRange2: None, + sxHeight: None, + sCapHeight: None, + usDefaultChar: None, + usBreakChar: None, + usMaxContext: None, + usLowerOpticalPointSize: None, + usUpperOpticalPointSize: None, + }; + + if version >= 1 { + t.ulCodePageRange1 = Some(r.u32()?); + t.ulCodePageRange2 = Some(r.u32()?); + } + if version >= 2 { + t.sxHeight = Some(r.i16()?); + t.sCapHeight = Some(r.i16()?); + t.usDefaultChar = Some(r.u16()?); + t.usBreakChar = Some(r.u16()?); + t.usMaxContext = Some(r.u16()?); + } + if version == 5 { + t.usLowerOpticalPointSize = Some(r.u16()?); + t.usUpperOpticalPointSize = Some(r.u16()?); + } + Ok(t) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::opentype::sfnt::Sfnt; + + #[test] + fn os_2() { + let font = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let table = font.get_table(b"OS/2").unwrap(); + let os2 = OS2::init(table).unwrap(); + + assert_eq!( + OS2 { + version: 4, + xAvgCharWidth: 1200, + usWeightClass: 400, + usWidthClass: 5, + fsType: 0, + ySubscriptXSize: 1300, + ySubscriptYSize: 1200, + ySubscriptXOffset: 0, + ySubscriptYOffset: 150, + ySuperscriptXSize: 1300, + ySuperscriptYSize: 1200, + ySuperscriptXOffset: 0, + ySuperscriptYOffset: 700, + yStrikeoutSize: 100, + yStrikeoutPosition: 550, + sFamilyClass: 0, + panose: [2, 11, 6, 9, 6, 3, 0, 2, 0, 4], + ulUnicodeRange1: 3843162111, + ulUnicodeRange2: 3603300351, + ulUnicodeRange3: 117760229, + ulUnicodeRange4: 96510060, + achVendID: *b"corm", + fsSelection: FSSelection { + regular: true, + use_typo_metrics: true, + ..Default::default() + }, + usFirstCharIndex: 13, + usLastCharIndex: 65535, + sTypoAscender: 1900, + sTypoDescender: -450, + sTypoLineGap: 0, + usWinAscent: 2400, + usWinDescent: 450, + ulCodePageRange1: Some(1613234687), + ulCodePageRange2: Some(0), + sxHeight: Some(1100), + sCapHeight: Some(1450), + usDefaultChar: Some(0), + usBreakChar: Some(32), + usMaxContext: Some(126), + usLowerOpticalPointSize: None, + usUpperOpticalPointSize: None, + }, + os2 + ); + } + + #[test] + fn fs_selection_roundtrip() { + for v in 0..(1u16 << 10) { + assert_eq!(FSSelection::from_u16(v).to_u16(), v); + } + } + + #[test] + fn unsupported_version() { + assert_eq!(OS2::init(&[0, 9]), Err(Os2Error::OS2VersionNotSupported)); + assert_eq!(OS2::init(&[0, 0, 1]), Err(Os2Error::EndOfStream)); + } +} diff --git a/crates/ghostty-font/src/opentype/post.rs b/crates/ghostty-font/src/opentype/post.rs new file mode 100644 index 0000000..cce59d5 --- /dev/null +++ b/crates/ghostty-font/src/opentype/post.rs @@ -0,0 +1,70 @@ +//! PostScript Table (`post`). Port of `font/opentype/post.zig`. +//! +//! References: +//! - https://learn.microsoft.com/en-us/typography/opentype/spec/post +#![allow(non_snake_case)] + +use super::sfnt::{Fixed, Version16Dot16}; +use super::{EndOfStream, Reader}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Post { + pub version: Version16Dot16, + /// Italic angle in counter-clockwise degrees from the vertical. + pub italicAngle: Fixed, + /// Suggested y offset of the top of the underline stroke (design units). + pub underlinePosition: i16, + /// Suggested underline thickness (design units). + pub underlineThickness: i16, + /// Set to 0 if the font is proportionally spaced, non-zero if monospaced. + pub isFixedPitch: u32, + pub minMemType42: u32, + pub maxMemType42: u32, + pub minMemType1: u32, + pub maxMemType1: u32, +} + +impl Post { + pub fn init(data: &[u8]) -> Result { + let mut r = Reader::new(data); + Ok(Post { + version: Version16Dot16::from_u32(r.u32()?), + italicAngle: Fixed(r.i32()?), + underlinePosition: r.i16()?, + underlineThickness: r.i16()?, + isFixedPitch: r.u32()?, + minMemType42: r.u32()?, + maxMemType42: r.u32()?, + minMemType1: r.u32()?, + maxMemType1: r.u32()?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::opentype::sfnt::Sfnt; + + #[test] + fn post() { + let font = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let table = font.get_table(b"post").unwrap(); + let post = Post::init(table).unwrap(); + assert_eq!( + Post { + version: Version16Dot16 { minor: 0, major: 2 }, + italicAngle: Fixed::from_f64(0.0), + underlinePosition: -200, + underlineThickness: 100, + isFixedPitch: 1, + minMemType42: 0, + maxMemType42: 0, + minMemType1: 0, + maxMemType1: 0, + }, + post + ); + } +} diff --git a/crates/ghostty-font/src/opentype/sfnt.rs b/crates/ghostty-font/src/opentype/sfnt.rs new file mode 100644 index 0000000..54635d9 --- /dev/null +++ b/crates/ghostty-font/src/opentype/sfnt.rs @@ -0,0 +1,230 @@ +//! SFNT container and basic OpenType data types. Port of +//! `font/opentype/sfnt.zig`. + +use super::{EndOfStream, Reader}; + +pub type Tag = [u8; 4]; + +/// 32-bit signed fixed-point number (16.16). Stored as the raw `i32`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Fixed(pub i32); + +impl Fixed { + pub fn from_f64(float: f64) -> Self { + Fixed((float * 65536.0).round() as i32) + } + pub fn to_f64(self) -> f64 { + f64::from(self.0) / 65536.0 + } + /// Fractional bits (low 16). + pub fn frac(self) -> u16 { + (self.0 as u32 & 0xFFFF) as u16 + } + /// Integer part (high 16, signed). + pub fn int(self) -> i16 { + (self.0 >> 16) as i16 + } +} + +/// 16-bit signed fixed number with the low 14 bits of fraction (2.14). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct F2Dot14(pub i16); + +impl F2Dot14 { + pub fn from_f64(float: f64) -> Self { + F2Dot14((float * 16384.0).round() as i16) + } + pub fn to_f64(self) -> f64 { + f64::from(self.0) / 16384.0 + } +} + +/// Packed 32-bit value with major and minor version numbers. +/// (Zig `packed struct(u32) { minor: u16, major: u16 }`: `minor` is the low +/// half, so reading a big-endian u32 yields `major` in the high half.) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Version16Dot16 { + pub minor: u16, + pub major: u16, +} + +impl Version16Dot16 { + pub fn from_u32(v: u32) -> Self { + Self { + minor: (v & 0xFFFF) as u16, + major: (v >> 16) as u16, + } + } +} + +/// 32-bit signed 26.6 fixed point numbers. Stored as the raw `i32` +/// (the 26.6 bit pattern, i.e. what FreeType calls `FT_F26Dot6`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct F26Dot6(pub i32); + +impl F26Dot6 { + const FRAC_FACTOR: f64 = 64.0; + const HALF: i32 = 1 << 5; + + pub fn from_f64(float: f64) -> Self { + F26Dot6((float * Self::FRAC_FACTOR).round() as i32) + } + + pub fn to_f64(self) -> f64 { + f64::from(self.0) / Self::FRAC_FACTOR + } + + pub fn to_f32(self) -> f32 { + self.0 as f32 / 64.0 + } + + /// The fractional bits (low 6, unsigned). + pub fn frac(self) -> u8 { + (self.0 & 0x3F) as u8 + } + + /// The integer part (floor). + pub fn int(self) -> i32 { + self.0 >> 6 + } + + /// Round to the nearest integer, .5 rounds away from 0. + pub fn round(self) -> i32 { + if self.0 & Self::HALF != 0 { + self.int() + 1 + } else { + self.int() + } + } +} + +impl std::fmt::Display for F26Dot6 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_f64()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum SfntError { + #[error("end of stream")] + EndOfStream, +} + +impl From for SfntError { + fn from(_: EndOfStream) -> Self { + SfntError::EndOfStream + } +} + +/// The static portion of the table directory (the "offset subtable"). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetSubtable { + /// Indicates the type of font file we're reading. + /// - 0x00_01_00_00 ---- TrueType + /// - 0x74_72_75_65 'true' TrueType + /// - 0x4F_54_54_4F 'OTTO' OpenType + /// - 0x74_79_70_31 'typ1' PostScript + pub sfnt_version: u32, + pub num_tables: u16, + pub search_range: u16, + pub entry_selector: u16, + pub range_shift: u16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TableRecord { + pub tag: Tag, + pub checksum: u32, + /// Offset from beginning of font file. + pub offset: u32, + pub length: u32, +} + +#[derive(Debug, Clone)] +pub struct Directory { + pub offset: OffsetSubtable, + pub records: Vec, +} + +/// Wrapper for parsing a SFNT font and accessing its tables. +#[derive(Debug, Clone)] +pub struct Sfnt<'a> { + pub directory: Directory, + data: &'a [u8], +} + +impl<'a> Sfnt<'a> { + /// Parse a font from raw data. The struct keeps a reference to `data`. + pub fn init(data: &'a [u8]) -> Result, SfntError> { + let mut r = Reader::new(data); + let offset = OffsetSubtable { + sfnt_version: r.u32()?, + num_tables: r.u16()?, + search_range: r.u16()?, + entry_selector: r.u16()?, + range_shift: r.u16()?, + }; + let mut records = Vec::with_capacity(usize::from(offset.num_tables)); + for _ in 0..offset.num_tables { + records.push(TableRecord { + tag: r.bytes()?, + checksum: r.u32()?, + offset: r.u32()?, + length: r.u32()?, + }); + } + Ok(Sfnt { + directory: Directory { offset, records }, + data, + }) + } + + /// Returns the bytes of the table with the provided tag if present. + /// (None as well if the record points outside of the data.) + pub fn get_table(&self, tag: &[u8; 4]) -> Option<&'a [u8]> { + let record = self.directory.records.iter().find(|r| &r.tag == tag)?; + let start = record.offset as usize; + let end = start.checked_add(record.length as usize)?; + self.data.get(start..end) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + + #[test] + fn fixed_point() { + let p = F26Dot6::from_f64(26.6); + assert_eq!(p.int(), 26); + assert_eq!(p.frac(), 38); + assert_eq!(p.to_f64(), 26.59375); + assert_eq!(p.round(), 27); + + let n = F26Dot6::from_f64(-26.6); + assert_eq!(n.int(), -27); + assert_eq!(n.frac(), 26); + assert_eq!(n.to_f64(), -26.59375); + assert_eq!(n.round(), -27); + } + + #[test] + fn parse_font() { + let sfnt = Sfnt::init(embedded::JULIA_MONO).unwrap(); + assert_eq!(19, sfnt.directory.offset.num_tables); + assert_eq!(b"prep", &sfnt.directory.records[18].tag); + } + + #[test] + fn get_table() { + let sfnt = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let svg = sfnt.get_table(b"SVG ").unwrap(); + assert_eq!(430, svg.len()); + } + + #[test] + fn truncated_data_is_an_error() { + assert!(Sfnt::init(&[0, 1, 0, 0, 0, 5]).is_err()); + } +} diff --git a/crates/ghostty-font/src/opentype/svg.rs b/crates/ghostty-font/src/opentype/svg.rs new file mode 100644 index 0000000..3847d9e --- /dev/null +++ b/crates/ghostty-font/src/opentype/svg.rs @@ -0,0 +1,146 @@ +//! SVG glyphs description table. Port of `font/opentype/svg.zig`. +//! +//! This struct is focused purely on the operations we need for Ghostty, +//! namely to be able to look up whether an glyph ID is present in the SVG +//! table or not. It isn't meant to be a general purpose SVG table reader. +//! +//! References: +//! - https://www.w3.org/2013/10/SVG_in_OpenType/#thesvg +//! - https://learn.microsoft.com/en-us/typography/opentype/spec/svg + +use super::EndOfStream; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum SvgError { + #[error("end of stream")] + EndOfStream, + #[error("SVG table version not supported")] + SVGVersionNotSupported, +} + +impl From for SvgError { + fn from(_: EndOfStream) -> Self { + SvgError::EndOfStream + } +} + +/// Size of one document record in bytes. +const RECORD_SIZE: usize = 12; + +#[derive(Debug, Clone)] +pub struct SVG<'a> { + /// The start and end glyph IDs (inclusive) that are present in the + /// table. This is used to very quickly include/exclude a glyph from + /// the table. + pub start_glyph_id: u16, + pub end_glyph_id: u16, + + /// All records in the table (`len * 12` bytes). + records: &'a [u8], +} + +fn glyph_range(record: &[u8]) -> Result<(u16, u16), SvgError> { + if record.len() < 4 { + return Err(SvgError::EndOfStream); + } + Ok(( + u16::from_be_bytes([record[0], record[1]]), + u16::from_be_bytes([record[2], record[3]]), + )) +} + +impl<'a> SVG<'a> { + pub fn init(data: &'a [u8]) -> Result, SvgError> { + let get = |pos: usize, n: usize| data.get(pos..pos.checked_add(n)?); + + // Version + let version = get(0, 2).ok_or(SvgError::EndOfStream)?; + if u16::from_be_bytes([version[0], version[1]]) != 0 { + return Err(SvgError::SVGVersionNotSupported); + } + + // Offset + let off = get(2, 4).ok_or(SvgError::EndOfStream)?; + let offset = u32::from_be_bytes([off[0], off[1], off[2], off[3]]) as usize; + + // Document list: count followed by records. + let len_b = get(offset, 2).ok_or(SvgError::EndOfStream)?; + let len = usize::from(u16::from_be_bytes([len_b[0], len_b[1]])); + let start = offset + 2; + // Zig reads records[0] (and the last) unconditionally. + let records = get(start, len * RECORD_SIZE).ok_or(SvgError::EndOfStream)?; + if len == 0 { + return Err(SvgError::EndOfStream); + } + + let start_range = glyph_range(&records[..RECORD_SIZE])?; + let end_range = if len == 1 { + start_range + } else { + glyph_range(&records[(len - 1) * RECORD_SIZE..])? + }; + + Ok(SVG { + start_glyph_id: start_range.0, + end_glyph_id: end_range.1, + records, + }) + } + + pub fn has_glyph(&self, glyph_id: u16) -> bool { + // Fast path: outside the table range + if glyph_id < self.start_glyph_id || glyph_id > self.end_glyph_id { + return false; + } + + // Fast path, matches the start/end glyph IDs + if glyph_id == self.start_glyph_id || glyph_id == self.end_glyph_id { + return true; + } + + // Slow path: binary search our records + let (mut lo, mut hi) = (0usize, self.records.len() / RECORD_SIZE); + while lo < hi { + let mid = lo + (hi - lo) / 2; + let rec = &self.records[mid * RECORD_SIZE..(mid + 1) * RECORD_SIZE]; + let (start, end) = glyph_range(rec).unwrap_or((u16::MAX, 0)); + if glyph_id < start { + hi = mid; + } else if glyph_id > end { + lo = mid + 1; + } else { + return true; + } + } + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::embedded; + use crate::opentype::sfnt::Sfnt; + + // NOTE: the Zig test obtains the table through `Face.copyTable`; we read + // the same bytes through the SFNT directory (Face-based variant lives + // with the freetype face tests). + #[test] + fn svg() { + let font = Sfnt::init(embedded::JULIA_MONO).unwrap(); + let table = font.get_table(b"SVG ").unwrap(); + let svg = SVG::init(table).unwrap(); + assert_eq!(11482, svg.start_glyph_id); + assert_eq!(11482, svg.end_glyph_id); + assert!(svg.has_glyph(11482)); + assert!(!svg.has_glyph(11483)); + } + + #[test] + fn svg_bad_version() { + assert_eq!( + SVG::init(&[0, 1, 0, 0, 0, 0]).unwrap_err(), + SvgError::SVGVersionNotSupported + ); + } +} diff --git a/crates/ghostty-font/src/shape.rs b/crates/ghostty-font/src/shape.rs new file mode 100644 index 0000000..65ab3d8 --- /dev/null +++ b/crates/ghostty-font/src/shape.rs @@ -0,0 +1,71 @@ +//! Shaping types shared by all shapers. Port of `font/shape.zig`. +//! +//! The shaper implementation for the Linux backend is HarfBuzz +//! ([`crate::shaper::harfbuzz::Shaper`]). + +use crate::collection::Index; + +pub use crate::shaper::cache::Cache; +pub use crate::shaper::feature::{DEFAULT_FEATURES, Feature, FeatureList}; +pub use crate::shaper::{harfbuzz, noop}; + +/// Shaper implementation for our compile options (backend +/// `fontconfig_freetype` => HarfBuzz). +pub use crate::shaper::harfbuzz::Shaper; + +/// A cell is a single single within a terminal that should be rendered +/// for a shaping call. Note all terminal cells may be present; only +/// cells that have a glyph that needs to be rendered. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Cell { + /// The X position of this shaper cell relative to the offset of the + /// run. Because runs are always within a single row, it is expected + /// that the caller can reconstruct the full position of the cell by + /// using the known Y position of the cell and adding the X position + /// to the run offset. + pub x: u16, + + /// An additional offset to apply to the rendering. + pub x_offset: i16, + pub y_offset: i16, + + /// The glyph index for this cell. The font index to use alongside + /// this cell is available in the text run. This glyph index is only + /// valid for a given GroupCache and FontIndex that was used to create + /// the runs. + pub glyph_index: u32, +} + +/// Options for shapers. +#[derive(Debug, Clone, Default)] +pub struct Options { + /// Font features to use when shaping. + /// + /// Note: eventually, this will move to font.Face probably as we may + /// want to support per-face feature configuration. For now, we only + /// support applying features globally. + pub features: Vec, +} + +/// A single text run. Port of `shaper/run.zig` `TextRun`. +/// +/// TODO(integration): `run.zig` (RunIterator over terminal rows) is deferred +/// to F2; the `grid: *SharedGrid` field is omitted here and should be added +/// when `SharedGrid` and the run iterator are wired in. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TextRun { + /// A unique hash for this run. This can be used to cache the shaping + /// results. + pub hash: u64, + + /// The offset in the row where this run started. This is added to the + /// X position of the final shaped cells to get the absolute position + /// in the row where they belong. + pub offset: u16, + + /// The total number of cells produced by this run. + pub cells: u16, + + /// The font index to use for the glyphs of this run. + pub font_index: Index, +} diff --git a/crates/ghostty-font/src/shaper/cache.rs b/crates/ghostty-font/src/shaper/cache.rs new file mode 100644 index 0000000..8778bab --- /dev/null +++ b/crates/ghostty-font/src/shaper/cache.rs @@ -0,0 +1,111 @@ +//! This structure caches the shaped cells for a given text run. +//! +//! At one point, shaping was the most expensive part of rendering text +//! (accounting for 96% of frame time on my machine). To speed it up, this +//! was introduced so that shaping results can be cached depending on the +//! run. +//! +//! The cache key is the text run. The text run builds its own hash value +//! based on the font, style, codepoint, etc. This just utilizes the hash that +//! the text run provides. + +use ghostty_datastruct::cache_table::{CacheContext, CacheTable}; + +use crate::shape::{Cell, TextRun}; + +/// Context for cache table. +#[derive(Debug, Default)] +pub struct CellCacheTableContext; + +impl CacheContext> for CellCacheTableContext { + fn hash(&mut self, key: &u64) -> u64 { + *key + } + fn eql(&mut self, a: &u64, b: &u64) -> bool { + a == b + } +} + +/// Cache table for run hash -> shaped cells. +/// +/// Capacity is slightly arbitrary. These numbers are guesses: I'd expect an +/// average of 256 frequently cached runs is a safe guess for most terminal +/// screens, with 8 items per bucket to give decent resiliency to important +/// runs. +type CellCacheTable = CacheTable, CellCacheTableContext, 256, 8>; + +/// The cache table of shaped cells. +#[derive(Debug)] +pub struct Cache { + map: CellCacheTable, +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} + +impl Cache { + pub fn new() -> Cache { + Cache { + map: CellCacheTable::new(CellCacheTableContext), + } + } + + /// Get the shaped cells for the given text run, + /// or None if they are not in the cache. + pub fn get(&mut self, run: &TextRun) -> Option<&[Cell]> { + self.map.get(&run.hash).map(Vec::as_slice) + } + + /// Insert the shaped cells for the given text run into the cache. + /// + /// The cells will be duplicated. + pub fn put(&mut self, run: &TextRun, cells: &[Cell]) { + // An evicted entry is simply dropped. + let _evicted = self.map.put(run.hash, cells.to_vec()); + } + + /// Remove everything from the cache. + pub fn clear(&mut self) { + self.map.clear(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collection::Index; + + #[test] + fn cache() { + let mut c = Cache::new(); + + let run = TextRun { + hash: 1, + offset: 0, + cells: 0, + font_index: Index::default(), + }; + assert!(c.get(&run).is_none()); + c.put( + &run, + &[ + Cell { + x: 0, + glyph_index: 0, + ..Cell::default() + }, + Cell { + x: 1, + glyph_index: 1, + ..Cell::default() + }, + ], + ); + + let actual = c.get(&run).unwrap(); + assert_eq!(actual.len(), 2); + } +} diff --git a/crates/ghostty-font/src/shaper/feature.rs b/crates/ghostty-font/src/shaper/feature.rs new file mode 100644 index 0000000..f855d87 --- /dev/null +++ b/crates/ghostty-font/src/shaper/feature.rs @@ -0,0 +1,474 @@ +//! OpenType font feature settings. Port of `font/shaper/feature.zig`. + +// The explicit `continue`s mirror the Zig state machine one-to-one. +#![allow(clippy::needless_continue, clippy::single_match_else)] + +use std::fmt; + +/// A minimal byte reader mirroring the `std.io.fixedBufferStream` reader +/// semantics that the Zig parser relies on: reading past the end yields a +/// `,` (the parser uses `reader.readByte() catch ','`). +pub struct Reader<'a> { + buf: &'a [u8], + pub pos: usize, +} + +impl<'a> Reader<'a> { + pub fn new(buf: &'a [u8]) -> Self { + Reader { buf, pos: 0 } + } + + /// Read a byte; at end of stream returns `,` without advancing. + fn read_byte(&mut self) -> u8 { + match self.buf.get(self.pos) { + Some(&b) => { + self.pos += 1; + b + } + None => b',', + } + } + + /// Skip until after the next `,` or end of stream. + fn skip_until_delimiter_or_eof(&mut self) { + while let Some(&b) = self.buf.get(self.pos) { + self.pos += 1; + if b == b',' { + break; + } + } + } + + pub fn at_end(&self) -> bool { + self.pos >= self.buf.len() + } +} + +/// Represents an OpenType font feature setting, which consists of a tag and +/// a numeric parameter >= 0. Most features are boolean, so only parameters +/// of 0 and 1 make sense for them, but some (e.g. 'cv01'..'cv99') can take +/// parameters to choose between multiple variants of a given character or +/// characters. +/// +/// Ref: +/// - https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#features-and-lookups +/// - https://harfbuzz.github.io/shaping-opentype-features.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Feature { + pub tag: [u8; 4], + pub value: u32, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum State { + /// Initial state. + Start, + /// Parsing the tag. + Tag, + /// In the space between the tag and the value. + Space, + /// Parsing an integer parameter directly in to `value`. + Int, + /// Parsing a boolean keyword parameter ("on"/"off"). + Bool, + /// Encountered an unrecoverable syntax error, advancing to boundary. + Err, + /// Done parsing feature, skip whitespace until end. + Done, + /// Successfully finished. + Finished, +} + +impl Feature { + pub fn from_string(s: &str) -> Option { + Self::from_bytes(s.as_bytes()) + } + + pub fn from_bytes(s: &[u8]) -> Option { + Self::from_reader(&mut Reader::new(s)) + } + + /// Parse a single font feature setting from a reader, with a version + /// of the syntax of HarfBuzz's font feature strings. Stops at end of stream + /// or when a ',' is encountered. + /// + /// This parsing aims to be as error-tolerant as possible while avoiding any + /// assumptions in ambiguous scenarios. When invalid syntax is encountered, + /// the reader is advanced to the next boundary (end-of-stream or ',') so + /// that further features may be read. + /// + /// Ref: https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string + pub fn from_reader(reader: &mut Reader<'_>) -> Option { + let mut tag = [0u8; 4]; + let mut tag_len = 0usize; + let mut value: Option = None; + let mut state = State::Start; + + loop { + match state { + // If we're done then we skip whitespace until we see a ','. + State::Finished => break, + State::Done => loop { + match reader.read_byte() { + b' ' | b'\t' => continue, + b',' => { + state = State::Finished; + break; + } + // If we see something other than whitespace or a ',' + // then this is an error since the intent is unclear. + _ => { + state = State::Err; + break; + } + } + }, + + // If we're fast-forwarding from an error we just wanna + // stop at the first boundary and ignore all other bytes. + State::Err => { + reader.skip_until_delimiter_or_eof(); + return None; + } + + State::Start => loop { + match reader.read_byte() { + // Ignore leading whitespace. + b' ' | b'\t' => continue, + // Empty feature string. + b',' => return None, + // '+' prefix to explicitly enable feature. + b'+' => { + value = Some(1); + state = State::Tag; + break; + } + // '-' prefix to explicitly disable feature. + b'-' => { + value = Some(0); + state = State::Tag; + break; + } + // Quote mark introducing a tag. + b'"' | b'\'' => { + state = State::Tag; + break; + } + // First letter of tag. + byte => { + tag_len = 1; + tag[0] = byte; + state = State::Tag; + break; + } + } + }, + + State::Tag => loop { + match reader.read_byte() { + // If the tag is interrupted by a comma it's invalid. + b',' => return None, + // Ignore quote marks. This does technically ignore cases like + // "'k'e'r'n' = 0", but it's unambiguous so if someone really + // wants to do that in their config then... sure why not. + b'"' | b'\'' => continue, + // In all other cases we add the byte to our tag. + byte => { + tag[tag_len] = byte; + tag_len += 1; + if tag_len == 4 { + state = State::Space; + break; + } + } + } + }, + + State::Space => { + loop { + match reader.read_byte() { + b' ' | b'\t' => continue, + // Ignore quote marks since we might have a + // closing quote from the tag still ahead. + b'"' | b'\'' => continue, + // Allow an '=' (which we can safely ignore) + // only if we don't already have a value due + // to a '+' or '-' prefix. + b'=' => { + if value.is_some() { + state = State::Err; + break; + } + } + b',' => { + // Specifying only a tag turns a feature on. + if value.is_none() { + value = Some(1); + } + state = State::Finished; + break; + } + byte @ b'0'..=b'9' => { + // If we already have value because of a + // '+' or '-' prefix then this is an error. + if value.is_some() { + state = State::Err; + } else { + value = Some(u32::from(byte - b'0')); + state = State::Int; + } + break; + } + b'o' | b'O' => { + // If we already have value because of a + // '+' or '-' prefix then this is an error. + if value.is_some() { + state = State::Err; + } else { + state = State::Bool; + } + break; + } + _ => { + state = State::Err; + break; + } + } + } + } + + State::Int => { + loop { + match reader.read_byte() { + b',' => { + state = State::Finished; + break; + } + byte @ b'0'..=b'9' => { + // If our value gets too big while + // parsing we consider it an error. + let v = value.unwrap_or(0); + match v + .checked_mul(10) + .and_then(|v| v.checked_add(u32::from(byte - b'0'))) + { + Some(n) => value = Some(n), + None => { + state = State::Err; + break; + } + } + } + _ => { + state = State::Err; + break; + } + } + } + } + + State::Bool => loop { + match reader.read_byte() { + b',' => return None, + b'n' | b'N' => { + // "ofn" + if value.is_some() { + debug_assert_eq!(value, Some(0)); + state = State::Err; + break; + } + value = Some(1); + state = State::Done; + break; + } + b'f' | b'F' => { + // To make sure we consume two 'f's. + if value.is_none() { + value = Some(0); + } else { + debug_assert_eq!(value, Some(0)); + state = State::Done; + break; + } + } + _ => { + state = State::Err; + break; + } + } + }, + } + + if state == State::Finished { + break; + } + } + + debug_assert!(value.is_some()); + debug_assert_eq!(tag_len, 4); + + Some(Feature { tag, value: value? }) + } +} + +impl fmt::Display for Feature { + /// Serialize this feature. The string that this produces should be valid + /// to parse. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let tag = String::from_utf8_lossy(&self.tag); + if self.value <= 1 { + // Format boolean options as "+tag" for on and "-tag" for off. + write!(f, "{}{}", if self.value == 1 { '+' } else { '-' }, tag) + } else { + // Format non-boolean tags as "tag=value". + write!(f, "{tag}={}", self.value) + } + } +} + +/// A list of font feature settings (see `Feature` for more documentation). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct FeatureList { + pub features: Vec, +} + +impl FeatureList { + /// Parse a comma separated list of features. + /// See `Feature::from_reader` for more docs. + pub fn from_string(s: &str) -> FeatureList { + let mut this = FeatureList::default(); + this.append_from_string(s); + this + } + + /// Append features to this list from a string with a comma separated list. + /// See `Feature::from_reader` for more docs. + pub fn append_from_string(&mut self, s: &str) { + let mut reader = Reader::new(s.as_bytes()); + while !reader.at_end() { + let i = reader.pos; + if let Some(feature) = Feature::from_reader(&mut reader) { + self.features.push(feature); + } else { + log::warn!( + "failed to parse font feature setting: \"{}\"", + String::from_utf8_lossy(&s.as_bytes()[i..reader.pos]) + ); + } + } + } +} + +impl fmt::Display for FeatureList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, feature) in self.features.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + write!(f, "{feature}")?; + } + Ok(()) + } +} + +/// These features are hardcoded to always be on by default. Users +/// can turn them off by setting the features to "-liga" for example. +pub const DEFAULT_FEATURES: [Feature; 1] = [Feature { + tag: *b"liga", + value: 1, +}]; + +#[cfg(test)] +mod tests { + use super::*; + + fn f(tag: [u8; 4], value: u32) -> Feature { + Feature { tag, value } + } + + #[test] + fn feature_from_string() { + // This is not *complete* coverage of every possible + // combination of syntax, but it covers quite a few. + + // Boolean settings (on) + let kern_on = Some(f(*b"kern", 1)); + for s in [ + "kern", + "kern, ", + "kern on", + "kern on, ", + "+kern", + "+kern, ", + "\"kern\" = 1", + "\"kern\" = 1, ", + ] { + assert_eq!(kern_on, Feature::from_string(s), "{s}"); + } + + // Boolean settings (off) + let kern_off = Some(f(*b"kern", 0)); + for s in [ + "kern off", + "kern off, ", + "-'kern'", + "-'kern', ", + "\"kern\" = 0", + "\"kern\" = 0, ", + ] { + assert_eq!(kern_off, Feature::from_string(s), "{s}"); + } + + // Non-boolean settings + let aalt_2 = Some(f(*b"aalt", 2)); + for s in ["aalt=2", "aalt=2, ", "'aalt' 2", "'aalt' 2, "] { + assert_eq!(aalt_2, Feature::from_string(s), "{s}"); + } + + // Various ambiguous/error cases which should be null + for s in [ + "aalt=2x", // bad number + "toolong", // tag too long + "sht", // tag too short + "-kern 1", // redundant/conflicting + "-kern on", // redundant/conflicting + "aalt=o,", // bad keyword + "aalt=ofn,", // bad keyword + ] { + assert_eq!(None, Feature::from_string(s), "{s}"); + } + } + + #[test] + fn feature_list_from_string() { + let s = concat!( + " kern, kern on , +kern, \"kern\" = 1,", // Boolean settings (on) + "kern off, -'kern' , \"kern\"=0,", // Boolean settings (off) + "aalt=2, 'aalt'\t2,", // Non-boolean settings + "aalt=2x, toolong, sht, -kern 1, -kern on, aalt=o, aalt=ofn,", // Invalid cases + "last", // To ensure final element is included correctly. + ); + let feats = FeatureList::from_string(s); + let mut expected = vec![f(*b"kern", 1); 4]; + expected.extend(vec![f(*b"kern", 0); 3]); + expected.extend(vec![f(*b"aalt", 2); 2]); + expected.push(f(*b"last", 1)); + assert_eq!(expected, feats.features); + } + + #[test] + fn feature_display() { + assert_eq!("+kern", f(*b"kern", 1).to_string()); + assert_eq!("-kern", f(*b"kern", 0).to_string()); + assert_eq!("aalt=3", f(*b"aalt", 3).to_string()); + assert_eq!( + "+liga, ss01=2", + FeatureList::from_string("liga, ss01=2").to_string() + ); + } + + #[test] + fn default_features_has_liga() { + assert_eq!(DEFAULT_FEATURES, [f(*b"liga", 1)]); + } +} diff --git a/crates/ghostty-font/src/shaper/harfbuzz.rs b/crates/ghostty-font/src/shaper/harfbuzz.rs new file mode 100644 index 0000000..0efaab2 --- /dev/null +++ b/crates/ghostty-font/src/shaper/harfbuzz.rs @@ -0,0 +1,400 @@ +//! HarfBuzz shaper. Port of `font/shaper/harfbuzz.zig`. +//! +//! The Zig shaper is driven by `RunIterator` (shaper/run.zig, deferred to F2) +//! through the hooks `prepare` / `addCodepoint` / `finalize`; those hooks are +//! exposed here as [`Shaper::prepare`], [`Shaper::add_codepoint`] and +//! [`Shaper::finalize`]. [`Shaper::shape`] takes the face of the run's font +//! (or `None` for the special sprite font, whose codepoint == glyph index) +//! instead of a `TextRun` + `SharedGrid`; the caller is responsible for +//! holding whatever lock protects the face (FreeType faces are not thread +//! safe). [`Shaper::shape_text`] is a convenience that does the whole +//! prepare/add/finalize/shape sequence. + +use super::hb; +use crate::face::freetype::Face; +use crate::shape::{self, Cell, DEFAULT_FEATURES, FeatureList}; + +#[derive(Debug, thiserror::Error)] +pub enum ShapeError { + #[error("failed to create harfbuzz buffer")] + BufferCreate, + #[error("harfbuzz failed")] + HarfbuzzFailed, +} + +#[derive(Debug, Clone, Copy)] +struct Codepoint { + cluster: u32, +} + +#[derive(Debug, Clone, Copy, Default)] +struct RunOffset { + cluster: u32, + x: i32, + y: i32, +} + +#[derive(Debug, Clone, Copy, Default)] +struct CellOffset { + cluster: u32, + x: i32, +} + +/// Shaper that uses Harfbuzz. +pub struct Shaper { + /// The buffer used for text shaping. We reuse it across multiple shaping + /// calls to prevent allocations. + hb_buf: hb::Buffer, + + /// The shared memory used for shaping results. + cell_buf: Vec, + + /// The features to use for shaping. + hb_feats: Vec, + + /// The codepoints added to the buffer before shaping. We need to keep + /// these separately because after shaping, HarfBuzz replaces codepoints + /// with glyph indices in the buffer. + codepoints: Vec, +} + +impl Shaper { + pub fn new(opts: &shape::Options) -> Result { + // Parse all the features we want to use. + let mut feature_list = FeatureList::default(); + feature_list.features.extend_from_slice(&DEFAULT_FEATURES); + for feature_str in &opts.features { + feature_list.append_from_string(feature_str); + } + + let hb_feats = feature_list + .features + .iter() + .map(|f| hb::global_feature(f.tag, f.value)) + .collect(); + + Ok(Shaper { + hb_buf: hb::Buffer::new().ok_or(ShapeError::BufferCreate)?, + cell_buf: Vec::new(), + hb_feats, + codepoints: Vec::new(), + }) + } + + pub fn end_frame(&self) {} + + /// Number of codepoints currently in the HarfBuzz buffer (before + /// shaping) / glyphs (after shaping). + pub fn hb_buf_len(&self) -> usize { + self.hb_buf.len() + } + + // ---- RunIterator hooks ---- + + /// Reset the buffer for the current run. + pub fn prepare(&mut self) { + self.hb_buf.reset(); + self.hb_buf.set_content_type_unicode(); + + // We set the cluster level to `characters` to give us the most + // granularity, matching the CoreText shaper, and allowing us + // to use our same ligature detection heuristics. + self.hb_buf.set_cluster_level_characters(); + + self.codepoints.clear(); + + // We don't support RTL text because RTL in terminals is messy. + // Its something we want to improve. For now, we force LTR because + // our renderers assume a strictly increasing X value. + self.hb_buf.set_direction_ltr(); + } + + pub fn add_codepoint(&mut self, cp: u32, cluster: u32) { + // We pass the index into codepoints as the cluster value to HarfBuzz. + // After shaping, we use info.cluster to get back the index, which + // lets us look up the original cluster value from codepoints. + let index = self.codepoints.len() as u32; + self.hb_buf.add(cp, index); + self.codepoints.push(Codepoint { cluster }); + } + + pub fn finalize(&mut self) { + self.hb_buf.guess_segment_properties(); + } + + /// Convenience: shape `text` as a single run where every item is a + /// `(codepoint, cluster)` pair (cluster = cell x position). + pub fn shape_text( + &mut self, + face: Option<&Face>, + text: &[(u32, u32)], + ) -> Result<&[Cell], ShapeError> { + self.prepare(); + for &(cp, cluster) in text { + self.add_codepoint(cp, cluster); + } + self.finalize(); + self.shape(face) + } + + /// Shape the current run (as built through the hooks). `face` is the face + /// for the run's font index, or `None` if the font is a special-case + /// font (sprite), for which codepoint == glyph_index. + /// + /// The return value is only valid until the next shape call is called. + pub fn shape(&mut self, face: Option<&Face>) -> Result<&[Cell], ShapeError> { + // We only do shaping if the font is not a special-case. For special-case + // fonts, the codepoint == glyph_index so we don't need to run any shaping. + if let Some(face) = face { + let i = if face.quirks_disable_default_font_features { + // If we are disabling default font features we just offset + // our features by the hardcoded items because always + // add those at the beginning. + DEFAULT_FEATURES.len() + } else { + 0 + }; + + hb::shape(face.hb_font(), &mut self.hb_buf, &self.hb_feats[i..]); + } + + self.cell_buf.clear(); + + // If our buffer is empty, we short-circuit the rest of the work + // return nothing. + if self.hb_buf.is_empty() { + return Ok(&self.cell_buf); + } + let info = self.hb_buf.glyph_infos(); + let pos = self + .hb_buf + .glyph_positions() + .ok_or(ShapeError::HarfbuzzFailed)?; + + // This is perhaps not true somewhere, but we currently assume it is true. + // If it isn't true, I'd like to catch it and learn more. + debug_assert_eq!(info.len(), pos.len()); + + // This keeps track of the current x and y offsets (sum of advances) + // and the furthest cluster we've seen so far (max). + let mut run_offset = RunOffset::default(); + + // This keeps track of the cell starting x and cluster. + let mut cell_offset = CellOffset::default(); + + // Convert all our info/pos to cells and set it. + for (info_v, pos_v) in info.iter().zip(pos.iter()) { + // info_v.cluster is the index into our codepoints array. We use it + // to get the original cluster. + let index = info_v.cluster as usize; + // Our cluster is also our cell X position. If the cluster changes + // then we need to reset our current cell offsets. + let cluster = self.codepoints[index].cluster; + if cell_offset.cluster != cluster { + let is_after_glyph_from_current_or_next_clusters = cluster <= run_offset.cluster; + + let is_first_codepoint_in_cluster = if index == 0 { + true + } else { + self.codepoints[index - 1].cluster != cluster + }; + + // We need to reset the `cell_offset` at the start of a new + // cluster, but we do that conditionally if the codepoint + // `is_first_codepoint_in_cluster` and the cluster is not + // `is_after_glyph_from_current_or_next_clusters`, which is + // a heuristic to detect ligatures and avoid positioning + // glyphs that mark ligatures incorrectly. The idea is that + // if the first codepoint in a cluster doesn't appear in + // the stream, it's very likely that it combined with + // codepoints from a previous cluster into a ligature. + // Then, the subsequent codepoints are very likely marking + // glyphs that are placed relative to that ligature, so if + // we were to reset the `cell_offset` to align it with the + // grid, the positions would be off. The + // `!is_after_glyph_from_current_or_next_clusters` check is + // needed in case these marking glyphs come from a later + // cluster but are rendered first (see the Chakma and + // Bengali tests). In that case when we get to the + // codepoint that `is_first_codepoint_in_cluster`, but in a + // cluster that + // `is_after_glyph_from_current_or_next_clusters`, we don't + // want to reset to the grid and cause the positions to be + // off. (Note that we could go back and align the cells to + // the grid starting from the one from the cluster that + // rendered out of order, but that is more complicated so + // we don't do that for now. Also, it's TBD if there are + // exceptions to this heuristic for detecting ligatures, + // but using the logging below seems to show it works + // well.) + if is_first_codepoint_in_cluster && !is_after_glyph_from_current_or_next_clusters { + cell_offset = CellOffset { + cluster, + x: run_offset.x, + }; + } + } + + // Under both FreeType and CoreText the harfbuzz scale is + // in 26.6 fixed point units, so we round to the nearest + // whole value here. + let x_offset = run_offset.x - cell_offset.x + ((pos_v.x_offset + 0b100_000) >> 6); + let y_offset = run_offset.y + ((pos_v.y_offset + 0b100_000) >> 6); + + self.cell_buf.push(Cell { + x: cell_offset.cluster as u16, + x_offset: x_offset as i16, + y_offset: y_offset as i16, + glyph_index: info_v.codepoint, + }); + + // Add our advances to keep track of our run offsets. + // Advances apply to the NEXT cell. + // Under both FreeType and CoreText the harfbuzz scale is + // in 26.6 fixed point units, so we round to the nearest + // whole value here. + run_offset.x += (pos_v.x_advance + 0b100_000) >> 6; + run_offset.y += (pos_v.y_advance + 0b100_000) >> 6; + run_offset.cluster = run_offset.cluster.max(cluster); + } + + Ok(&self.cell_buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::face::{DesiredSize, FontData, Options}; + use crate::{Library, embedded}; + + fn text(s: &str) -> Vec<(u32, u32)> { + s.chars() + .enumerate() + .map(|(i, c)| (c as u32, i as u32)) + .collect() + } + + fn face(data: &'static [u8]) -> Face { + let lib = Library::init().unwrap(); + Face::init( + &lib, + FontData::Static(data), + Options::new(DesiredSize::new(12.0)), + ) + .unwrap() + } + + #[test] + fn shape_emoji_run_buffer_length() { + // "shape": thumbs up, thumbs up, skin tone modifier => 3 codepoints. + let f = face(embedded::INCONSOLATA); + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + s.prepare(); + s.add_codepoint(0x1F44D, 0); + s.add_codepoint(0x1F44D, 1); + s.add_codepoint(0x1F3FD, 1); + s.finalize(); + assert_eq!(3, s.hb_buf_len()); + s.shape(Some(&f)).unwrap(); + } + + #[test] + fn shape_inconsolata_ligs() { + let f = face(embedded::INCONSOLATA); + // Some of our tests rely on dlig being enabled by default. + let mut s = Shaper::new(&shape::Options { + features: vec!["dlig".to_string()], + }) + .unwrap(); + let cells = s.shape_text(Some(&f), &text(">=")).unwrap(); + assert_eq!(1, cells.len()); + let cells = s.shape_text(Some(&f), &text("===")).unwrap(); + assert_eq!(1, cells.len()); + } + + #[test] + fn shape_monaspace_ligs() { + let f = face(embedded::MONASPACE_NEON); + // Some of our tests rely on dlig being enabled by default. + let mut s = Shaper::new(&shape::Options { + features: vec!["dlig".to_string()], + }) + .unwrap(); + let cells = s.shape_text(Some(&f), &text("===")).unwrap(); + assert_eq!(1, cells.len()); + } + + #[test] + fn shape_ligatures_disabled_by_feature() { + let f = face(embedded::INCONSOLATA); + let mut s = Shaper::new(&shape::Options { + features: vec!["-liga".to_string()], + }) + .unwrap(); + let cells = s.shape_text(Some(&f), &text("===")).unwrap(); + assert_eq!(3, cells.len()); + } + + #[test] + fn shape_arabic_forced_ltr() { + let f = face(embedded::ARABIC); + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + let arabic = include_str!("../../testdata/arabic.txt"); + let line = arabic.lines().next().unwrap(); + let t = text(line); + let n = t.len(); + let cells = s.shape_text(Some(&f), &t).unwrap(); + assert_eq!(n, cells.len()); + let mut x = cells[0].x; + for cell in &cells[1..] { + assert_eq!(x + 1, cell.x); + x = cell.x; + } + } + + #[test] + fn shape_combining_characters() { + let f = face(embedded::INCONSOLATA); + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + // n + two combining diaeresis in cell 0, then 'a' in cell 1. + let t = [('n' as u32, 0), (0x0308, 0), (0x0308, 0), ('a' as u32, 1)]; + let cells = s.shape_text(Some(&f), &t).unwrap(); + assert_eq!(4, cells.len()); + assert_eq!(0, cells[0].x); + assert_eq!(0, cells[1].x); + assert_eq!(0, cells[2].x); + assert_eq!(1, cells[3].x); + } + + #[test] + fn shape_special_font_passthrough() { + // Special fonts (sprite) are not shaped: glyph index == codepoint. + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + let cells = s.shape_text(None, &[(0x2500, 0), (0x2502, 1)]).unwrap(); + assert_eq!(2, cells.len()); + assert_eq!(0x2500, cells[0].glyph_index); + assert_eq!(1, cells[1].x); + assert_eq!(0, cells[1].x_offset); + } + + #[test] + fn shape_empty_run() { + let f = face(embedded::INCONSOLATA); + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + assert_eq!(0, s.shape_text(Some(&f), &[]).unwrap().len()); + } + + #[test] + fn shape_with_empty_cells_in_between_clusters() { + let f = face(embedded::INCONSOLATA); + let mut s = Shaper::new(&shape::Options::default()).unwrap(); + // 'A' at cell 0, 'B' at cell 3. + let cells = s + .shape_text(Some(&f), &[('A' as u32, 0), ('B' as u32, 3)]) + .unwrap(); + assert_eq!(2, cells.len()); + assert_eq!(0, cells[0].x); + assert_eq!(3, cells[1].x); + } +} diff --git a/crates/ghostty-font/src/shaper/hb.rs b/crates/ghostty-font/src/shaper/hb.rs new file mode 100644 index 0000000..14e8f3d --- /dev/null +++ b/crates/ghostty-font/src/shaper/hb.rs @@ -0,0 +1,165 @@ +//! Minimal safe wrapper over the HarfBuzz buffer/shape API (the bits of +//! the Zig `harfbuzz` package that the shaper uses). +#![allow(unsafe_code)] + +use std::ptr::NonNull; + +use harfbuzz_sys as sys; + +use crate::face::freetype::HbFont; + +/// A HarfBuzz feature (`hb_feature_t`) applied globally. +pub type Feature = sys::hb_feature_t; + +pub const FEATURE_GLOBAL_START: u32 = 0; +pub const FEATURE_GLOBAL_END: u32 = u32::MAX; + +/// Build a global feature from a 4-byte tag and value. +pub fn global_feature(tag: [u8; 4], value: u32) -> Feature { + Feature { + tag: u32::from_be_bytes(tag), + value, + start: FEATURE_GLOBAL_START, + end: FEATURE_GLOBAL_END, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct GlyphInfo { + pub codepoint: u32, + pub cluster: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct GlyphPosition { + pub x_advance: i32, + pub y_advance: i32, + pub x_offset: i32, + pub y_offset: i32, +} + +/// An owned `hb_buffer_t`. +pub struct Buffer(NonNull); + +// SAFETY: the buffer is uniquely owned and HarfBuzz buffers have no thread +// affinity; all access goes through `&self`/`&mut self`. +unsafe impl Send for Buffer {} + +impl Buffer { + pub fn new() -> Option { + // SAFETY: hb_buffer_create always returns a valid (possibly the + // inert "empty") buffer; we check for null anyway. + NonNull::new(unsafe { sys::hb_buffer_create() }).map(Buffer) + } + + pub fn reset(&mut self) { + // SAFETY: self.0 is a valid buffer. + unsafe { sys::hb_buffer_reset(self.0.as_ptr()) } + } + + pub fn set_content_type_unicode(&mut self) { + // SAFETY: self.0 is a valid buffer. + unsafe { + sys::hb_buffer_set_content_type(self.0.as_ptr(), sys::HB_BUFFER_CONTENT_TYPE_UNICODE); + } + } + + pub fn set_cluster_level_characters(&mut self) { + // SAFETY: self.0 is a valid buffer. + unsafe { + sys::hb_buffer_set_cluster_level( + self.0.as_ptr(), + sys::HB_BUFFER_CLUSTER_LEVEL_CHARACTERS, + ); + } + } + + pub fn set_direction_ltr(&mut self) { + // SAFETY: self.0 is a valid buffer. + unsafe { sys::hb_buffer_set_direction(self.0.as_ptr(), sys::HB_DIRECTION_LTR) } + } + + pub fn add(&mut self, cp: u32, cluster: u32) { + // SAFETY: self.0 is a valid buffer. + unsafe { sys::hb_buffer_add(self.0.as_ptr(), cp, cluster) } + } + + pub fn guess_segment_properties(&mut self) { + // SAFETY: self.0 is a valid buffer. + unsafe { sys::hb_buffer_guess_segment_properties(self.0.as_ptr()) } + } + + pub fn len(&self) -> usize { + // SAFETY: self.0 is a valid buffer. + unsafe { sys::hb_buffer_get_length(self.0.as_ptr()) as usize } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Glyph infos (valid after shaping). + pub fn glyph_infos(&mut self) -> Vec { + let mut len: u32 = 0; + // SAFETY: self.0 is valid; the returned pointer addresses `len` + // initialized infos that live until the buffer is next mutated, and + // we copy them out immediately. + unsafe { + let ptr = sys::hb_buffer_get_glyph_infos(self.0.as_ptr(), &raw mut len); + if ptr.is_null() { + return Vec::new(); + } + std::slice::from_raw_parts(ptr, len as usize) + .iter() + .map(|i| GlyphInfo { + codepoint: i.codepoint, + cluster: i.cluster, + }) + .collect() + } + } + + /// Glyph positions (valid after shaping); None if unavailable. + pub fn glyph_positions(&mut self) -> Option> { + let mut len: u32 = 0; + // SAFETY: as for `glyph_infos`. + unsafe { + let ptr = sys::hb_buffer_get_glyph_positions(self.0.as_ptr(), &raw mut len); + if ptr.is_null() { + return None; + } + Some( + std::slice::from_raw_parts(ptr, len as usize) + .iter() + .map(|p| GlyphPosition { + x_advance: p.x_advance, + y_advance: p.y_advance, + x_offset: p.x_offset, + y_offset: p.y_offset, + }) + .collect(), + ) + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + // SAFETY: we own the buffer reference. + unsafe { sys::hb_buffer_destroy(self.0.as_ptr()) } + } +} + +/// Shape the buffer with the given font and features. +pub fn shape(font: &HbFont, buf: &mut Buffer, features: &[Feature]) { + // SAFETY: font and buffer pointers are valid; the feature slice is valid + // for `features.len()` entries. + unsafe { + sys::hb_shape( + font.as_ptr(), + buf.0.as_ptr(), + features.as_ptr(), + features.len() as u32, + ); + } +} diff --git a/crates/ghostty-font/src/shaper/mod.rs b/crates/ghostty-font/src/shaper/mod.rs new file mode 100644 index 0000000..7d90123 --- /dev/null +++ b/crates/ghostty-font/src/shaper/mod.rs @@ -0,0 +1,9 @@ +//! Text shapers. Port of `font/shaper/`. +//! +//! `run.zig` (RunIterator over terminal rows) is deferred (F2). + +pub mod cache; +pub mod feature; +pub mod harfbuzz; +mod hb; +pub mod noop; diff --git a/crates/ghostty-font/src/shaper/noop.rs b/crates/ghostty-font/src/shaper/noop.rs new file mode 100644 index 0000000..854e22a --- /dev/null +++ b/crates/ghostty-font/src/shaper/noop.rs @@ -0,0 +1,87 @@ +//! Shaper that doesn't do any shaping. Port of `font/shaper/noop.zig`. +//! Each individual codepoint is mapped directly to the detected text run +//! font's glyph index. + +use crate::face::freetype::Face; +use crate::shape::{self, Cell}; + +#[derive(Debug, Clone, Copy)] +struct Codepoint { + codepoint: u32, + cluster: u32, +} + +/// Shaper that doesn't do any shaping. +#[derive(Debug, Default)] +pub struct Shaper { + /// The codepoints of the current run. + codepoints: Vec, + + /// The shared memory used for shaping results. + cell_buf: Vec, +} + +impl Shaper { + pub fn new(_opts: &shape::Options) -> Shaper { + Shaper::default() + } + + pub fn end_frame(&self) {} + + // ---- RunIterator hooks ---- + + pub fn prepare(&mut self) { + self.codepoints.clear(); + } + + pub fn add_codepoint(&mut self, cp: u32, cluster: u32) { + self.codepoints.push(Codepoint { + codepoint: cp, + cluster, + }); + } + + pub fn finalize(&mut self) {} + + /// Shape the current run. `face` is `None` for special fonts (sprite), + /// whose codepoint == glyph index. + /// + /// Panics if a codepoint has no glyph in `face` (the run iterator + /// guarantees this never happens). + pub fn shape(&mut self, face: Option<&Face>) -> &[Cell] { + self.cell_buf.clear(); + for entry in &self.codepoints { + let glyph_index = match face { + // Special fonts aren't shaped and their codepoint == glyph. + None => entry.codepoint, + Some(face) => face + .glyph_index(entry.codepoint) + .expect("run iterator guarantees a glyph for every codepoint"), + }; + self.cell_buf.push(Cell { + x: entry.cluster as u16, + glyph_index, + ..Cell::default() + }); + } + &self.cell_buf + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn noop_special_passthrough() { + let mut s = Shaper::new(&shape::Options::default()); + s.prepare(); + s.add_codepoint(0x41, 0); + s.add_codepoint(0x42, 2); + s.finalize(); + let cells = s.shape(None); + assert_eq!(2, cells.len()); + assert_eq!(0x41, cells[0].glyph_index); + assert_eq!(2, cells[1].x); + } +} diff --git a/crates/ghostty-font/src/shared_grid.rs b/crates/ghostty-font/src/shared_grid.rs new file mode 100644 index 0000000..ee20f5b --- /dev/null +++ b/crates/ghostty-font/src/shared_grid.rs @@ -0,0 +1,539 @@ +//! This structure represents the state required to render a terminal +//! grid using the font subsystem. It is "shared" because it is able to +//! be shared across multiple surfaces. +//! +//! It is desirable for the grid state to be shared because the font +//! configuration for a set of surfaces is almost always the same and +//! font data is relatively memory intensive. Further, the font subsystem +//! should be read-heavy compared to write-heavy, so it handles concurrent +//! reads well. Going even further, the font subsystem should be very rarely +//! read at all since it should only be necessary when the grid actively +//! changes. +//! +//! SharedGrid does NOT support resizing, font-family changes, font removals +//! in collections, etc. Because the Grid is shared this would cause a +//! major disruption in the rendering of multiple surfaces (i.e. increasing +//! the font size in one would increase it in all). In many cases this isn't +//! desirable so to implement configuration changes the grid should be +//! reinitialized and all surfaces should switch over to using that one. + +#![allow(clippy::collapsible_if, clippy::if_not_else, clippy::float_cmp)] + +use std::collections::HashMap; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::atlas::{self, Atlas}; +use crate::codepoint_resolver::{CodepointResolver, RenderGlyphError}; +use crate::collection::{self, Collection, GetFaceError, Index, PresentationMode}; +use crate::face::{Constraint, ConstraintSize, RenderOptions}; +use crate::{Glyph, Metrics, Presentation, SpriteFace, Style}; + +/// The size of a grid cell in pixels. +// TODO(integration): replace with `renderer::CellSize` once the renderer +// crate exists. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CellSize { + pub width: u32, + pub height: u32, +} + +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error(transparent)] + UpdateMetrics(#[from] collection::UpdateMetricsError), +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + GetFace(#[from] GetFaceError), + #[error(transparent)] + Render(#[from] RenderGlyphError), +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Render { + pub glyph: Glyph, + pub presentation: Presentation, +} + +/// The glyph cache key. Like upstream (`GlyphKey.Packed`), only the render +/// option fields that affect the identity of the render are part of the key: +/// the grid metrics and the constraint are NOT (the constraint is a function +/// of the codepoint/font, and the metrics are fixed for the grid's lifetime). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct GlyphKey { + index: Index, + glyph: u32, + cell_width: u8, + thicken: bool, + thicken_strength: u8, + constraint_width: u8, +} + +impl GlyphKey { + fn new(index: Index, glyph: u32, opts: &RenderOptions) -> GlyphKey { + GlyphKey { + index, + glyph, + cell_width: opts.cell_width.unwrap_or(0) & 0b11, + thicken: opts.thicken, + thicken_strength: opts.thicken_strength, + constraint_width: opts.constraint_width & 0b11, + } + } +} + +/// The lock-protected state of the grid. Callers that need direct access +/// (for example the renderer uploading the atlases) take the guard from +/// [`SharedGrid::read`] / [`SharedGrid::write`]. +pub struct State { + /// Cache for codepoints to font indexes in a group. + pub codepoints: HashMap<(Style, u32, Option), Option>, + + /// Cache for glyph renders into the atlas. + glyphs: HashMap, + + /// The texture atlas to store renders in. The Glyph data in the glyphs + /// cache is dependent on the atlas matching. + pub atlas_grayscale: Atlas, + pub atlas_color: Atlas, + + /// The underlying resolver for font data, fallbacks, etc. The shared + /// grid takes ownership of the resolver. + pub resolver: CodepointResolver, + + /// The currently active grid metrics dictating the layout of the grid. + /// This is calculated based on the resolver and current fonts. + pub metrics: Metrics, +} + +pub struct SharedGrid { + /// The RwLock used to protect the shared grid. Callers are expected to use + /// this (through `read`/`write`) if they need to i.e. access the atlas + /// directly. Because callers can use this lock directly, maintainers need + /// to be extra careful to review call sites to ensure they are using the + /// lock correctly. + lock: RwLock, +} + +impl SharedGrid { + /// Initialize the grid. + /// + /// The resolver must have a collection that supports deferred loading + /// (`collection.load_options != None`). This is because we need the load + /// options data to determine grid metrics and setup our sprite font. + /// + /// SharedGrid always configures the sprite font. This struct is expected + /// to be used with a terminal grid and therefore the sprite font is + /// always necessary for correct rendering. + pub fn new(resolver: CodepointResolver) -> Result { + // We need to support loading options since we use the size data + assert!(resolver.collection.load_options.is_some()); + + let mut state = State { + // We set an initial capacity that can fit a good number of + // characters. This number was picked empirically based on my own + // terminal usage. + codepoints: HashMap::with_capacity(128), + glyphs: HashMap::with_capacity(128), + atlas_grayscale: Atlas::new(512, atlas::Format::Grayscale), + atlas_color: Atlas::new(512, atlas::Format::Bgra), + resolver, + // Loaded below + metrics: Metrics::calc(crate::metrics::FaceMetrics::default()), + }; + + // Initialize our metrics. + state.reload_metrics()?; + + Ok(SharedGrid { + lock: RwLock::new(state), + }) + } + + /// Take the shared (read) lock. + pub fn read(&self) -> RwLockReadGuard<'_, State> { + self.lock + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + /// Take the exclusive (write) lock. + pub fn write(&self) -> RwLockWriteGuard<'_, State> { + self.lock + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + /// Returns the grid cell size. + pub fn cell_size(&self) -> CellSize { + let st = self.read(); + CellSize { + width: st.metrics.cell_width, + height: st.metrics.cell_height, + } + } + + /// Returns a copy of the current grid metrics. + pub fn metrics(&self) -> Metrics { + self.read().metrics + } + + /// Get the font index for a given codepoint. This is cached. + /// + /// This always forces loading any deferred fonts since we assume that if + /// you're looking up an index that the caller plans to use the font. By + /// loading the font in this function we can ensure thread-safety on the + /// load without complicating future calls. + pub fn get_index( + &self, + cp: u32, + style: Style, + p: Option, + ) -> Result, Error> { + let key = (style, cp, p); + + // Fast path: the cache has the value. This is almost always true and + // only requires a read lock. + { + let st = self.read(); + if let Some(v) = st.codepoints.get(&key) { + return Ok(*v); + } + } + + // Slow path: we need to search this codepoint + let mut guard = self.write(); + let st = &mut *guard; + + // Try to get it, if it is now in the cache another thread beat us to it. + if let Some(v) = st.codepoints.get(&key) { + return Ok(*v); + } + + // Load a value and cache it. This even caches negative matches. + let value = st.resolver.get_index(cp, style, p); + st.codepoints.insert(key, value); + + if let Some(idx) = value { + // If the font is a sprite font then we don't need to preload + // because get_face doesn't work with special fonts. + if idx.special().is_none() { + // Load the face in case its deferred. If this fails then we + // would've failed to load it in the future anyways so we want + // to undo all the caching we did. + if let Err(err) = st.resolver.collection.get_face(idx) { + st.codepoints.remove(&key); + return Err(err.into()); + } + } + } + + Ok(value) + } + + /// Returns true if the given font index has the codepoint and presentation. + pub fn has_codepoint(&self, idx: Index, cp: u32, p: Option) -> bool { + let st = self.read(); + st.resolver.collection.has_codepoint( + idx, + cp, + match p { + Some(v) => PresentationMode::Explicit(v), + None => PresentationMode::Any, + }, + ) + } + + /// Render a codepoint. This uses the first font index that has the codepoint + /// and matches the presentation requested. If the codepoint cannot be found + /// in any font, an null render is returned. + pub fn render_codepoint( + &self, + cp: u32, + style: Style, + p: Option, + opts: RenderOptions, + ) -> Result, Error> { + // Note: we could optimize the below to use way less locking, but + // at the time of writing this codepath is only called for preedit + // text which is relatively rare and almost non-existent in multiple + // surfaces at the same time. + + // Get the font that has the codepoint + let Some(index) = self.get_index(cp, style, p)? else { + return Ok(None); + }; + + // Get the glyph for the font + let glyph_index = { + let mut guard = self.write(); + // (The face was preloaded by `get_index`; `get_face` is a no-op + // lookup then. We need `&mut` for the possible lazy load.) + let face = guard.resolver.collection.get_face(index)?; + match face.glyph_index(cp) { + Some(g) => g, + None => return Ok(None), + } + }; + + // Render + Ok(Some(self.render_glyph(index, glyph_index, opts)?)) + } + + /// Render a glyph index. This automatically determines the correct texture + /// atlas to use and caches the result. + pub fn render_glyph( + &self, + index: Index, + glyph_index: u32, + opts: RenderOptions, + ) -> Result { + let key = GlyphKey::new(index, glyph_index, &opts); + + // Fast path: the cache has the value. This is almost always true and + // only requires a read lock. + { + let st = self.read(); + if let Some(v) = st.glyphs.get(&key) { + return Ok(*v); + } + } + + // Slow path: we need to search this codepoint + let mut guard = self.write(); + let st = &mut *guard; + + if let Some(v) = st.glyphs.get(&key) { + return Ok(*v); + } + + // Get the presentation to determine what atlas to use. (Nothing is + // inserted into the cache until the render succeeds, which gives the + // same roll-back behaviour as upstream's errdefer.) + let p = st.resolver.get_presentation(index, glyph_index)?; + let atlas: &mut Atlas = match p { + Presentation::Text => &mut st.atlas_grayscale, + Presentation::Emoji => &mut st.atlas_color, + }; + + let mut render_opts = opts; + + // Always use these constraints for emoji. + if p == Presentation::Emoji { + render_opts.constraint = Constraint { + // Scale emoji to be as large as possible + // while preserving their aspect ratio. + size: ConstraintSize::Cover, + + // Center the emoji in its cells. + align_horizontal: crate::face::Align::Center, + align_vertical: crate::face::Align::Center, + + // Add a small bit of padding so the emoji + // doesn't quite touch the edges of the cells. + pad_left: 0.025, + pad_right: 0.025, + + ..Constraint::NONE + }; + } + + // Render into the atlas + let glyph = match st + .resolver + .render_glyph(atlas, index, glyph_index, render_opts) + { + Ok(g) => g, + // If the atlas is full, we resize it + Err(err) if err.is_atlas_full() => { + atlas.grow(atlas.size * 2); + st.resolver + .render_glyph(atlas, index, glyph_index, render_opts)? + } + Err(err) => return Err(err.into()), + }; + + // Cache and return + let render = Render { + glyph, + presentation: p, + }; + st.glyphs.insert(key, render); + Ok(render) + } +} + +impl State { + fn reload_metrics(&mut self) -> Result<(), collection::UpdateMetricsError> { + let collection: &mut Collection = &mut self.resolver.collection; + collection.update_metrics()?; + + self.metrics = collection.metrics.expect("set by update_metrics"); + + // Setup our sprite font. + self.resolver.sprite = Some(SpriteFace::new(self.metrics)); + Ok(()) + } + + /// Number of cached glyph renders (test/diagnostic helper). + pub fn glyph_cache_len(&self) -> usize { + self.glyphs.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collection::{AddOptions, LoadOptions, SizeAdjustment}; + use crate::face::{DesiredSize, Options}; + use crate::{Face, Library, embedded}; + + fn test_grid(lib: &Library) -> SharedGrid { + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib.clone())); + + c.add( + Face::init( + lib, + embedded::REGULAR.into(), + Options::new(DesiredSize { + points: 12.0, + xdpi: 96, + ydpi: 96, + }), + ) + .unwrap(), + AddOptions { + style: Style::Regular, + fallback: false, + size_adjustment: SizeAdjustment::None, + }, + ) + .unwrap(); + + SharedGrid::new(CodepointResolver::new(c)).unwrap() + } + + #[test] + fn get_index() { + let lib = Library::init().unwrap(); + let grid = test_grid(&lib); + + // Visible ASCII. + for i in 32u32..127 { + let idx = grid.get_index(i, Style::Regular, None).unwrap().unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(0, idx.idx); + assert!(grid.has_codepoint(idx, i, None)); + } + + // Do it again without a resolver set to ensure we only hit the cache + // (upstream sets the resolver to `undefined`; we swap in an empty one). + let old_resolver = std::mem::replace( + &mut grid.write().resolver, + CodepointResolver::new(Collection::new()), + ); + for i in 32u32..127 { + let idx = grid.get_index(i, Style::Regular, None).unwrap().unwrap(); + assert_eq!(Style::Regular, idx.style); + assert_eq!(0, idx.idx); + } + grid.write().resolver = old_resolver; + } + + #[test] + fn render_glyph_error_after_cache_insert_rolls_back_cache_entry() { + // Upstream uses a tripwire to fail `renderGlyph` after the cache entry + // was inserted. Here a failure is provoked with an invalid index + // (`get_presentation` fails); the cache must stay clean. + let lib = Library::init().unwrap(); + let grid = test_grid(&lib); + + let idx = grid + .get_index('A' as u32, Style::Regular, None) + .unwrap() + .unwrap(); + let glyph_index = { + let st = grid.read(); + st.resolver + .collection + .get_loaded_face(idx) + .unwrap() + .glyph_index('A' as u32) + .unwrap() + }; + + let render_opts = RenderOptions::new(grid.metrics()); + assert_eq!(0, grid.read().glyph_cache_len()); + + let bad = Index { + style: Style::Regular, + idx: 100, + }; + assert!(grid.render_glyph(bad, glyph_index, render_opts).is_err()); + assert_eq!(0, grid.read().glyph_cache_len()); + + // And a good render is cached. + let r = grid.render_glyph(idx, glyph_index, render_opts).unwrap(); + assert_eq!(Presentation::Text, r.presentation); + assert_eq!(1, grid.read().glyph_cache_len()); + assert_eq!(r, grid.render_glyph(idx, glyph_index, render_opts).unwrap()); + assert_eq!(1, grid.read().glyph_cache_len()); + } + + #[test] + fn render_codepoint_and_cell_size() { + let lib = Library::init().unwrap(); + let grid = test_grid(&lib); + let size = grid.cell_size(); + assert!(size.width > 0 && size.height > 0); + + let r = grid + .render_codepoint( + 'A' as u32, + Style::Regular, + None, + RenderOptions::new(grid.metrics()), + ) + .unwrap() + .unwrap(); + assert!(r.glyph.width > 0); + // Not in any font and no discovery. + assert!( + grid.render_codepoint( + 0x10FFFE, + Style::Regular, + None, + RenderOptions::new(grid.metrics()) + ) + .unwrap() + .is_none() + ); + } + + #[test] + fn atlas_grows_when_full() { + let lib = Library::init().unwrap(); + let grid = test_grid(&lib); + let metrics = grid.metrics(); + // Render sprite glyphs (box drawing) until the 512 atlas must grow. + for cp in 0x2500u32..0x2580 { + let idx = grid.get_index(cp, Style::Regular, None).unwrap().unwrap(); + grid.render_glyph(idx, cp, RenderOptions::new(metrics)) + .unwrap(); + } + assert!(grid.read().atlas_grayscale.size >= 512); + } + + #[test] + fn init_error_without_primary_face() { + // Analogue of upstream "init error": failure in `init` must surface + // as an error (no primary font to compute metrics from). + let lib = Library::init().unwrap(); + let mut c = Collection::new(); + c.load_options = Some(LoadOptions::new(lib)); + assert!(SharedGrid::new(CodepointResolver::new(c)).is_err()); + } +} diff --git a/crates/ghostty-font/src/sprite/face.rs b/crates/ghostty-font/src/sprite/face.rs index 8b89b84..9c30a71 100644 --- a/crates/ghostty-font/src/sprite/face.rs +++ b/crates/ghostty-font/src/sprite/face.rs @@ -26,22 +26,8 @@ use crate::atlas::{self, Atlas}; use crate::glyph::Glyph; use crate::metrics::Metrics; -/// Text/emoji presentation. -// TODO(integration): replace with the real `font::Presentation` once the -// rest of the font engine (face.zig / main.zig) is ported. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Presentation { - Text, - Emoji, -} - -/// The subset of `font.face.RenderOptions` that the sprite face uses. -// TODO(integration): replace with the real `font::face::RenderOptions`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct RenderOptions { - /// The number of grid cells this glyph will take up (0..=3, a `u2` in Zig). - pub cell_width: Option, -} +pub use crate::Presentation; +pub use crate::face::RenderOptions; /// Errors from [`Face::render_glyph`]. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] @@ -469,7 +455,14 @@ mod tests { for cp in cps { for cell_width in [None, Some(1), Some(2)] { let g = face - .render_glyph(&mut atlas, cp, RenderOptions { cell_width }) + .render_glyph( + &mut atlas, + cp, + RenderOptions { + cell_width, + ..RenderOptions::new(metrics) + }, + ) .unwrap_or_else(|e| panic!("cp {cp:X} {w}x{}: {e}", asc + desc)); // The glyph must stay within the padded canvas. let cw = metrics.cell_width * u32::from(cell_width.unwrap_or(1).max(1)); @@ -538,7 +531,7 @@ mod tests { .render_glyph( &mut atlas, Sprite::CursorBar as u32, - RenderOptions::default(), + RenderOptions::new(face.metrics), ) .unwrap(); // 1px bar placed at x = -1 (half its thickness rounded up). diff --git a/crates/ghostty-font/testdata/arabic.txt b/crates/ghostty-font/testdata/arabic.txt new file mode 100644 index 0000000..d450c76 --- /dev/null +++ b/crates/ghostty-font/testdata/arabic.txt @@ -0,0 +1,3 @@ +غريبه لاني عربي أبا عن جد +واتكلم الانجليزية بطلاقة اكثر من ٢٥ سنه +ومع هذا اجد العربيه افضل لان فيها الكثير من المفردات الاكثر دقه بالوصف diff --git a/docs/rewrite/notes/ghostty-font-F1.md b/docs/rewrite/notes/ghostty-font-F1.md new file mode 100644 index 0000000..57f2dd8 --- /dev/null +++ b/docs/rewrite/notes/ghostty-font-F1.md @@ -0,0 +1,94 @@ +# ghostty-font — F1 (faces, discovery, collection, resolver, shaper, shared grid) + +Linux-only backend (`fontconfig_freetype`): fontconfig discovery, FreeType +rasterization, HarfBuzz shaping. FFI via `freetype-sys`, `harfbuzz-sys`, +`yeslogic-fontconfig-sys` (the crates.io `fontconfig-sys` 2.11 does not parse with +current cargo). Crate lint is `unsafe_code = "deny"`; unsafe is confined to +`face/freetype/ft.rs`, `fontconfig.rs`, `shaper/hb.rs` (module-level allow, every +block has a `// SAFETY:` comment). Embedded fonts + licenses live in `res/` +(JetBrains Mono 2.304 variable/static [OFL], Symbols Nerd Font 3.4.0 [MIT], Noto +emoji and the test fonts from `ZIG/src/font/res`). + +## Module map (Zig -> Rust, `crates/ghostty-font/src/`) +| Zig | Rust | +|---|---| +| main.zig (Style, Presentation, sprite_index, options) | lib.rs, backend.rs (`backend.zig`) | +| library.zig | library.rs (`Library`, Clone/Arc, face-creation mutex) | +| face.zig | face.rs (`Options`, `DesiredSize`, `Variation(Id)`, `GlyphSize`, `RenderOptions`, `Constraint`, `FontData`) | +| face/freetype.zig | face/freetype.rs (+ `freetype/ft.rs` FFI, `freetype/resize.rs` = stbir_resize_uint8 equivalent) | +| DeferredFace.zig / discovery.zig | deferred_face.rs / discovery.rs (+ `fontconfig.rs` safe wrapper) | +| Collection.zig / CodepointResolver.zig / CodepointMap.zig | collection.rs / codepoint_resolver.rs / codepoint_map.rs | +| SharedGrid.zig | shared_grid.rs | +| embedded.zig | embedded.rs (`include_bytes!` from `res/`) | +| opentype.zig + opentype/* | opentype/{mod,head,hhea,os2,post,sfnt,svg}.rs | +| nerd_font_attributes.zig | nerd_font_attributes.rs (413 sorted ranges -> 276 constraints, binary search; generated mechanically) | +| shape.zig, shaper/{feature,Cache,noop,harfbuzz}.zig | shape.rs, shaper/{feature,cache,noop,harfbuzz,hb}.rs | +| (config types used by the font code) | config_types.rs (`FreetypeLoadFlags`, `FontSyntheticStyle`, `FontShapingBreak`) | + +W7's local `Presentation`/`RenderOptions` stand-ins in `sprite/face.rs` are replaced by +`crate::Presentation` / `crate::face::RenderOptions` (`RenderOptions::new(grid_metrics)`). + +## API overview for the renderer +```rust +use ghostty_font::{Library, Face, Collection, CodepointResolver, SharedGrid, Style, Presentation}; +use ghostty_font::face::{self, DesiredSize, FontData, RenderOptions}; +use ghostty_font::collection::{AddOptions, LoadOptions, Index, SizeAdjustment}; + +let lib = Library::init()?; // Clone; share it +let opts = face::Options::new(DesiredSize { points: 12.0, xdpi: 96, ydpi: 96 }); +let mut c = Collection::new(); +c.load_options = Some(LoadOptions::new(lib.clone())); // needed for deferred faces + SharedGrid +c.add(Face::init(&lib, FontData::Static(embedded::VARIABLE), opts)?, + AddOptions { style: Style::Regular, fallback: false, size_adjustment: SizeAdjustment::None })?; +// fonts found by name: Fontconfig::init().discover(&Descriptor{family:Some("..".into()), ..}) +// -> iterator of DeferredFace -> c.add_deferred(df, AddOptions{..}) +c.complete_styles(synthetic_style)?; // synthesize bold/italic if missing +let mut r = CodepointResolver::new(c); // + r.codepoint_map = ..., r.sprite = ... +let grid = SharedGrid::new(r)?; // computes Metrics, sprite face, atlases +``` +* Metrics: `grid.metrics()` / `grid.cell_size()` (cell metrics -> terminal grid size). +* Lookup: `grid.get_index(cp, Style, Option) -> Result>` (cached; + `Index` is (style, idx), `ghostty_font::SPRITE_INDEX` selects the sprite font). +* Render: `grid.render_glyph(index, glyph_index, RenderOptions{ constraint, constraint_width, cell_width, ..RenderOptions::new(metrics)}) + -> Render{glyph, presentation}` (cached, grows atlas on AtlasFull). `render_codepoint` = lookup+shape-less render. + `grid.read()/write()` give the `State` guard with `atlas_grayscale`, `atlas_color` (check `atlas.modified/resized` + atomics to upload to GL), `resolver`, `metrics`. +* Nerd-font icon constraints: the renderer must call `nerd_font_attributes::get_constraint(cp)` and pass it in + `RenderOptions.constraint` (as in Zig `renderer/generic.zig`; SharedGrid itself only adds the emoji constraint). +* Shaping: `shaper::harfbuzz::Shaper::new(&shape::Options{features})`; per run: + `prepare(); add_codepoint(cp, cluster)...; finalize(); shape(Some(&face))` (None for the sprite font) + -> `&[shape::Cell{x, x_offset, y_offset, glyph_index}]`, or `shape_text(face, &[(cp, cluster)])`. + `shaper::cache::Cache` caches cells per `shape::TextRun`. Shaping needs the face from the collection + (`collection.get_face(index)` under the grid write lock, as Zig does). +* Face: `get_metrics()` -> `metrics::FaceMetrics` -> `Metrics::calc`; `set_size`, `set_variations`, + `synthetic_bold/italic`, `glyph_index`, `is_color_glyph`, `render_glyph(&mut Atlas, glyph_index, RenderOptions)`. + +## Tests +139 tests pass in the crate (W7's 53 + this work). Zig tests ported: face.zig (Variation x2, Constraints), freetype.zig (9/9), +Collection (11/12; "face metrics" is web_canvas-only upstream), CodepointResolver 3/3, CodepointMap 1/1, +SharedGrid getIndex + render rollback (emulating the tripwire) + error test, discovery/DeferredFace fontconfig tests (CoreText/Wasm skipped), +opentype (head/hhea/os2/post/sfnt/svg), feature.zig, Cache.zig, shaper tests that need no terminal (shape with +embedded fonts: ligatures with `dlig` as in the Zig test shaper, arabic LTR, combining marks, special passthrough, -liga). +Skipped as Zig-only: allocator failure injection, Atlas.Wasm/web_canvas/coretext tests, refAllDecls. + +## Deferred / TODO(integration) +* shaper/run.zig (RunIterator/TextRun over terminal rows) and SharedGridSet.zig: not ported (need ghostty-terminal/config). The + 23 terminal-dependent harfbuzz shaper tests are deferred to F2: run iterator, run iterator: empty cells with background set, + shape, shape inconsolata ligs, shape monaspace ligs, shape arabic forced LTR, shape emoji width, shape emoji width long, + shape variation selector VS15, VS16, shape with empty cells in between, shape Combining characters, shape Devanagari string, + shape Tai Tham vowels, shape Tibetan characters, shape Tai Tham letters, shape Javanese ligatures, shape Chakma vowel sign, + shape Bengali ligatures, shape box glyphs, shape selection boundary, shape cursor boundary, shape cursor boundary and colored emoji, + shape cell attribute change. `shape::TextRun` is a local version without the `grid` field. +* `config_types.rs` mirrors config's `FreetypeLoadFlags`, `FontSyntheticStyle`, `FontShapingBreak`; `shared_grid::CellSize` + stands in for `renderer::CellSize`. +* ghostty-config should map `adjust-*` options through `Modifier::parse_cli` (W7 note) and font-codepoint-map into `CodepointMap`. + +## Deviations +* `Face` is `Send + Sync` (no unsafe impl); synthetic bold/italic copies share the original's `ft_mutex` (Zig allocated one per Face + but they shared one FT_Face). Unsupported pixel mode returns `RenderError::UnsupportedPixelMode` instead of panicking; bitmap + path checks the atlas format before resizing. `set_variations` caps at 32 axes without overrun. +* Collection aliases store the `Index` of the target entry. SharedGrid wraps `RwLock`; `Collection::get_loaded_face(&self)` is + an added read-lock path. +* freetype-sys lacks FT_Bitmap_*/TT_Header/TT_HoriHeader/FT_LOAD_NO_SVG: declared locally in `ft.rs`. +* stb resize replaced with an in-crate Catmull-Rom/Mitchell separable resampler (not bit-identical to stb). +* `face.rs` carries `#![allow(clippy::manual_midpoint)]` to keep `(a+b)/2` verbatim.