feat(font): port the Linux font engine

Ports the rest of ZIG/src/font for the freetype/fontconfig/harfbuzz
backend: face loading and rasterization (synthetic bold/italic, color
emoji bitmaps, glyph constraints for nerd font icons), fontconfig
discovery, DeferredFace, Collection, CodepointResolver, CodepointMap,
SharedGrid, opentype table parsers, the nerd font attribute table, and
the harfbuzz shaper with feature parsing and shaping cache.

Embedded fonts (JetBrains Mono 2.304 variable, Symbols Nerd Font 3.4.0,
Noto emoji) and the test fonts from Ghostty live in res/ with their OFL/
MIT licenses.

The crate opts into unsafe_code = "deny"; FFI is confined to ft.rs,
fontconfig.rs and hb.rs with SAFETY comments. Glyph resampling uses an
stb-style resampler that is not bit-identical to stb.

Deferred to the next font pass (needs ghostty-terminal/config):
shaper/run.zig, SharedGridSet.zig and the 23 terminal-driven shaper
tests. Config-facing types are mirrored locally in config_types.rs.

Test Plan:
- cargo test -p ghostty-font (139 tests)
This commit is contained in:
ddidderr committed 2026-10-11 22:13:06 +02:00
1 parent b9ac54e794
commit fc7bb5c025
64 files changed
+35316 -23

No files matched your search

Generated
+1
View File
@@ -874,6 +874,7 @@ dependencies = [
"ratatui",
"tempfile",
"thiserror 2.0.21",
"unicode-width",
]
[[package]]
+37 -2
View File
@@ -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"
+24
View File
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+19
View File
@@ -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.
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
+91
View File
@@ -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.
@@ -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 <https://openfontlicense.org/>.
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).
Binary file not shown.
Binary file not shown.
+22328
View File
@@ -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 <control>
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
Binary file not shown.
Binary file not shown.
+91
View File
@@ -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());
}
}
+139
View File
@@ -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<Entry>,
}
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<H: Hasher>(&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<H: Hasher>(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());
}
}
@@ -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<Discover>,
/// A map of codepoints to font requests for codepoint-level overrides.
pub codepoint_map: Option<CodepointMap>,
/// 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<SpriteFace>,
}
/// 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<u64, Option<Index>>);
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<Presentation>) -> Option<Index> {
// 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<Index> {
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(|_| "<error>".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<Index> {
// 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<Index> = 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<Presentation, GetFaceError> {
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<Glyph, RenderGlyphError> {
// 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);
}
}
+1481
View File
@@ -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<EntryOrAlias>; 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<Metrics>,
/// 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<FaceMetrics>,
/// 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<LoadOptions>,
}
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<Index, AddError> {
// 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<Index, AddDeferredError> {
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<Index, EntryError> {
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<Index> {
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<Face, SyntheticError> {
// 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<Face, SyntheticError> {
// 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<Special> {
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::<u16>());
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::<Face>(c.get_face(idx).unwrap());
let p2 = std::ptr::from_ref::<Face>(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::<Face>(c.get_face(idx).unwrap());
let p2 = std::ptr::from_ref::<Face>(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);
}
}
}
+59
View File
@@ -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 }
}
}
+170
View File
@@ -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<Variation>,
}
#[derive(Default)]
pub struct DeferredFace {
/// Fontconfig
pub fc: Option<Fontconfig>,
}
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<String, LoadError> {
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<String, LoadError> {
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<Face, LoadError> {
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<Presentation>) -> 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());
}
}
+278
View File
@@ -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<String>,
/// 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<String>,
/// 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<Variation>,
}
impl Hash for Descriptor {
/// Hash the descriptor with the given hasher. Covers the same fields
/// as the Zig implementation.
fn hash<H: Hasher>(&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<fc::Config>,
}
#[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<DiscoverIterator, DiscoverError> {
// 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<DiscoverIterator, DiscoverError> {
self.discover(desc)
}
}
pub struct DiscoverIterator {
config: Arc<fc::Config>,
pattern: fc::Pattern,
set: fc::FontSet,
variations: Vec<Variation>,
i: usize,
}
impl DiscoverIterator {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<DeferredFace>, 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));
}
}
+55
View File
@@ -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");
+872
View File
@@ -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<Vec<u8>> for FontData {
fn from(v: Vec<u8>) -> Self {
FontData::Shared(v.into())
}
}
impl From<Arc<[u8]>> 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<u8>,
/// 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<f64>,
/// 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),
);
}
}
}
+1448
View File
@@ -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<Mutex<()>>,
/// Our Library
lib: Library,
/// Keeps in-memory font data alive (FreeType does not copy it).
_data: Option<FontData>,
}
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<Face, FaceError> {
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<Face, FaceError> {
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<FontData>,
opts: Options,
) -> Result<Face, FaceError> {
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<FontData>,
opts: Options,
ft_mutex: Arc<Mutex<()>>,
) -> Result<Face, FaceError> {
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<u16> = 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<Face, FaceError> {
// 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<Face, FaceError> {
// 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<u32> {
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<Glyph, RenderError> {
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<f64>, Option<f64>) =
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<f64>, Option<f64>) =
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<f64>) = {
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<f64>, Option<f64>) = '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<f64> {
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<f64> = '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<Option<Vec<u8>>, 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());
}
}
+676
View File
@@ -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<Lib, FtError> {
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<u8>,
}
/// 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<u8>,
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<sys::FT_FaceRec>,
}
// 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<FtFace, FtError> {
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<FtFace, FtError> {
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<FtFace, FtError> {
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<FtFace, FtError> {
// 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<u32> {
// 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<u16> {
let p = self.sfnt_table(sys::FT_SFNT_HEAD).cast::<TtHeader>();
// 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<Post> {
let p = self
.sfnt_table(sys::FT_SFNT_POST)
.cast::<sys::TT_Postscript>();
// 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<Os2> {
let p = self.sfnt_table(sys::FT_SFNT_OS2).cast::<sys::TT_OS2>();
// 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<Hhea> {
let p = self.sfnt_table(sys::FT_SFNT_HHEA).cast::<TtHoriHeader>();
// 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<SfntName> {
let mut name = std::mem::MaybeUninit::<sys::FT_SfntName>::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<Option<Vec<u8>>, 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<Vec<VarAxis>, 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::<Vec<_>>()
};
// 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<Vec<i64>, FtError> {
let mut coords: Vec<c_long> = 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<c_long> = 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::<sys::FT_BBox>::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<Bitmap, FtError> {
let mut target = std::mem::MaybeUninit::<sys::FT_Bitmap>::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<HbFont> {
// 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<u8> {
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<harfbuzz_sys::hb_font_t>);
// 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()) };
}
}
@@ -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<f32>)> {
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<Vec<u8>> {
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]);
}
}
}
+438
View File
@@ -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<sys::FcCharSet>);
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<sys::FcLangSet>);
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<sys::FcPattern>);
// 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<String, Error> {
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<i32, Error> {
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<bool, Error> {
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<bool, Error> {
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<sys::FcFontSet>);
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<sys::FcConfig>);
// 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<Config> {
// 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<FontSet, Error> {
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<Pattern, Error> {
// 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<Config>;
#[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));
}
}
+71 -3
View File
@@ -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;
+67
View File
@@ -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<LibraryInner>,
}
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<Library, LibraryError> {
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());
}
}
@@ -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<Constraint> {
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());
}
}
+121
View File
@@ -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<Head, EndOfStream> {
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));
}
}
+105
View File
@@ -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<Hhea, EndOfStream> {
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
);
}
}
+57
View File
@@ -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<const N: usize>(&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<u16, EndOfStream> {
Ok(u16::from_be_bytes(self.bytes()?))
}
pub(crate) fn i16(&mut self) -> Result<i16, EndOfStream> {
Ok(i16::from_be_bytes(self.bytes()?))
}
pub(crate) fn u32(&mut self) -> Result<u32, EndOfStream> {
Ok(u32::from_be_bytes(self.bytes()?))
}
pub(crate) fn i32(&mut self) -> Result<i32, EndOfStream> {
Ok(i32::from_be_bytes(self.bytes()?))
}
pub(crate) fn i64(&mut self) -> Result<i64, EndOfStream> {
Ok(i64::from_be_bytes(self.bytes()?))
}
}
+286
View File
@@ -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<EndOfStream> 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<u32>,
pub ulCodePageRange2: Option<u32>,
// Version 2+
pub sxHeight: Option<i16>,
pub sCapHeight: Option<i16>,
pub usDefaultChar: Option<u16>,
pub usBreakChar: Option<u16>,
pub usMaxContext: Option<u16>,
// Version 5
pub usLowerOpticalPointSize: Option<u16>,
pub usUpperOpticalPointSize: Option<u16>,
}
impl OS2 {
pub fn init(data: &[u8]) -> Result<OS2, Os2Error> {
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));
}
}
+70
View File
@@ -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<Post, EndOfStream> {
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
);
}
}
+230
View File
@@ -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<EndOfStream> 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<TableRecord>,
}
/// 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<Sfnt<'a>, 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());
}
}
+146
View File
@@ -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<EndOfStream> 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<SVG<'a>, 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
);
}
}
+71
View File
@@ -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<String>,
}
/// 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,
}
+111
View File
@@ -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<u64, Vec<Cell>> 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<u64, Vec<Cell>, 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);
}
}
+474
View File
@@ -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<Feature> {
Self::from_bytes(s.as_bytes())
}
pub fn from_bytes(s: &[u8]) -> Option<Feature> {
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<Feature> {
let mut tag = [0u8; 4];
let mut tag_len = 0usize;
let mut value: Option<u32> = 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<Feature>,
}
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)]);
}
}
+400
View File
@@ -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<Cell>,
/// The features to use for shaping.
hb_feats: Vec<hb::Feature>,
/// 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<Codepoint>,
}
impl Shaper {
pub fn new(opts: &shape::Options) -> Result<Shaper, ShapeError> {
// 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);
}
}
+165
View File
@@ -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<sys::hb_buffer_t>);
// 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<Buffer> {
// 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<GlyphInfo> {
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<Vec<GlyphPosition>> {
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,
);
}
}
+9
View File
@@ -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;
+87
View File
@@ -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<Codepoint>,
/// The shared memory used for shaping results.
cell_buf: Vec<Cell>,
}
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);
}
}
+539
View File
@@ -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<Presentation>), Option<Index>>,
/// Cache for glyph renders into the atlas.
glyphs: HashMap<GlyphKey, Render>,
/// 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<State>,
}
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<SharedGrid, InitError> {
// 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<Presentation>,
) -> Result<Option<Index>, 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<Presentation>) -> 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<Presentation>,
opts: RenderOptions,
) -> Result<Option<Render>, 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<Render, Error> {
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());
}
}
+11 -18
View File
@@ -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<u8>,
}
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).
+3
View File
@@ -0,0 +1,3 @@
غريبه لاني عربي أبا عن جد
واتكلم الانجليزية بطلاقة اكثر من ٢٥ سنه
ومع هذا اجد العربيه افضل لان فيها الكثير من المفردات الاكثر دقه بالوصف
+94
View File
@@ -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<Presentation>) -> Result<Option<Index>>` (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<State>`; `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.