diff --git a/CMakeLists.txt b/CMakeLists.txt index bf097a7..152c6ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ add_library(plot_core src/render/shaders.cpp src/render/text.cpp src/render/text_atlas.cpp - src/render/bitmap_font.cpp + src/render/embedded_mono_font.cpp src/render/primitives.cpp src/render/buffer.cpp src/render/vertex_array.cpp diff --git a/include/plot/plot.hpp b/include/plot/plot.hpp index 252f468..9e3c823 100644 --- a/include/plot/plot.hpp +++ b/include/plot/plot.hpp @@ -35,7 +35,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/plot/plot/ticks.hpp b/include/plot/plot/ticks.hpp index 5728026..55b4233 100644 --- a/include/plot/plot/ticks.hpp +++ b/include/plot/plot/ticks.hpp @@ -23,7 +23,7 @@ enum class TickFormat { /// Writes an SI-prefixed rendering of `v` to `buf`, always NUL-terminated and /// never writing past `n` bytes (a too-small buffer truncates, it does not /// overflow). ASCII plus the UTF-8 micro sign U+00B5 (0xC2 0xB5) for 1e-6, so -/// `plot::render::BitmapFont` renders it instead of mojibake. +/// `plot::render::EmbeddedMonoFont` renders it instead of mojibake. /// /// `format_si(0.0)` is exactly `"0"`. Values use three significant digits, so /// `1234.0` renders as `1.23k`. The sign of a negative value is kept. A value diff --git a/include/plot/render/bitmap_font.hpp b/include/plot/render/bitmap_font.hpp deleted file mode 100644 index 8c7be53..0000000 --- a/include/plot/render/bitmap_font.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once -#include -#include -#include - -namespace plot::render { - -/// 8x8 bitmap font, the text backend of the renderer. -/// -/// The glyphs are 1-bit, one byte per row, bit 0 the leftmost pixel; the table -/// and its provenance are in `src/render/bitmap_font.cpp`. Nothing here needs a -/// font file, FreeType or stb_truetype: `plot_core` stays dependency-free. -/// -/// Metrics are proportional, not fixed width: an advance is the glyph's ink -/// width plus one pixel of spacing, floored at two pixels, derived from the -/// same bitmaps the atlas uploads, so a second width table cannot drift from the -/// artwork. Whitespace is a blank glyph with a nonzero advance, and every -/// codepoint without a glyph draws a visible box, so a missing glyph is obvious -/// rather than silently blank. -/// -/// Metrics are in unscaled font pixels, one per glyph texel, and the atlas is a -/// grid of `kGlyphPx` cells: every glyph's rect is its own cell, so a run drawn -/// at `s` pixels per font pixel measures `measure(utf8) * s` and sits in a cell -/// of `s * kGlyphPx` pixels. `TextBatch::add_text` takes that factor and applies -/// it to the pen it advances. -class BitmapFont final : public TextBackend { - public: - // Atlas geometry: a grid of `kGlyphPx` cells whose width is a whole number of - // them. A glyph's rect is its own cell, normalised by these sizes, so the grid - // is all the text pipeline has to know about the atlas. - static constexpr u32 kGlyphPx = 8; ///< glyph cell edge, texels - static constexpr u32 kFirstChar = 32; ///< U+0020 - static constexpr u32 kAsciiGlyphs = 95; ///< U+0020..U+007E - static constexpr u32 kDegreeSlot = kAsciiGlyphs; ///< U+00B0 DEGREE SIGN - static constexpr u32 kMicroSlot = kAsciiGlyphs + 1u; ///< U+00B5 MICRO SIGN - static constexpr u32 kOhmSlot = kAsciiGlyphs + 2u; ///< U+03A9 OHM - static constexpr u32 kFallbackSlot = kAsciiGlyphs + 3u; ///< missing-glyph box - static constexpr u32 kGlyphCount = kAsciiGlyphs + 4u; - static constexpr u32 kAtlasCols = 16; - static constexpr u32 kAtlasRows = 7; - static constexpr u32 kAtlasWidth = kAtlasCols * kGlyphPx; ///< 128 texels - static constexpr u32 kAtlasHeight = kAtlasRows * kGlyphPx; ///< 56 texels - - /// The atlas cell, normalised: one `kGlyphPx` square of the grid. Every glyph's - /// rect is this, and `cell_uv()` reports it -- one number, derived from the same - /// two constants `pack_atlas()` packs, so the grid the pipeline draws and the - /// grid the ink was written into cannot disagree. - static constexpr Vec2 kCellUV{static_cast(kGlyphPx) / static_cast(kAtlasWidth), - static_cast(kGlyphPx) / static_cast(kAtlasHeight)}; - static_assert(kAtlasCols * kAtlasRows >= kGlyphCount, "atlas too small for the glyph set"); - - BitmapFont() noexcept = default; - - /// Width of `utf8`, the sum of the glyph advances: zero for the empty string - /// and for null. The ASCII path allocates nothing. - f32 measure(const char* utf8) const override; - - /// Cell height in pixels (one glyph row of text occupies `height()` pixels). - f32 height() const override; - /// Top of the cell to the baseline, in pixels. - f32 ascent() const override; - - /// Advance of one ASCII character in pixels. A byte that is not printable - /// ASCII reports the missing-glyph box, matching what `measure` gives that - /// byte on its own. - f32 advance(char c) const noexcept; - - /// Advance of a decoded Unicode codepoint, in pixels. - f32 advance_codepoint(u32 codepoint) const noexcept; - - /// Glyph slot for a codepoint: printable ASCII is its own slot, the three SI - /// codepoints `format_si` emits map to real glyphs, and anything else maps to - /// the visible missing-glyph box. - static u32 glyph_index(u32 codepoint) noexcept; - /// The glyph for `codepoint`: its atlas cell as a normalised rect (see - /// `Glyph`), a zero bearing and its ink-derived advance. Never null -- a - /// codepoint with no artwork maps to the visible missing-glyph box, which is - /// what a label outside the coverage set should show. - const Glyph* glyph(u32 codepoint) const override; - - /// `kCellUV`: the 8x8 cell every glyph occupies. - Vec2 cell_uv() const override { return kCellUV; } - - /// The packed atlas, as `TextBackend` sees it: the bytes `atlas_image()` - /// returns, `kAtlasWidth` by `kAtlasHeight`, GL_R8-ready. Never null. - const u8* atlas_pixels() const override; - u32 atlas_width() const override; - u32 atlas_height() const override; - - /// Always zero: the atlas is a compile-time constant, so it never changes and - /// the renderer uploads it once. - u32 revision() const override; - - /// Rows in a glyph bitmap, always `kGlyphPx`. - u32 glyph_rows(char c) const noexcept; - - /// The `kGlyphPx` rows of one ASCII character's bitmap, top row first. - const u8* glyph_bitmap(char c) const noexcept; - - /// The `kGlyphPx` rows of a glyph slot. - static const u8* glyph_bitmap_of(u32 slot) noexcept; - - /// The packed atlas image, ready for `plot::gl::Texture::upload_r8`: rows of - /// `kAtlasWidth` bytes, top row first, 255 for ink and 0 for background - /// (straight alpha, no dilation or gamma applied). - static const u8* atlas_image() noexcept; - - /// Texel origin of a glyph slot in the atlas, in texels; `glyph()` gives the - /// same rectangle normalised, which is what the text pipeline stages. - static Vec2 atlas_origin(u32 slot) noexcept; - - private: - /// Every slot's rect, bearing and advance, derived from the bitmaps above at - /// compile time. This replaced a per-instance `advances_` array holding the - /// same numbers, so the advances the pen uses and the ones `glyph()` publishes - /// cannot drift apart. - static const Glyph* glyph_table() noexcept; -}; - -} // namespace plot::render diff --git a/include/plot/render/embedded_mono_font.hpp b/include/plot/render/embedded_mono_font.hpp new file mode 100644 index 0000000..2301dc9 --- /dev/null +++ b/include/plot/render/embedded_mono_font.hpp @@ -0,0 +1,107 @@ +#pragma once +#include +#include +#include + +namespace plot::render { + +/// The renderer's built-in text backend: an anti-aliased greyscale atlas of +/// Adwaita Mono, baked at authoring time and committed as data. +/// +/// There is no font file, FreeType or stb_truetype behind this: the coverage +/// bytes and the glyph table are generated by `tools/fonts/gen_atlas.py` into +/// `src/render/embedded_mono_font.cpp`, so `plot_core` stays dependency-free and +/// nothing rasterises at build or run time. That file carries the provenance -- +/// source face, its version, the em size, the supersample factor and the +/// coverage set -- and `gen_atlas.py --check` fails if it has drifted from its +/// input. Adwaita Mono is SIL OFL 1.1 (a copy is committed at +/// `licences/OFL-1.1.txt`); the class is named neutrally because OFL reserves +/// the font name for modified redistributions. +/// +/// **One cell, every glyph.** The atlas is a fixed-cell grid -- `cell_uv()` is +/// the contract, and the staging path asserts it per glyph. Each cell is the +/// face's maximum advance wide and its ascent-to-descent extent tall, and every +/// glyph is drawn into its cell with its bearing preserved, never trimmed to its +/// ink. Because the face is monospaced the pen advances by the same amount for +/// every glyph, which is the face's own `hmtx` advance and not an assumption. +/// +/// A codepoint outside the coverage set draws the baked fallback box, so a label +/// the atlas cannot spell is visible rather than silently blank. +class EmbeddedMonoFont final : public TextBackend { + public: + /// The size the atlas was baked at, in font pixels, and the coverage samples + /// per texel. Repeated here so a caller can reason about the artwork without + /// opening the generated table; the generated translation unit is the + /// authority and pins the same values. + static constexpr u32 kEmPx = 13; + static constexpr u32 kSupersample = 4; + + /// The codepoint value `codepoint_of_slot()` reports for the fallback slot. It + /// is deliberately not a Unicode scalar, so it can never collide with a + /// codepoint the atlas really holds. + static constexpr u32 kFallbackCodepoint = 0xFFFFFFFFu; + + EmbeddedMonoFont() noexcept = default; + + /// Width of `utf8` in font pixels: the sum of the glyphs' advances, zero for + /// the empty string and for null. Allocates nothing. + f32 measure(const char* utf8) const override; + + /// Cell height in font pixels: one line of text occupies this many pixels. + f32 height() const override; + /// Top of the cell to the baseline, in font pixels. + f32 ascent() const override; + /// `kEmPx`: the size the face was baked at. The renderer scales a run by + /// `Style::axis_font_px / em_px()`, so the same backend draws the size the + /// style asked for whatever it was baked at. + f32 em_px() const override; + + /// The glyph for `codepoint`: its atlas cell as a normalised rect, no extra + /// bearing -- the bearing is inside the cell's ink -- and its advance. Never + /// null: anything outside the coverage set maps to the fallback box. + const Glyph* glyph(u32 codepoint) const override; + + /// `(kCellW / atlas_width(), kCellH / atlas_height())`: the cell every glyph's + /// rect is. + Vec2 cell_uv() const override; + + /// The packed atlas, GL_R8-ready, rows of `atlas_width()` bytes, top row + /// first, one coverage byte per texel. Never null. + const u8* atlas_pixels() const override; + u32 atlas_width() const override; + u32 atlas_height() const override; + + /// Always zero: the atlas is a compile-time constant, so the renderer uploads + /// it once and never re-uploads. + u32 revision() const override; + + /// Advance of a decoded codepoint, in font pixels. A codepoint outside the + /// coverage set reports the fallback box's advance. + static f32 advance_codepoint(u32 codepoint) noexcept; + + /// Slot a codepoint maps to: printable ASCII is a direct index, the bakes SI + /// and dash codepoints are a short switch, and everything else is the fallback + /// box -- so a missing glyph is always visible, never blank. + static u32 glyph_index(u32 codepoint) noexcept; + + /// Number of cells in the atlas, the fallback included. + static u32 glyph_count() noexcept; + + /// The codepoint a slot was baked for, or `kFallbackCodepoint` for the + /// fallback slot. Out-of-range slots report the fallback too. + static u32 codepoint_of_slot(u32 slot) noexcept; + + /// The fixed cell size, in atlas texels. Every glyph's rect is exactly this. + static u32 cell_width() noexcept; + static u32 cell_height() noexcept; + + /// Texel origin of a slot's cell in the atlas; `glyph()` gives the same + /// rectangle normalised, which is what the text pipeline stages. + static Vec2 atlas_origin(u32 slot) noexcept; + + /// The packed atlas image: the bytes `atlas_pixels()` returns, as a + /// compile-time table. Never null. + static const u8* atlas_image() noexcept; +}; + +} // namespace plot::render diff --git a/include/plot/render/renderer.hpp b/include/plot/render/renderer.hpp index 41f3689..a9a3ace 100644 --- a/include/plot/render/renderer.hpp +++ b/include/plot/render/renderer.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -155,9 +155,11 @@ class Renderer { bool using_persistent_upload() const noexcept { return line_vao_.persistent() && text_vao_.persistent(); } - /// The axis text size the renderer actually draws with: `style().axis_font_px` - /// rounded to the nearest whole multiple of `render::BitmapFont::kGlyphPx`, - /// because a 1-bit glyph only scales cleanly by whole factors. + /// The axis text size the renderer actually draws with: the on-screen cell + /// height of the backend it draws with. `Style::axis_font_px` is an em size, so + /// with an ascent-to-descent cell it is this that a host reads back as the line + /// height it got -- and it is the backend's own `height()` scaled, so it always + /// agrees with the metrics the plot laid the labels out with. f32 axis_font_px() const noexcept { return font_backend_.cell_px(); } /// Writes the framebuffer's viewport region as a binary PPM (P6, 8 bits per @@ -174,23 +176,25 @@ class Renderer { bool dump_frame_ppm(const char* path, Status* st = nullptr) const; private: - /// The bitmap font as the plot layer sees it, at the size the text pipeline - /// draws it: `measure`/`height`/`ascent` scale with the glyph cell, so a label - /// can never be laid out for one size and drawn at another. The 8x8 font scales - /// by whole multiples of its cell only -- anything else lands a 1-bit glyph's - /// strokes unevenly -- so `reset()` picks the multiple nearest the requested - /// size. + /// The backend the plot layer sees, at the size the text pipeline draws it: + /// `measure`/`height`/`ascent` scale with the run, so a label can never be laid + /// out for one size and drawn at another. /// - /// It is also the backend the renderer uploads the atlas from, so it forwards - /// the whole interface: the metrics and the glyph bearings and advances are - /// scaled, while the artwork itself -- the atlas bytes, its size and its - /// revision -- is the font's own and passes through untouched. + /// It wraps a `TextBackend` and nothing more: the scale is + /// `axis_font_px / em_px()`, a plain ratio, so an anti-aliased face is drawn at + /// the size the style asked for rather than at a whole multiple of its cell, + /// and any backend -- the embedded font, a host's TrueType face -- is drawn the + /// same way. It forwards the whole interface: the metrics and the glyph + /// bearings and advances are scaled, while the artwork itself -- the atlas + /// bytes, its size and its revision -- is the backend's own and passes through + /// untouched. class AxisFont final : public render::TextBackend { public: - void reset(const render::BitmapFont& font, f32 requested_px) noexcept; + void reset(const render::TextBackend& font, f32 requested_px) noexcept; f32 measure(const char* utf8) const override; f32 height() const override; f32 ascent() const override; + f32 em_px() const override; /// The font's glyph at this wrapper's scale: the atlas rect is unchanged (it /// is scale-free) and the bearing and advance are multiplied by it, so one /// `Glyph` describes the glyph this backend draws. The result lives in the @@ -213,7 +217,7 @@ class Renderer { f32 scale() const noexcept { return scale_; } private: - const render::BitmapFont* font_ = nullptr; + const render::TextBackend* font_ = nullptr; f32 cell_px_ = 1.0f; f32 scale_ = 1.0f; /// `glyph()`'s scaled result, returned by pointer because the interface is @@ -280,7 +284,10 @@ class Renderer { void draw_measures(const Plot& plot, const PlotLayout& layout); void draw_annotations(const Plot& plot); - render::BitmapFont font_; + /// The built-in backend the renderer owns. Every use of it goes through the + /// `TextBackend` interface -- `self`-typed nowhere -- so swapping it for + /// another face is a change to this member alone. + render::EmbeddedMonoFont default_font_; AxisFont font_backend_; render::Style style_; diff --git a/include/plot/render/text.hpp b/include/plot/render/text.hpp index c683696..978859f 100644 --- a/include/plot/render/text.hpp +++ b/include/plot/render/text.hpp @@ -108,6 +108,18 @@ class TextBackend { /// text bottoms instead of cell bottoms. virtual f32 ascent() const = 0; + /// The face's em size in font pixels: the size the atlas was baked at, in the + /// same units as `height()` and `ascent()`. + /// + /// It exists so a run can be drawn at the size a caller asked for whatever the + /// backend was baked at: the renderer scales by `requested_px / em_px()`, so a + /// backend re-baked at another size -- or swapped for a different face -- keeps + /// drawing `Style::axis_font_px`, and the renderer never has to know which font + /// it holds. A backend with no meaningful em (one whose "font pixels" are an + /// arbitrary unit) returns something proportional, `height()` being the + /// natural choice. + virtual f32 em_px() const = 0; + /// The glyph for `codepoint`, or null when this backend has none -- the caller /// then draws nothing for it (a backend that wants a visible missing-glyph box /// publishes one as the glyph for the codepoints it cannot render). The diff --git a/licences/OFL-1.1.txt b/licences/OFL-1.1.txt new file mode 100644 index 0000000..6dc188d --- /dev/null +++ b/licences/OFL-1.1.txt @@ -0,0 +1,96 @@ +Copyright (c) 2016, The Inter Project Authors (https://github.com/rsms/inter) +Copyright (c) 2015-2025, Renzhi Li (aka. Belleve Invis, belleve@typeof.net) +Copyright (c) 2025, Jamie Gravendeel (https://jamie.garden) +Copyright (c) 2025, Florian Müllner + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/render/bitmap_font.cpp b/src/render/bitmap_font.cpp deleted file mode 100644 index d6d5fa0..0000000 --- a/src/render/bitmap_font.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include - -#include - -namespace plot::render { -namespace { - -// Glyph data and its provenance -// ----------------------------- -// The 95 printable ASCII glyphs (slots 0..94, U+0020..U+007E) are the -// `font8x8_basic` table of the font8x8 project by Daniel Hepper, released into -// the public domain. It is itself based on Marcel Sondaar's public-domain 8x8 -// VGA font (via the IBM public-domain VGA fonts). Fetched on 2026-09-27 from -// https://raw.githubusercontent.com/dhepper/font8x8/master/font8x8_basic.h -// The glyphs the plot layer's SI labels need come from the same project: -// U+00B0 DEGREE SIGN and U+00B5 MICRO SIGN from font8x8_ext_latin.h (which -// covers U+00A0..U+00FF), and U+03A9 GREEK CAPITAL LETTER OMEGA from -// font8x8_greek.h (which covers U+0390..U+03C9). -// The missing-glyph box in the last slot is authored here: a hollow 8x8 square. -// Nothing else in this table is hand-drawn, and none of it is a library: it is -// public-domain data, so `plot_core` still links nothing third-party. -// -// Row 0 is the top row and bit 0 is the leftmost pixel, one byte per row. - -constexpr u8 kGlyphBits[BitmapFont::kGlyphCount][BitmapFont::kGlyphPx] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // [ 0] U+0020 space - {0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // [ 1] U+0021 '!' - {0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // [ 2] U+0022 '"' - {0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // [ 3] U+0023 '#' - {0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // [ 4] U+0024 '$' - {0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // [ 5] U+0025 '%' - {0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // [ 6] U+0026 '&' - {0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // [ 7] U+0027 ''' - {0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // [ 8] U+0028 '(' - {0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // [ 9] U+0029 ')' - {0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // [10] U+002A '*' - {0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // [11] U+002B '+' - {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // [12] U+002C ',' - {0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // [13] U+002D '-' - {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // [14] U+002E '.' - {0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // [15] U+002F '/' - {0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // [16] U+0030 '0' - {0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // [17] U+0031 '1' - {0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // [18] U+0032 '2' - {0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // [19] U+0033 '3' - {0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // [20] U+0034 '4' - {0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // [21] U+0035 '5' - {0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // [22] U+0036 '6' - {0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // [23] U+0037 '7' - {0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // [24] U+0038 '8' - {0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // [25] U+0039 '9' - {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // [26] U+003A ':' - {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // [27] U+003B ';' - {0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // [28] U+003C '<' - {0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // [29] U+003D '=' - {0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // [30] U+003E '>' - {0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // [31] U+003F '?' - {0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // [32] U+0040 '@' - {0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // [33] U+0041 'A' - {0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // [34] U+0042 'B' - {0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // [35] U+0043 'C' - {0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // [36] U+0044 'D' - {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // [37] U+0045 'E' - {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // [38] U+0046 'F' - {0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // [39] U+0047 'G' - {0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // [40] U+0048 'H' - {0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // [41] U+0049 'I' - {0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // [42] U+004A 'J' - {0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // [43] U+004B 'K' - {0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // [44] U+004C 'L' - {0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // [45] U+004D 'M' - {0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // [46] U+004E 'N' - {0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // [47] U+004F 'O' - {0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // [48] U+0050 'P' - {0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // [49] U+0051 'Q' - {0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // [50] U+0052 'R' - {0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // [51] U+0053 'S' - {0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // [52] U+0054 'T' - {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // [53] U+0055 'U' - {0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // [54] U+0056 'V' - {0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // [55] U+0057 'W' - {0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // [56] U+0058 'X' - {0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // [57] U+0059 'Y' - {0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // [58] U+005A 'Z' - {0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // [59] U+005B '[' - {0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // [60] U+005C '\' - {0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // [61] U+005D ']' - {0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // [62] U+005E '^' - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // [63] U+005F '_' - {0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // [64] U+0060 '`' - {0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // [65] U+0061 'a' - {0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // [66] U+0062 'b' - {0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // [67] U+0063 'c' - {0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00}, // [68] U+0064 'd' - {0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // [69] U+0065 'e' - {0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00}, // [70] U+0066 'f' - {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // [71] U+0067 'g' - {0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // [72] U+0068 'h' - {0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // [73] U+0069 'i' - {0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // [74] U+006A 'j' - {0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // [75] U+006B 'k' - {0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // [76] U+006C 'l' - {0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // [77] U+006D 'm' - {0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // [78] U+006E 'n' - {0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // [79] U+006F 'o' - {0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // [80] U+0070 'p' - {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // [81] U+0071 'q' - {0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // [82] U+0072 'r' - {0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // [83] U+0073 's' - {0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // [84] U+0074 't' - {0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // [85] U+0075 'u' - {0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // [86] U+0076 'v' - {0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // [87] U+0077 'w' - {0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // [88] U+0078 'x' - {0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // [89] U+0079 'y' - {0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // [90] U+007A 'z' - {0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // [91] U+007B '{' - {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // [92] U+007C '|' - {0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // [93] U+007D '}' - {0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // [94] U+007E '~' - {0x1C, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00}, // [95] U+00B0 degree - {0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x03}, // [96] U+00B5 micro - {0x3E, 0x63, 0x63, 0x63, 0x36, 0x36, 0x77, 0x00}, // [97] U+03A9 ohm - {0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00}, // [98] missing-glyph box, authored here -}; - -/// Repacks the glyph-major table into the atlas image the text pipeline -/// uploads: one 8x8 cell per slot, 255 for ink, straight alpha, no dilation. -constexpr std::array pack_atlas() noexcept { - std::array image{}; - for (u32 slot = 0; slot < BitmapFont::kGlyphCount; ++slot) { - const u32 cell_x = (slot % BitmapFont::kAtlasCols) * BitmapFont::kGlyphPx; - const u32 cell_y = (slot / BitmapFont::kAtlasCols) * BitmapFont::kGlyphPx; - for (u32 y = 0; y < BitmapFont::kGlyphPx; ++y) { - const u8 bits = kGlyphBits[slot][y]; - for (u32 x = 0; x < BitmapFont::kGlyphPx; ++x) { - image[(cell_y + y) * BitmapFont::kAtlasWidth + cell_x + x] = - static_cast(((bits >> x) & 1u) != 0u ? 0xFFu : 0x00u); - } - } - } - return image; -} - -constexpr std::array kAtlasImage = - pack_atlas(); - -/// Span of set bits in a glyph's rows: 0 for a blank glyph such as space. -constexpr u32 ink_width(const u8* rows) noexcept { - u32 lo = BitmapFont::kGlyphPx; - u32 hi = 0; - bool any = false; - for (u32 y = 0; y < BitmapFont::kGlyphPx; ++y) { - for (u32 x = 0; x < BitmapFont::kGlyphPx; ++x) { - if (((rows[y] >> x) & 1u) != 0u) { - if (x < lo) { - lo = x; - } - if (x > hi) { - hi = x; - } - any = true; - } - } - } - return any ? hi - lo + 1u : 0u; -} - -/// The glyph table the pipeline stages from: one entry per slot, its rect at the -/// slot's place in the grid and one cell big -- the cell `cell_uv()` declares, -/// so a glyph's rect and the size the draw is told cannot disagree -- a zero -/// bearing (the ink sits at the cell's top-left) and the ink-derived advance. -/// Built from the same tables `pack_atlas()` packs. -constexpr std::array build_glyphs() noexcept { - std::array glyphs{}; - for (u32 slot = 0; slot < BitmapFont::kGlyphCount; ++slot) { - const f32 u0 = static_cast(slot % BitmapFont::kAtlasCols) * BitmapFont::kCellUV.x; - const f32 v0 = static_cast(slot / BitmapFont::kAtlasCols) * BitmapFont::kCellUV.y; - const u32 ink = ink_width(kGlyphBits[slot]); - const u32 spaced = ink + 1u; // one pixel of side bearing, floored at two - glyphs[slot] = Glyph{u0, v0, u0 + BitmapFont::kCellUV.x, v0 + BitmapFont::kCellUV.y, 0.0f, - 0.0f, static_cast(spaced < 2u ? 2u : spaced)}; - } - return glyphs; -} - -constexpr std::array kGlyphs = build_glyphs(); - -} // namespace - -const Glyph* BitmapFont::glyph_table() noexcept { return kGlyphs.data(); } - -f32 BitmapFont::measure(const char* utf8) const { - if (utf8 == nullptr) { - return 0.0f; - } - f32 width = 0.0f; - for (const char* p = utf8; *p != '\0';) { - width += advance_codepoint(next_codepoint(p)); - } - return width; -} - -const Glyph* BitmapFont::glyph(u32 codepoint) const { - // Every codepoint has a glyph: `glyph_index` maps whatever the font cannot - // draw -- including `kInvalidCodepoint` -- to the visible box. - return &glyph_table()[glyph_index(codepoint)]; -} - -const u8* BitmapFont::atlas_pixels() const { return atlas_image(); } - -u32 BitmapFont::atlas_width() const { return kAtlasWidth; } - -u32 BitmapFont::atlas_height() const { return kAtlasHeight; } - -u32 BitmapFont::revision() const { return 0u; } - -f32 BitmapFont::height() const { return static_cast(kGlyphPx); } - -f32 BitmapFont::ascent() const { return static_cast(kGlyphPx - 1u); } - -f32 BitmapFont::advance(char c) const noexcept { - return glyph_table()[glyph_index(static_cast(static_cast(c)))].advance; -} - -f32 BitmapFont::advance_codepoint(u32 codepoint) const noexcept { - return glyph_table()[glyph_index(codepoint)].advance; -} - -u32 BitmapFont::glyph_index(u32 codepoint) noexcept { - if (codepoint >= kFirstChar && codepoint < kFirstChar + kAsciiGlyphs) { - return codepoint - kFirstChar; - } - switch (codepoint) { - case 0x00B0u: - return kDegreeSlot; - case 0x00B5u: - return kMicroSlot; - case 0x03A9u: - return kOhmSlot; - default: - return kFallbackSlot; - } -} - -u32 BitmapFont::glyph_rows(char) const noexcept { return kGlyphPx; } - -const u8* BitmapFont::glyph_bitmap(char c) const noexcept { - return glyph_bitmap_of(glyph_index(static_cast(static_cast(c)))); -} - -const u8* BitmapFont::glyph_bitmap_of(u32 slot) noexcept { - return kGlyphBits[slot < kGlyphCount ? slot : kFallbackSlot]; -} - -const u8* BitmapFont::atlas_image() noexcept { return kAtlasImage.data(); } - -Vec2 BitmapFont::atlas_origin(u32 slot) noexcept { - const u32 s = slot < kGlyphCount ? slot : kFallbackSlot; - return Vec2{static_cast((s % kAtlasCols) * kGlyphPx), - static_cast((s / kAtlasCols) * kGlyphPx)}; -} - -} // namespace plot::render diff --git a/src/render/embedded_mono_font.cpp b/src/render/embedded_mono_font.cpp new file mode 100644 index 0000000..6cfa858 --- /dev/null +++ b/src/render/embedded_mono_font.cpp @@ -0,0 +1,2099 @@ +// GENERATED FILE -- do not edit by hand. +// +// Regenerate with: tools/fonts/gen_atlas.py +// The generator takes the source font, the em size and the supersample factor; +// its defaults bake the face this file holds. `tools/fonts/gen_atlas.py --check` +// regenerates into memory and fails if this file has drifted from its input. +// +// Provenance +// source font : AdwaitaMono-Regular.ttf +// font name : Adwaita Mono +// font version : Version 34.8.0; ttfautohint (v1.8.4) +// licence : SIL Open Font License 1.1 (licences/OFL-1.1.txt) +// units per em : 1000 +// em size : 13 px +// supersample : 4x (16 samples per texel) +// coverage : U+0020..U+007E, U+00B0, U+00B5, U+03A9, U+2013, U+2014, plus a fallback box +// cell : 8 x 17 texels, one row of 101 +// atlas : 808 x 17 texels +// +// The cell is the face's maximum advance wide and its ascent-to-descent extent +// tall; every glyph is drawn into that cell with its bearing preserved, never +// trimmed to its ink, because the pipeline stages one rect *origin* per glyph and +// carries one rect *size* for the whole draw (see render::TextBackend::cell_uv). +// Coverage is a plain supersampled coverage count, so a stroke's edges are grey +// and not a 1-bit threshold. + +#include + +#include +#include + +namespace plot::render { +namespace { + +// Geometry, all of it derived from the bake above by the generator. +constexpr u32 kEmPx = 13u; +constexpr u32 kSupersample = 4u; +constexpr u32 kUnitsPerEm = 1000u; +constexpr u32 kCellW = 8u; +constexpr u32 kCellH = 17u; +constexpr u32 kGlyphCount = 101u; +constexpr u32 kFallbackSlot = 100u; +constexpr u32 kAtlasW = kCellW * kGlyphCount; +constexpr u32 kAtlasH = kCellH; +constexpr u32 kAsciiFirst = 32u; +constexpr u32 kAsciiGlyphs = 95u; +constexpr u32 kFallbackCodepoint = 0xFFFFFFFFu; + +constexpr u32 kAscentUnits = 965u; +// Font pixels per font unit. The one non-integer factor, and the only place a +// float enters: everything else here is an integer. +constexpr f32 kScale = static_cast(kEmPx) / static_cast(kUnitsPerEm); +constexpr f32 kAscentPx = static_cast(kAscentUnits) * kScale; + +// The public header repeats the em size, the supersample factor and the +// fallback marker so a caller can read them without opening this table. +// These asserts are what make that a pin and not a copy: re-baking at another +// em or supersample factor, or moving the fallback marker, fails to compile +// until the header agrees, so the two can never silently disagree. +static_assert(kEmPx == EmbeddedMonoFont::kEmPx, + "embedded_mono_font.hpp kEmPx disagrees with the bake"); +static_assert(kSupersample == EmbeddedMonoFont::kSupersample, + "embedded_mono_font.hpp kSupersample disagrees with the bake"); +static_assert(kFallbackCodepoint == EmbeddedMonoFont::kFallbackCodepoint, + "embedded_mono_font.hpp kFallbackCodepoint disagrees with the bake"); + +/// The codepoint each slot was baked for, in slot order; the fallback slot +/// carries `kFallbackCodepoint`, which is not a Unicode scalar. +constexpr u32 kCodepoints[kGlyphCount] = { + 0x0020u, // [ 0] U+0020 + 0x0021u, // [ 1] U+0021 + 0x0022u, // [ 2] U+0022 + 0x0023u, // [ 3] U+0023 + 0x0024u, // [ 4] U+0024 + 0x0025u, // [ 5] U+0025 + 0x0026u, // [ 6] U+0026 + 0x0027u, // [ 7] U+0027 + 0x0028u, // [ 8] U+0028 + 0x0029u, // [ 9] U+0029 + 0x002Au, // [ 10] U+002A + 0x002Bu, // [ 11] U+002B + 0x002Cu, // [ 12] U+002C + 0x002Du, // [ 13] U+002D + 0x002Eu, // [ 14] U+002E + 0x002Fu, // [ 15] U+002F + 0x0030u, // [ 16] U+0030 + 0x0031u, // [ 17] U+0031 + 0x0032u, // [ 18] U+0032 + 0x0033u, // [ 19] U+0033 + 0x0034u, // [ 20] U+0034 + 0x0035u, // [ 21] U+0035 + 0x0036u, // [ 22] U+0036 + 0x0037u, // [ 23] U+0037 + 0x0038u, // [ 24] U+0038 + 0x0039u, // [ 25] U+0039 + 0x003Au, // [ 26] U+003A + 0x003Bu, // [ 27] U+003B + 0x003Cu, // [ 28] U+003C + 0x003Du, // [ 29] U+003D + 0x003Eu, // [ 30] U+003E + 0x003Fu, // [ 31] U+003F + 0x0040u, // [ 32] U+0040 + 0x0041u, // [ 33] U+0041 + 0x0042u, // [ 34] U+0042 + 0x0043u, // [ 35] U+0043 + 0x0044u, // [ 36] U+0044 + 0x0045u, // [ 37] U+0045 + 0x0046u, // [ 38] U+0046 + 0x0047u, // [ 39] U+0047 + 0x0048u, // [ 40] U+0048 + 0x0049u, // [ 41] U+0049 + 0x004Au, // [ 42] U+004A + 0x004Bu, // [ 43] U+004B + 0x004Cu, // [ 44] U+004C + 0x004Du, // [ 45] U+004D + 0x004Eu, // [ 46] U+004E + 0x004Fu, // [ 47] U+004F + 0x0050u, // [ 48] U+0050 + 0x0051u, // [ 49] U+0051 + 0x0052u, // [ 50] U+0052 + 0x0053u, // [ 51] U+0053 + 0x0054u, // [ 52] U+0054 + 0x0055u, // [ 53] U+0055 + 0x0056u, // [ 54] U+0056 + 0x0057u, // [ 55] U+0057 + 0x0058u, // [ 56] U+0058 + 0x0059u, // [ 57] U+0059 + 0x005Au, // [ 58] U+005A + 0x005Bu, // [ 59] U+005B + 0x005Cu, // [ 60] U+005C + 0x005Du, // [ 61] U+005D + 0x005Eu, // [ 62] U+005E + 0x005Fu, // [ 63] U+005F + 0x0060u, // [ 64] U+0060 + 0x0061u, // [ 65] U+0061 + 0x0062u, // [ 66] U+0062 + 0x0063u, // [ 67] U+0063 + 0x0064u, // [ 68] U+0064 + 0x0065u, // [ 69] U+0065 + 0x0066u, // [ 70] U+0066 + 0x0067u, // [ 71] U+0067 + 0x0068u, // [ 72] U+0068 + 0x0069u, // [ 73] U+0069 + 0x006Au, // [ 74] U+006A + 0x006Bu, // [ 75] U+006B + 0x006Cu, // [ 76] U+006C + 0x006Du, // [ 77] U+006D + 0x006Eu, // [ 78] U+006E + 0x006Fu, // [ 79] U+006F + 0x0070u, // [ 80] U+0070 + 0x0071u, // [ 81] U+0071 + 0x0072u, // [ 82] U+0072 + 0x0073u, // [ 83] U+0073 + 0x0074u, // [ 84] U+0074 + 0x0075u, // [ 85] U+0075 + 0x0076u, // [ 86] U+0076 + 0x0077u, // [ 87] U+0077 + 0x0078u, // [ 88] U+0078 + 0x0079u, // [ 89] U+0079 + 0x007Au, // [ 90] U+007A + 0x007Bu, // [ 91] U+007B + 0x007Cu, // [ 92] U+007C + 0x007Du, // [ 93] U+007D + 0x007Eu, // [ 94] U+007E + 0x00B0u, // [ 95] U+00B0 + 0x00B5u, // [ 96] U+00B5 + 0x03A9u, // [ 97] U+03A9 + 0x2013u, // [ 98] U+2013 + 0x2014u, // [ 99] U+2014 + 0xFFFFFFFFu, // [100] fallback box +}; + +/// The advance each slot contributes to the pen, in font units. The face is +/// monospaced, so these are all equal -- but the table is per glyph, because the +/// advance is the font's own `hmtx` value and not an assumption about the face. +constexpr u32 kAdvanceUnits[kGlyphCount] = { + 600u, // [ 0] U+0020 + 600u, // [ 1] U+0021 + 600u, // [ 2] U+0022 + 600u, // [ 3] U+0023 + 600u, // [ 4] U+0024 + 600u, // [ 5] U+0025 + 600u, // [ 6] U+0026 + 600u, // [ 7] U+0027 + 600u, // [ 8] U+0028 + 600u, // [ 9] U+0029 + 600u, // [ 10] U+002A + 600u, // [ 11] U+002B + 600u, // [ 12] U+002C + 600u, // [ 13] U+002D + 600u, // [ 14] U+002E + 600u, // [ 15] U+002F + 600u, // [ 16] U+0030 + 600u, // [ 17] U+0031 + 600u, // [ 18] U+0032 + 600u, // [ 19] U+0033 + 600u, // [ 20] U+0034 + 600u, // [ 21] U+0035 + 600u, // [ 22] U+0036 + 600u, // [ 23] U+0037 + 600u, // [ 24] U+0038 + 600u, // [ 25] U+0039 + 600u, // [ 26] U+003A + 600u, // [ 27] U+003B + 600u, // [ 28] U+003C + 600u, // [ 29] U+003D + 600u, // [ 30] U+003E + 600u, // [ 31] U+003F + 600u, // [ 32] U+0040 + 600u, // [ 33] U+0041 + 600u, // [ 34] U+0042 + 600u, // [ 35] U+0043 + 600u, // [ 36] U+0044 + 600u, // [ 37] U+0045 + 600u, // [ 38] U+0046 + 600u, // [ 39] U+0047 + 600u, // [ 40] U+0048 + 600u, // [ 41] U+0049 + 600u, // [ 42] U+004A + 600u, // [ 43] U+004B + 600u, // [ 44] U+004C + 600u, // [ 45] U+004D + 600u, // [ 46] U+004E + 600u, // [ 47] U+004F + 600u, // [ 48] U+0050 + 600u, // [ 49] U+0051 + 600u, // [ 50] U+0052 + 600u, // [ 51] U+0053 + 600u, // [ 52] U+0054 + 600u, // [ 53] U+0055 + 600u, // [ 54] U+0056 + 600u, // [ 55] U+0057 + 600u, // [ 56] U+0058 + 600u, // [ 57] U+0059 + 600u, // [ 58] U+005A + 600u, // [ 59] U+005B + 600u, // [ 60] U+005C + 600u, // [ 61] U+005D + 600u, // [ 62] U+005E + 600u, // [ 63] U+005F + 600u, // [ 64] U+0060 + 600u, // [ 65] U+0061 + 600u, // [ 66] U+0062 + 600u, // [ 67] U+0063 + 600u, // [ 68] U+0064 + 600u, // [ 69] U+0065 + 600u, // [ 70] U+0066 + 600u, // [ 71] U+0067 + 600u, // [ 72] U+0068 + 600u, // [ 73] U+0069 + 600u, // [ 74] U+006A + 600u, // [ 75] U+006B + 600u, // [ 76] U+006C + 600u, // [ 77] U+006D + 600u, // [ 78] U+006E + 600u, // [ 79] U+006F + 600u, // [ 80] U+0070 + 600u, // [ 81] U+0071 + 600u, // [ 82] U+0072 + 600u, // [ 83] U+0073 + 600u, // [ 84] U+0074 + 600u, // [ 85] U+0075 + 600u, // [ 86] U+0076 + 600u, // [ 87] U+0077 + 600u, // [ 88] U+0078 + 600u, // [ 89] U+0079 + 600u, // [ 90] U+007A + 600u, // [ 91] U+007B + 600u, // [ 92] U+007C + 600u, // [ 93] U+007D + 600u, // [ 94] U+007E + 600u, // [ 95] U+00B0 + 600u, // [ 96] U+00B5 + 600u, // [ 97] U+03A9 + 600u, // [ 98] U+2013 + 600u, // [ 99] U+2014 + 600u, // [100] fallback box +}; + +/// The packed atlas: one row of cells, top row first, straight coverage, 0 for +/// background and 255 for full ink. GL_R8-ready, no dilation, no gamma. +constexpr u8 kAtlas[kAtlasW * kAtlasH] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 4] U+0024 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 32] U+0040 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 61] U+005D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 74] U+006A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 91] U+007B row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 92] U+007C row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 93] U+007D row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 1 + 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, // [ 4] U+0024 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, // [ 8] U+0028 row 1 + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 15] U+002F row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 32] U+0040 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 1 + 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, // [ 59] U+005B row 1 + 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 1 + 0x00, 0x10, 0x40, 0x40, 0x40, 0x30, 0x00, 0x00, // [ 61] U+005D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 74] U+006A row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, // [ 91] U+007B row 1 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, // [ 92] U+007C row 1 + 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 93] U+007D row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 2 + 0x00, 0x20, 0x80, 0x00, 0x20, 0x80, 0x00, 0x00, // [ 2] U+0022 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 2 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 4] U+0024 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 2 + 0x00, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x00, // [ 7] U+0027 row 2 + 0x00, 0x00, 0x00, 0x00, 0x9F, 0xDF, 0x10, 0x00, // [ 8] U+0028 row 2 + 0x00, 0x20, 0xEF, 0x60, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x80, 0x00, // [ 15] U+002F row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 2 + 0x00, 0x00, 0x50, 0x80, 0x80, 0x30, 0x00, 0x00, // [ 32] U+0040 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 2 + 0x00, 0x00, 0xFF, 0xBF, 0xBF, 0xBF, 0x00, 0x00, // [ 59] U+005B row 2 + 0x00, 0xAF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 2 + 0x00, 0x30, 0xBF, 0xBF, 0xCF, 0xBF, 0x00, 0x00, // [ 61] U+005D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 2 + 0x00, 0x20, 0x9F, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 74] U+006A row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 2 + 0x00, 0x00, 0x00, 0x20, 0xDF, 0xCF, 0x00, 0x00, // [ 91] U+007B row 2 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 2 + 0x00, 0x30, 0xDF, 0xCF, 0x10, 0x00, 0x00, 0x00, // [ 93] U+007D row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 2 + 0x00, 0x00, 0x10, 0x70, 0x60, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 2 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 3 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 3 + 0x00, 0x40, 0xFF, 0x00, 0x40, 0xFF, 0x00, 0x00, // [ 2] U+0022 row 3 + 0x00, 0x00, 0x10, 0xAF, 0x00, 0x40, 0x8F, 0x00, // [ 3] U+0023 row 3 + 0x00, 0x00, 0x60, 0xBF, 0xBF, 0x40, 0x00, 0x00, // [ 4] U+0024 row 3 + 0x50, 0xDF, 0xDF, 0x50, 0x00, 0x30, 0xDF, 0x10, // [ 5] U+0025 row 3 + 0x00, 0x20, 0xDF, 0xCF, 0xEF, 0x30, 0x00, 0x00, // [ 6] U+0026 row 3 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 7] U+0027 row 3 + 0x00, 0x00, 0x00, 0x9F, 0xBF, 0x10, 0x00, 0x00, // [ 8] U+0028 row 3 + 0x00, 0x00, 0x10, 0xCF, 0x60, 0x00, 0x00, 0x00, // [ 9] U+0029 row 3 + 0x00, 0x00, 0x00, 0x8F, 0x80, 0x00, 0x00, 0x00, // [ 10] U+002A row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x20, 0x00, // [ 15] U+002F row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 16] U+0030 row 3 + 0x00, 0x00, 0x10, 0xCF, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 18] U+0032 row 3 + 0x00, 0x20, 0xCF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 19] U+0033 row 3 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, // [ 21] U+0035 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 22] U+0036 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 23] U+0037 row 3 + 0x00, 0x10, 0xBF, 0xDF, 0xEF, 0x9F, 0x00, 0x00, // [ 24] U+0038 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 25] U+0039 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 31] U+003F row 3 + 0x00, 0x9F, 0xBF, 0x70, 0x70, 0xDF, 0x60, 0x00, // [ 32] U+0040 row 3 + 0x00, 0x00, 0x00, 0xDF, 0x9F, 0x00, 0x00, 0x00, // [ 33] U+0041 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xEF, 0x80, 0x00, 0x00, // [ 34] U+0042 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 35] U+0043 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xDF, 0x30, 0x00, 0x00, // [ 36] U+0044 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 37] U+0045 row 3 + 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 38] U+0046 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 39] U+0047 row 3 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 3 + 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, 0x00, // [ 41] U+0049 row 3 + 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0x40, 0x00, // [ 42] U+004A row 3 + 0x00, 0x80, 0x80, 0x00, 0x00, 0xBF, 0x80, 0x00, // [ 43] U+004B row 3 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 3 + 0x40, 0xFF, 0x80, 0x00, 0x00, 0xBF, 0xFF, 0x00, // [ 45] U+004D row 3 + 0x00, 0xBF, 0xAF, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 46] U+004E row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 47] U+004F row 3 + 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xBF, 0x10, 0x00, // [ 48] U+0050 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 49] U+0051 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xAF, 0x10, 0x00, // [ 50] U+0052 row 3 + 0x00, 0x30, 0xDF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 51] U+0053 row 3 + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 52] U+0054 row 3 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 3 + 0x10, 0xFF, 0x10, 0x00, 0x00, 0x40, 0xDF, 0x00, // [ 54] U+0056 row 3 + 0x40, 0xBF, 0x00, 0xAF, 0x80, 0x00, 0xFF, 0x10, // [ 55] U+0057 row 3 + 0x00, 0xBF, 0x60, 0x00, 0x00, 0x9F, 0x9F, 0x00, // [ 56] U+0058 row 3 + 0x00, 0xDF, 0x50, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 57] U+0059 row 3 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 58] U+005A row 3 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 3 + 0x00, 0x60, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 3 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 3 + 0x00, 0x00, 0x10, 0xBF, 0xAF, 0x00, 0x00, 0x00, // [ 62] U+005E row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 3 + 0x00, 0x00, 0x9F, 0xAF, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 3 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 68] U+0064 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 3 + 0x00, 0x00, 0x00, 0x60, 0xEF, 0xFF, 0xFF, 0x00, // [ 70] U+0066 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 3 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 3 + 0x00, 0x00, 0x00, 0xAF, 0xAF, 0x00, 0x00, 0x00, // [ 73] U+0069 row 3 + 0x00, 0x00, 0x00, 0x20, 0xEF, 0x50, 0x00, 0x00, // [ 74] U+006A row 3 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 3 + 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 3 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 3 + 0x00, 0x00, 0x00, 0x9F, 0x8F, 0x00, 0x00, 0x00, // [ 91] U+007B row 3 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 3 + 0x00, 0x00, 0x00, 0xBF, 0x70, 0x00, 0x00, 0x00, // [ 93] U+007D row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 3 + 0x00, 0x00, 0x80, 0x9F, 0xBF, 0x50, 0x00, 0x00, // [ 95] U+00B0 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 3 + 0x00, 0x20, 0xCF, 0xDF, 0xEF, 0xBF, 0x10, 0x00, // [ 97] U+03A9 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 3 + 0x20, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, // [100] fallback box row 3 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 4 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 4 + 0x00, 0x40, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, // [ 2] U+0022 row 4 + 0x00, 0x00, 0x50, 0xBF, 0x00, 0x80, 0x80, 0x00, // [ 3] U+0023 row 4 + 0x00, 0x8F, 0xCF, 0xAF, 0x8F, 0xEF, 0x50, 0x00, // [ 4] U+0024 row 4 + 0x80, 0x80, 0x40, 0xBF, 0x00, 0xBF, 0x50, 0x00, // [ 5] U+0025 row 4 + 0x00, 0x80, 0x8F, 0x00, 0x60, 0xAF, 0x00, 0x00, // [ 6] U+0026 row 4 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 7] U+0027 row 4 + 0x00, 0x00, 0x40, 0xDF, 0x10, 0x00, 0x00, 0x00, // [ 8] U+0028 row 4 + 0x00, 0x00, 0x00, 0x30, 0xEF, 0x20, 0x00, 0x00, // [ 9] U+0029 row 4 + 0x00, 0xAF, 0x50, 0x80, 0x70, 0x70, 0x9F, 0x00, // [ 10] U+002A row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 4 + 0x00, 0x00, 0x00, 0x00, 0x60, 0xCF, 0x00, 0x00, // [ 15] U+002F row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 16] U+0030 row 4 + 0x00, 0x30, 0xDF, 0x9F, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 4 + 0x00, 0xAF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 18] U+0032 row 4 + 0x00, 0x9F, 0x80, 0x00, 0x00, 0xAF, 0x70, 0x00, // [ 19] U+0033 row 4 + 0x00, 0x00, 0x00, 0x40, 0xFF, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x9F, 0x9F, 0x00, // [ 22] U+0036 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xAF, 0x70, 0x00, // [ 23] U+0037 row 4 + 0x00, 0x9F, 0xAF, 0x00, 0x10, 0xDF, 0x60, 0x00, // [ 24] U+0038 row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 25] U+0039 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x60, 0x00, // [ 28] U+003C row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 4 + 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 4 + 0x00, 0xAF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 31] U+003F row 4 + 0x30, 0xDF, 0x00, 0x00, 0x00, 0x20, 0xEF, 0x00, // [ 32] U+0040 row 4 + 0x00, 0x00, 0x20, 0xDF, 0xEF, 0x00, 0x00, 0x00, // [ 33] U+0041 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x20, 0xDF, 0x50, 0x00, // [ 34] U+0042 row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 35] U+0043 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x60, 0xEF, 0x10, 0x00, // [ 36] U+0044 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 4 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 39] U+0047 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 4 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 4 + 0x00, 0x80, 0x80, 0x00, 0x70, 0xBF, 0x00, 0x00, // [ 43] U+004B row 4 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 4 + 0x40, 0xDF, 0xBF, 0x00, 0x00, 0xDF, 0xFF, 0x00, // [ 45] U+004D row 4 + 0x00, 0xBF, 0xDF, 0x20, 0x00, 0x80, 0xBF, 0x00, // [ 46] U+004E row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 47] U+004F row 4 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x9F, 0x9F, 0x00, // [ 48] U+0050 row 4 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 49] U+0051 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0xAF, 0x80, 0x00, // [ 50] U+0052 row 4 + 0x00, 0xAF, 0x80, 0x00, 0x00, 0xAF, 0x8F, 0x00, // [ 51] U+0053 row 4 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 4 + 0x00, 0xCF, 0x40, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 54] U+0056 row 4 + 0x30, 0xBF, 0x00, 0xBF, 0x80, 0x00, 0xFF, 0x00, // [ 55] U+0057 row 4 + 0x00, 0x40, 0xDF, 0x00, 0x20, 0xFF, 0x20, 0x00, // [ 56] U+0058 row 4 + 0x00, 0x70, 0xAF, 0x00, 0x00, 0xDF, 0x40, 0x00, // [ 57] U+0059 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0x60, 0x00, // [ 58] U+005A row 4 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 4 + 0x00, 0x00, 0xEF, 0x20, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 4 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 4 + 0x00, 0x10, 0xCF, 0x70, 0x9F, 0x9F, 0x00, 0x00, // [ 62] U+005E row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 4 + 0x00, 0x00, 0x00, 0xCF, 0x8F, 0x00, 0x00, 0x00, // [ 64] U+0060 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 68] U+0064 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 4 + 0x00, 0x00, 0x00, 0xEF, 0x40, 0x00, 0x00, 0x00, // [ 70] U+0066 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 4 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 4 + 0x00, 0x00, 0x00, 0x70, 0x70, 0x00, 0x00, 0x00, // [ 73] U+0069 row 4 + 0x00, 0x00, 0x00, 0x10, 0x8F, 0x30, 0x00, 0x00, // [ 74] U+006A row 4 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 4 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 4 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 4 + 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, // [ 91] U+007B row 4 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 4 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 93] U+007D row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 4 + 0x00, 0x00, 0x80, 0x8F, 0xBF, 0x50, 0x00, 0x00, // [ 95] U+00B0 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 4 + 0x00, 0xAF, 0x8F, 0x00, 0x00, 0xBF, 0x70, 0x00, // [ 97] U+03A9 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 4 + 0x40, 0xFF, 0xBF, 0xBF, 0xBF, 0xBF, 0xEF, 0x80, // [100] fallback box row 4 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 5 + 0x00, 0x40, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, // [ 2] U+0022 row 5 + 0x00, 0x80, 0xBF, 0xBF, 0x80, 0xDF, 0x9F, 0x40, // [ 3] U+0023 row 5 + 0x00, 0xBF, 0x40, 0x80, 0x40, 0x60, 0x80, 0x00, // [ 4] U+0024 row 5 + 0x80, 0x80, 0x40, 0xBF, 0x50, 0xBF, 0x00, 0x00, // [ 5] U+0025 row 5 + 0x00, 0x80, 0x8F, 0x00, 0x60, 0xAF, 0x00, 0x00, // [ 6] U+0026 row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 7] U+0027 row 5 + 0x00, 0x00, 0xAF, 0x70, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 5 + 0x00, 0x00, 0x00, 0x00, 0x9F, 0x70, 0x00, 0x00, // [ 9] U+0029 row 5 + 0x00, 0x30, 0xBF, 0xEF, 0xEF, 0x9F, 0x20, 0x00, // [ 10] U+002A row 5 + 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // [ 11] U+002B row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 5 + 0x00, 0x00, 0x00, 0x00, 0xAF, 0x60, 0x00, 0x00, // [ 15] U+002F row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 16] U+0030 row 5 + 0x00, 0x20, 0x30, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 5 + 0x00, 0x40, 0x10, 0x00, 0x00, 0x70, 0xBF, 0x00, // [ 18] U+0032 row 5 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x80, 0xAF, 0x00, // [ 19] U+0033 row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x10, 0x30, 0x00, // [ 22] U+0036 row 5 + 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0x20, 0x00, // [ 23] U+0037 row 5 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x80, 0x80, 0x00, // [ 24] U+0038 row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 25] U+0039 row 5 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, // [ 26] U+003A row 5 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, // [ 27] U+003B row 5 + 0x00, 0x00, 0x00, 0x00, 0x80, 0xEF, 0x60, 0x00, // [ 28] U+003C row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 5 + 0x00, 0x80, 0xEF, 0x60, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 5 + 0x00, 0x40, 0x10, 0x00, 0x00, 0x80, 0xAF, 0x00, // [ 31] U+003F row 5 + 0x40, 0xBF, 0x00, 0x80, 0x50, 0x00, 0xFF, 0x00, // [ 32] U+0040 row 5 + 0x00, 0x00, 0x60, 0x9F, 0xCF, 0x30, 0x00, 0x00, // [ 33] U+0041 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x8F, 0x80, 0x00, // [ 34] U+0042 row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 35] U+0043 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x9F, 0x70, 0x00, // [ 36] U+0044 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 5 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 39] U+0047 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 5 + 0x00, 0x80, 0x80, 0x20, 0xDF, 0x20, 0x00, 0x00, // [ 43] U+004B row 5 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 5 + 0x40, 0xBF, 0xDF, 0x00, 0x20, 0xBF, 0xFF, 0x00, // [ 45] U+004D row 5 + 0x00, 0xBF, 0x9F, 0x80, 0x00, 0x80, 0xBF, 0x00, // [ 46] U+004E row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 47] U+004F row 5 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 48] U+0050 row 5 + 0x00, 0xFF, 0x20, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 49] U+0051 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 50] U+0052 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 51] U+0053 row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 5 + 0x00, 0x8F, 0x80, 0x00, 0x00, 0xBF, 0x60, 0x00, // [ 54] U+0056 row 5 + 0x00, 0xFF, 0x00, 0xAF, 0xBF, 0x20, 0xBF, 0x00, // [ 55] U+0057 row 5 + 0x00, 0x00, 0xBF, 0x60, 0x8F, 0x9F, 0x00, 0x00, // [ 56] U+0058 row 5 + 0x00, 0x10, 0xEF, 0x20, 0x50, 0xCF, 0x00, 0x00, // [ 57] U+0059 row 5 + 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, 0x00, // [ 58] U+005A row 5 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 5 + 0x00, 0x00, 0x9F, 0x80, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 5 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 5 + 0x00, 0x9F, 0x9F, 0x00, 0x10, 0xCF, 0x80, 0x00, // [ 62] U+005E row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 5 + 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, // [ 64] U+0060 row 5 + 0x00, 0x00, 0x20, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 65] U+0061 row 5 + 0x00, 0xBF, 0x40, 0x30, 0x40, 0x20, 0x00, 0x00, // [ 66] U+0062 row 5 + 0x00, 0x00, 0x10, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 67] U+0063 row 5 + 0x00, 0x00, 0x20, 0x40, 0x20, 0x80, 0xBF, 0x00, // [ 68] U+0064 row 5 + 0x00, 0x00, 0x10, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 69] U+0065 row 5 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 5 + 0x00, 0x00, 0x20, 0x40, 0x20, 0x20, 0x30, 0x00, // [ 71] U+0067 row 5 + 0x00, 0xBF, 0x40, 0x30, 0x40, 0x10, 0x00, 0x00, // [ 72] U+0068 row 5 + 0x00, 0x00, 0x40, 0x40, 0x20, 0x00, 0x00, 0x00, // [ 73] U+0069 row 5 + 0x00, 0x00, 0x40, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 74] U+006A row 5 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x30, 0x30, 0x00, // [ 75] U+006B row 5 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 5 + 0x10, 0x30, 0x30, 0x30, 0x10, 0x40, 0x10, 0x00, // [ 77] U+006D row 5 + 0x00, 0x30, 0x10, 0x30, 0x40, 0x10, 0x00, 0x00, // [ 78] U+006E row 5 + 0x00, 0x00, 0x10, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 79] U+006F row 5 + 0x00, 0x30, 0x10, 0x30, 0x40, 0x20, 0x00, 0x00, // [ 80] U+0070 row 5 + 0x00, 0x00, 0x20, 0x40, 0x20, 0x20, 0x30, 0x00, // [ 81] U+0071 row 5 + 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x20, 0x00, // [ 82] U+0072 row 5 + 0x00, 0x00, 0x20, 0x40, 0x40, 0x10, 0x00, 0x00, // [ 83] U+0073 row 5 + 0x20, 0x40, 0x70, 0xCF, 0x40, 0x40, 0x40, 0x00, // [ 84] U+0074 row 5 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 85] U+0075 row 5 + 0x10, 0x40, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, // [ 86] U+0076 row 5 + 0x10, 0x30, 0x00, 0x20, 0x20, 0x00, 0x40, 0x10, // [ 87] U+0077 row 5 + 0x00, 0x40, 0x10, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 88] U+0078 row 5 + 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, // [ 89] U+0079 row 5 + 0x00, 0x30, 0x40, 0x40, 0x40, 0x40, 0x30, 0x00, // [ 90] U+007A row 5 + 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, // [ 91] U+007B row 5 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 5 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 93] U+007D row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 5 + 0x00, 0x00, 0x10, 0x70, 0x60, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 5 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 96] U+00B5 row 5 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xAF, 0x00, // [ 97] U+03A9 row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 5 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 6 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 6 + 0x00, 0x30, 0x8F, 0x00, 0x00, 0xBF, 0x00, 0x00, // [ 2] U+0022 row 6 + 0x00, 0x80, 0xDF, 0x9F, 0x80, 0xFF, 0x80, 0x40, // [ 3] U+0023 row 6 + 0x00, 0x9F, 0x9F, 0x8F, 0x40, 0x00, 0x00, 0x00, // [ 4] U+0024 row 6 + 0x70, 0xAF, 0x80, 0x9F, 0xDF, 0x30, 0x00, 0x00, // [ 5] U+0025 row 6 + 0x00, 0x20, 0xDF, 0x60, 0xEF, 0x40, 0x00, 0x00, // [ 6] U+0026 row 6 + 0x00, 0x00, 0x00, 0x70, 0x60, 0x00, 0x00, 0x00, // [ 7] U+0027 row 6 + 0x00, 0x00, 0xEF, 0x30, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 6 + 0x00, 0x00, 0x00, 0x00, 0x60, 0xBF, 0x00, 0x00, // [ 9] U+0029 row 6 + 0x00, 0x20, 0x9F, 0xEF, 0xEF, 0x80, 0x10, 0x00, // [ 10] U+002A row 6 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 11] U+002B row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 6 + 0x00, 0x00, 0x00, 0x10, 0xFF, 0x10, 0x00, 0x00, // [ 15] U+002F row 6 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 16] U+0030 row 6 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0x8F, 0x00, // [ 18] U+0032 row 6 + 0x00, 0x00, 0x00, 0x00, 0x10, 0xCF, 0x50, 0x00, // [ 19] U+0033 row 6 + 0x00, 0x00, 0x60, 0xBF, 0x40, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 6 + 0x00, 0xBF, 0x8F, 0xAF, 0x9F, 0x40, 0x00, 0x00, // [ 21] U+0035 row 6 + 0x00, 0xFF, 0x00, 0x30, 0x40, 0x10, 0x00, 0x00, // [ 22] U+0036 row 6 + 0x00, 0x00, 0x00, 0x00, 0x60, 0xAF, 0x00, 0x00, // [ 23] U+0037 row 6 + 0x00, 0x70, 0xAF, 0x00, 0x10, 0xDF, 0x40, 0x00, // [ 24] U+0038 row 6 + 0x00, 0xFF, 0x30, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 25] U+0039 row 6 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 26] U+003A row 6 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 27] U+003B row 6 + 0x00, 0x00, 0x40, 0xDF, 0x9F, 0x10, 0x00, 0x00, // [ 28] U+003C row 6 + 0x00, 0x8F, 0xBF, 0xBF, 0xBF, 0xBF, 0x8F, 0x00, // [ 29] U+003D row 6 + 0x00, 0x00, 0x20, 0xBF, 0xBF, 0x20, 0x00, 0x00, // [ 30] U+003E row 6 + 0x00, 0x00, 0x00, 0x00, 0x10, 0xDF, 0x60, 0x00, // [ 31] U+003F row 6 + 0x40, 0xBF, 0x00, 0xEF, 0xBF, 0x00, 0xFF, 0x00, // [ 32] U+0040 row 6 + 0x00, 0x00, 0x9F, 0x60, 0x8F, 0x70, 0x00, 0x00, // [ 33] U+0041 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x10, 0xDF, 0x40, 0x00, // [ 34] U+0042 row 6 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x70, 0xAF, 0x00, // [ 36] U+0044 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 6 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 6 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 6 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 6 + 0x00, 0x80, 0x80, 0xBF, 0x70, 0x00, 0x00, 0x00, // [ 43] U+004B row 6 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 6 + 0x40, 0xBF, 0xBF, 0x20, 0x50, 0x80, 0xFF, 0x00, // [ 45] U+004D row 6 + 0x00, 0xBF, 0x60, 0xDF, 0x00, 0x80, 0xBF, 0x00, // [ 46] U+004E row 6 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 47] U+004F row 6 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 48] U+0050 row 6 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 49] U+0051 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 50] U+0052 row 6 + 0x00, 0x8F, 0xCF, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 6 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 6 + 0x00, 0x50, 0xBF, 0x00, 0x00, 0xFF, 0x20, 0x00, // [ 54] U+0056 row 6 + 0x00, 0xDF, 0x00, 0xBF, 0xBF, 0x40, 0xBF, 0x00, // [ 55] U+0057 row 6 + 0x00, 0x00, 0x40, 0xDF, 0xEF, 0x20, 0x00, 0x00, // [ 56] U+0058 row 6 + 0x00, 0x00, 0x9F, 0x80, 0xBF, 0x60, 0x00, 0x00, // [ 57] U+0059 row 6 + 0x00, 0x00, 0x00, 0x10, 0xEF, 0x40, 0x00, 0x00, // [ 58] U+005A row 6 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 6 + 0x00, 0x00, 0x40, 0xDF, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 6 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 6 + 0x00, 0x80, 0x10, 0x00, 0x00, 0x20, 0x70, 0x00, // [ 62] U+005E row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 6 + 0x00, 0x60, 0xEF, 0xBF, 0xBF, 0xEF, 0x30, 0x00, // [ 65] U+0061 row 6 + 0x00, 0xBF, 0x9F, 0xBF, 0xBF, 0xEF, 0x30, 0x00, // [ 66] U+0062 row 6 + 0x00, 0x50, 0xEF, 0xBF, 0xBF, 0xEF, 0x20, 0x00, // [ 67] U+0063 row 6 + 0x00, 0x60, 0xFF, 0xBF, 0xBF, 0xBF, 0xBF, 0x00, // [ 68] U+0064 row 6 + 0x00, 0x50, 0xEF, 0xBF, 0xBF, 0xEF, 0x20, 0x00, // [ 69] U+0065 row 6 + 0x20, 0x80, 0x80, 0xFF, 0x80, 0x80, 0x80, 0x00, // [ 70] U+0066 row 6 + 0x00, 0x60, 0xFF, 0xBF, 0xBF, 0xBF, 0xBF, 0x00, // [ 71] U+0067 row 6 + 0x00, 0xBF, 0x9F, 0xBF, 0xCF, 0xEF, 0x20, 0x00, // [ 72] U+0068 row 6 + 0x00, 0x00, 0xBF, 0xDF, 0x80, 0x00, 0x00, 0x00, // [ 73] U+0069 row 6 + 0x00, 0x00, 0xBF, 0xBF, 0xEF, 0x40, 0x00, 0x00, // [ 74] U+006A row 6 + 0x00, 0x80, 0x80, 0x00, 0x30, 0xDF, 0x30, 0x00, // [ 75] U+006B row 6 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 6 + 0x40, 0xDF, 0x8F, 0xEF, 0xAF, 0xAF, 0xBF, 0x00, // [ 77] U+006D row 6 + 0x00, 0xBF, 0x9F, 0xBF, 0xCF, 0xEF, 0x20, 0x00, // [ 78] U+006E row 6 + 0x00, 0x50, 0xEF, 0xBF, 0xBF, 0xEF, 0x20, 0x00, // [ 79] U+006F row 6 + 0x00, 0xBF, 0x9F, 0xBF, 0xBF, 0xEF, 0x30, 0x00, // [ 80] U+0070 row 6 + 0x00, 0x60, 0xFF, 0xBF, 0xBF, 0xBF, 0xBF, 0x00, // [ 81] U+0071 row 6 + 0x00, 0x00, 0xFF, 0x30, 0xDF, 0xCF, 0x60, 0x00, // [ 82] U+0072 row 6 + 0x00, 0x50, 0xEF, 0xBF, 0xBF, 0xEF, 0x30, 0x00, // [ 83] U+0073 row 6 + 0x60, 0xBF, 0xCF, 0xEF, 0xBF, 0xBF, 0xBF, 0x00, // [ 84] U+0074 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 85] U+0075 row 6 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 86] U+0076 row 6 + 0x40, 0xBF, 0x00, 0xBF, 0x80, 0x00, 0xFF, 0x00, // [ 87] U+0077 row 6 + 0x00, 0x80, 0xBF, 0x00, 0x10, 0xEF, 0x50, 0x00, // [ 88] U+0078 row 6 + 0x00, 0xDF, 0x50, 0x00, 0x00, 0x70, 0x9F, 0x00, // [ 89] U+0079 row 6 + 0x00, 0x8F, 0xBF, 0xBF, 0xBF, 0xEF, 0xAF, 0x00, // [ 90] U+007A row 6 + 0x00, 0x00, 0x00, 0xDF, 0x40, 0x00, 0x00, 0x00, // [ 91] U+007B row 6 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 6 + 0x00, 0x00, 0x00, 0x70, 0xAF, 0x00, 0x00, 0x00, // [ 93] U+007D row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 96] U+00B5 row 6 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 97] U+03A9 row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 6 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 6 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 7 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 7 + 0x00, 0x00, 0xEF, 0x10, 0x30, 0xDF, 0x00, 0x00, // [ 3] U+0023 row 7 + 0x00, 0x10, 0xBF, 0xFF, 0xAF, 0x20, 0x00, 0x00, // [ 4] U+0024 row 7 + 0x10, 0x70, 0x80, 0x8F, 0x8F, 0x00, 0x00, 0x00, // [ 5] U+0025 row 7 + 0x00, 0x00, 0xAF, 0xFF, 0x60, 0x00, 0x00, 0x00, // [ 6] U+0026 row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 7 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 7 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 9] U+0029 row 7 + 0x00, 0xCF, 0x60, 0x80, 0x60, 0x80, 0x9F, 0x00, // [ 10] U+002A row 7 + 0x00, 0x30, 0x40, 0x9F, 0x9F, 0x40, 0x30, 0x00, // [ 11] U+002B row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 7 + 0x00, 0x30, 0x40, 0x40, 0x40, 0x40, 0x30, 0x00, // [ 13] U+002D row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 7 + 0x00, 0x00, 0x00, 0x70, 0xAF, 0x00, 0x00, 0x00, // [ 15] U+002F row 7 + 0x00, 0xFF, 0x00, 0xDF, 0xAF, 0x40, 0xBF, 0x00, // [ 16] U+0030 row 7 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 7 + 0x00, 0x00, 0x00, 0x00, 0x50, 0xEF, 0x10, 0x00, // [ 18] U+0032 row 7 + 0x00, 0x00, 0x00, 0x9F, 0xFF, 0x9F, 0x00, 0x00, // [ 19] U+0033 row 7 + 0x00, 0x00, 0xDF, 0x40, 0x40, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 7 + 0x00, 0xBF, 0xCF, 0x50, 0x60, 0xEF, 0x40, 0x00, // [ 21] U+0035 row 7 + 0x00, 0xFF, 0x70, 0xBF, 0xCF, 0xEF, 0x30, 0x00, // [ 22] U+0036 row 7 + 0x00, 0x00, 0x00, 0x00, 0xCF, 0x60, 0x00, 0x00, // [ 23] U+0037 row 7 + 0x00, 0x00, 0xBF, 0xDF, 0xEF, 0x8F, 0x00, 0x00, // [ 24] U+0038 row 7 + 0x00, 0xAF, 0xBF, 0x40, 0x40, 0xAF, 0xBF, 0x00, // [ 25] U+0039 row 7 + 0x00, 0x00, 0x00, 0x9F, 0x80, 0x00, 0x00, 0x00, // [ 26] U+003A row 7 + 0x00, 0x00, 0x00, 0x9F, 0x80, 0x00, 0x00, 0x00, // [ 27] U+003B row 7 + 0x00, 0x80, 0xCF, 0x40, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 7 + 0x00, 0x30, 0x40, 0x40, 0x40, 0x40, 0x30, 0x00, // [ 29] U+003D row 7 + 0x00, 0x00, 0x00, 0x00, 0x50, 0xDF, 0x70, 0x00, // [ 30] U+003E row 7 + 0x00, 0x00, 0x00, 0x10, 0xCF, 0x9F, 0x00, 0x00, // [ 31] U+003F row 7 + 0x40, 0xBF, 0x00, 0xFF, 0xBF, 0x00, 0xFF, 0x00, // [ 32] U+0040 row 7 + 0x00, 0x00, 0xDF, 0x20, 0x60, 0xAF, 0x00, 0x00, // [ 33] U+0041 row 7 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0x8F, 0x00, 0x00, // [ 34] U+0042 row 7 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 7 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 36] U+0044 row 7 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, 0x00, // [ 37] U+0045 row 7 + 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, 0x00, // [ 38] U+0046 row 7 + 0x00, 0xFF, 0x00, 0x00, 0xBF, 0xBF, 0x8F, 0x00, // [ 39] U+0047 row 7 + 0x00, 0xBF, 0xCF, 0xBF, 0xBF, 0xDF, 0xBF, 0x00, // [ 40] U+0048 row 7 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 7 + 0x00, 0x80, 0xDF, 0xEF, 0x10, 0x00, 0x00, 0x00, // [ 43] U+004B row 7 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 7 + 0x40, 0xBF, 0x80, 0x50, 0x80, 0x50, 0xFF, 0x00, // [ 45] U+004D row 7 + 0x00, 0xBF, 0x40, 0xAF, 0x50, 0x80, 0xBF, 0x00, // [ 46] U+004E row 7 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 47] U+004F row 7 + 0x00, 0x80, 0x9F, 0x40, 0x60, 0xCF, 0x70, 0x00, // [ 48] U+0050 row 7 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 49] U+0051 row 7 + 0x00, 0xBF, 0x70, 0x40, 0x60, 0xEF, 0x50, 0x00, // [ 50] U+0052 row 7 + 0x00, 0x00, 0x8F, 0xEF, 0x9F, 0x10, 0x00, 0x00, // [ 51] U+0053 row 7 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 7 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 7 + 0x00, 0x10, 0xFF, 0x00, 0x30, 0xCF, 0x00, 0x00, // [ 54] U+0056 row 7 + 0x00, 0xBF, 0x60, 0x80, 0xBF, 0x50, 0x80, 0x00, // [ 55] U+0057 row 7 + 0x00, 0x00, 0x00, 0xCF, 0x9F, 0x00, 0x00, 0x00, // [ 56] U+0058 row 7 + 0x00, 0x00, 0x20, 0xEF, 0xEF, 0x00, 0x00, 0x00, // [ 57] U+0059 row 7 + 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x00, 0x00, 0x00, // [ 58] U+005A row 7 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 7 + 0x00, 0x00, 0x00, 0xDF, 0x30, 0x00, 0x00, 0x00, // [ 60] U+005C row 7 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 7 + 0x00, 0x9F, 0x40, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 65] U+0061 row 7 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 66] U+0062 row 7 + 0x00, 0xCF, 0x60, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 67] U+0063 row 7 + 0x00, 0xDF, 0x60, 0x00, 0x00, 0xAF, 0xBF, 0x00, // [ 68] U+0064 row 7 + 0x00, 0xCF, 0x60, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 69] U+0065 row 7 + 0x10, 0x40, 0x40, 0xFF, 0x40, 0x40, 0x40, 0x00, // [ 70] U+0066 row 7 + 0x00, 0xDF, 0x60, 0x00, 0x00, 0xAF, 0xBF, 0x00, // [ 71] U+0067 row 7 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x9F, 0x80, 0x00, // [ 72] U+0068 row 7 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 73] U+0069 row 7 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 7 + 0x00, 0x80, 0x80, 0x30, 0xEF, 0x30, 0x00, 0x00, // [ 75] U+006B row 7 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 7 + 0x40, 0xCF, 0x00, 0x8F, 0x80, 0x00, 0xFF, 0x00, // [ 77] U+006D row 7 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x9F, 0x80, 0x00, // [ 78] U+006E row 7 + 0x00, 0xCF, 0x60, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 79] U+006F row 7 + 0x00, 0xBF, 0x70, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 80] U+0070 row 7 + 0x00, 0xDF, 0x60, 0x00, 0x00, 0xAF, 0xBF, 0x00, // [ 81] U+0071 row 7 + 0x00, 0x00, 0xFF, 0xAF, 0x20, 0x00, 0x00, 0x00, // [ 82] U+0072 row 7 + 0x00, 0xBF, 0x60, 0x00, 0x00, 0x40, 0x40, 0x00, // [ 83] U+0073 row 7 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 7 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 85] U+0075 row 7 + 0x00, 0x8F, 0x8F, 0x00, 0x00, 0xCF, 0x60, 0x00, // [ 86] U+0076 row 7 + 0x00, 0xEF, 0x00, 0xBF, 0xBF, 0x20, 0xCF, 0x00, // [ 87] U+0077 row 7 + 0x00, 0x00, 0xCF, 0x70, 0x8F, 0x8F, 0x00, 0x00, // [ 88] U+0078 row 7 + 0x00, 0x70, 0x9F, 0x00, 0x00, 0xCF, 0x50, 0x00, // [ 89] U+0079 row 7 + 0x00, 0x00, 0x00, 0x00, 0x30, 0xDF, 0x10, 0x00, // [ 90] U+007A row 7 + 0x00, 0x30, 0xAF, 0xCF, 0x00, 0x00, 0x00, 0x00, // [ 91] U+007B row 7 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 7 + 0x00, 0x00, 0x00, 0x10, 0xEF, 0x8F, 0x20, 0x00, // [ 93] U+007D row 7 + 0x00, 0x10, 0xAF, 0x80, 0x00, 0x00, 0x8F, 0x00, // [ 94] U+007E row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 7 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 96] U+00B5 row 7 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 97] U+03A9 row 7 + 0x20, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x10, // [ 98] U+2013 row 7 + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x30, // [ 99] U+2014 row 7 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 8 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 1] U+0021 row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 8 + 0x00, 0x30, 0xDF, 0x00, 0x60, 0x9F, 0x00, 0x00, // [ 3] U+0023 row 8 + 0x00, 0x00, 0x00, 0x8F, 0xAF, 0xEF, 0x30, 0x00, // [ 4] U+0024 row 8 + 0x00, 0x00, 0x10, 0xDF, 0x8F, 0xDF, 0xDF, 0x10, // [ 5] U+0025 row 8 + 0x00, 0x70, 0xCF, 0xAF, 0x8F, 0x00, 0x70, 0x20, // [ 6] U+0026 row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 8 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 8 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xCF, 0x00, 0x00, // [ 9] U+0029 row 8 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 10] U+002A row 8 + 0x00, 0x60, 0x80, 0xBF, 0xBF, 0x80, 0x60, 0x00, // [ 11] U+002B row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 8 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 13] U+002D row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 8 + 0x00, 0x00, 0x00, 0xCF, 0x50, 0x00, 0x00, 0x00, // [ 15] U+002F row 8 + 0x00, 0xFF, 0x00, 0x60, 0x50, 0x40, 0xBF, 0x00, // [ 16] U+0030 row 8 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 8 + 0x00, 0x00, 0x00, 0x30, 0xDF, 0x30, 0x00, 0x00, // [ 18] U+0032 row 8 + 0x00, 0x00, 0x00, 0x00, 0x50, 0xCF, 0x50, 0x00, // [ 19] U+0033 row 8 + 0x00, 0x60, 0xBF, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 8 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x70, 0xAF, 0x00, // [ 21] U+0035 row 8 + 0x00, 0xFF, 0x60, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 22] U+0036 row 8 + 0x00, 0x00, 0x00, 0x20, 0xEF, 0x00, 0x00, 0x00, // [ 23] U+0037 row 8 + 0x00, 0x80, 0xBF, 0x30, 0x50, 0xCF, 0x50, 0x00, // [ 24] U+0038 row 8 + 0x00, 0x10, 0x9F, 0xBF, 0x9F, 0x50, 0xBF, 0x00, // [ 25] U+0039 row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 8 + 0x00, 0xAF, 0x8F, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 8 + 0x00, 0x00, 0x00, 0x00, 0x20, 0xAF, 0x9F, 0x00, // [ 30] U+003E row 8 + 0x00, 0x00, 0x00, 0x70, 0xBF, 0x00, 0x00, 0x00, // [ 31] U+003F row 8 + 0x40, 0xBF, 0x00, 0xFF, 0xBF, 0x00, 0xFF, 0x00, // [ 32] U+0040 row 8 + 0x00, 0x20, 0xDF, 0x00, 0x20, 0xEF, 0x00, 0x00, // [ 33] U+0041 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x30, 0xCF, 0x80, 0x00, // [ 34] U+0042 row 8 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 36] U+0044 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 8 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 8 + 0x00, 0xFF, 0x00, 0x00, 0x40, 0x9F, 0xBF, 0x00, // [ 39] U+0047 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 8 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 8 + 0x00, 0x80, 0xEF, 0xAF, 0x80, 0x00, 0x00, 0x00, // [ 43] U+004B row 8 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 8 + 0x40, 0xBF, 0x50, 0x80, 0xBF, 0x20, 0xFF, 0x00, // [ 45] U+004D row 8 + 0x00, 0xBF, 0x40, 0x50, 0xAF, 0x70, 0xBF, 0x00, // [ 46] U+004E row 8 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 47] U+004F row 8 + 0x00, 0x80, 0xBF, 0x80, 0x80, 0x60, 0x00, 0x00, // [ 48] U+0050 row 8 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 49] U+0051 row 8 + 0x00, 0xBF, 0x9F, 0x9F, 0xEF, 0x50, 0x00, 0x00, // [ 50] U+0052 row 8 + 0x00, 0x00, 0x00, 0x20, 0x9F, 0xEF, 0x30, 0x00, // [ 51] U+0053 row 8 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 8 + 0x00, 0x00, 0xBF, 0x40, 0x70, 0x8F, 0x00, 0x00, // [ 54] U+0056 row 8 + 0x00, 0x8F, 0x80, 0x80, 0xAF, 0x8F, 0x70, 0x00, // [ 55] U+0057 row 8 + 0x00, 0x00, 0x10, 0xEF, 0xDF, 0x00, 0x00, 0x00, // [ 56] U+0058 row 8 + 0x00, 0x00, 0x00, 0xCF, 0x8F, 0x00, 0x00, 0x00, // [ 57] U+0059 row 8 + 0x00, 0x00, 0x20, 0xFF, 0x20, 0x00, 0x00, 0x00, // [ 58] U+005A row 8 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 8 + 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x00, 0x00, 0x00, // [ 60] U+005C row 8 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 8 + 0x00, 0x00, 0x30, 0x70, 0x80, 0xBF, 0xBF, 0x00, // [ 65] U+0061 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 66] U+0062 row 8 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 8 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 68] U+0064 row 8 + 0x00, 0xFF, 0x50, 0x40, 0x40, 0x70, 0xBF, 0x00, // [ 69] U+0065 row 8 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 8 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 71] U+0067 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 72] U+0068 row 8 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 73] U+0069 row 8 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 8 + 0x00, 0x80, 0x8F, 0xDF, 0x60, 0x00, 0x00, 0x00, // [ 75] U+006B row 8 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 8 + 0x40, 0xBF, 0x00, 0x80, 0x80, 0x00, 0xFF, 0x00, // [ 77] U+006D row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 78] U+006E row 8 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 79] U+006F row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 80] U+0070 row 8 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 81] U+0071 row 8 + 0x00, 0x00, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 8 + 0x00, 0x60, 0xDF, 0x80, 0x30, 0x00, 0x00, 0x00, // [ 83] U+0073 row 8 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 85] U+0075 row 8 + 0x00, 0x30, 0xDF, 0x00, 0x20, 0xEF, 0x10, 0x00, // [ 86] U+0076 row 8 + 0x00, 0xCF, 0x20, 0xAF, 0xBF, 0x40, 0x9F, 0x00, // [ 87] U+0077 row 8 + 0x00, 0x00, 0x30, 0xEF, 0xDF, 0x10, 0x00, 0x00, // [ 88] U+0078 row 8 + 0x00, 0x20, 0xEF, 0x00, 0x20, 0xDF, 0x00, 0x00, // [ 89] U+0079 row 8 + 0x00, 0x00, 0x00, 0x30, 0xDF, 0x30, 0x00, 0x00, // [ 90] U+007A row 8 + 0x00, 0x50, 0xDF, 0x9F, 0x00, 0x00, 0x00, 0x00, // [ 91] U+007B row 8 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 8 + 0x00, 0x00, 0x00, 0x00, 0xCF, 0xCF, 0x40, 0x00, // [ 93] U+007D row 8 + 0x00, 0xBF, 0x8F, 0xCF, 0x60, 0x70, 0xBF, 0x00, // [ 94] U+007E row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 96] U+00B5 row 8 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 97] U+03A9 row 8 + 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20, // [ 98] U+2013 row 8 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x60, // [ 99] U+2014 row 8 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 8 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 9 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, // [ 1] U+0021 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 9 + 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 3] U+0023 row 9 + 0x00, 0x40, 0x00, 0x80, 0x40, 0x60, 0xAF, 0x00, // [ 4] U+0024 row 9 + 0x00, 0x00, 0x9F, 0x70, 0xBF, 0x10, 0x8F, 0x40, // [ 5] U+0025 row 9 + 0x00, 0xDF, 0x30, 0x10, 0xDF, 0x70, 0xDF, 0x00, // [ 6] U+0026 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 9 + 0x00, 0x00, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 9 + 0x00, 0x00, 0x00, 0x00, 0x50, 0xBF, 0x00, 0x00, // [ 9] U+0029 row 9 + 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, // [ 10] U+002A row 9 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 11] U+002B row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 9 + 0x00, 0x00, 0x20, 0xEF, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 16] U+0030 row 9 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 9 + 0x00, 0x00, 0x20, 0xEF, 0x60, 0x00, 0x00, 0x00, // [ 18] U+0032 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 19] U+0033 row 9 + 0x00, 0xDF, 0xAF, 0x80, 0x9F, 0xDF, 0x60, 0x00, // [ 20] U+0034 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 21] U+0035 row 9 + 0x00, 0xFF, 0x10, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 22] U+0036 row 9 + 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x00, 0x00, 0x00, // [ 23] U+0037 row 9 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x60, 0xAF, 0x00, // [ 24] U+0038 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 25] U+0039 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 9 + 0x00, 0x00, 0x80, 0xEF, 0x60, 0x00, 0x00, 0x00, // [ 28] U+003C row 9 + 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, // [ 29] U+003D row 9 + 0x00, 0x00, 0x00, 0x80, 0xDF, 0x60, 0x00, 0x00, // [ 30] U+003E row 9 + 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, // [ 31] U+003F row 9 + 0x40, 0xBF, 0x00, 0xFF, 0xBF, 0x00, 0xFF, 0x00, // [ 32] U+0040 row 9 + 0x00, 0x70, 0xEF, 0xBF, 0xBF, 0xFF, 0x30, 0x00, // [ 33] U+0041 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 34] U+0042 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 36] U+0044 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 9 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 39] U+0047 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 9 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, // [ 42] U+004A row 9 + 0x00, 0x80, 0x80, 0x20, 0xDF, 0x20, 0x00, 0x00, // [ 43] U+004B row 9 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 9 + 0x40, 0xEF, 0x20, 0xBF, 0xDF, 0x00, 0xFF, 0x00, // [ 45] U+004D row 9 + 0x00, 0xBF, 0x40, 0x00, 0xDF, 0x60, 0xBF, 0x00, // [ 46] U+004E row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 47] U+004F row 9 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 9 + 0x00, 0xFF, 0x00, 0x80, 0xAF, 0x40, 0xBF, 0x00, // [ 49] U+0051 row 9 + 0x00, 0xBF, 0x40, 0x00, 0xDF, 0x40, 0x00, 0x00, // [ 50] U+0052 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 51] U+0053 row 9 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 53] U+0055 row 9 + 0x00, 0x00, 0x80, 0x80, 0xAF, 0x50, 0x00, 0x00, // [ 54] U+0056 row 9 + 0x00, 0x80, 0xBF, 0x40, 0x80, 0xBF, 0x40, 0x00, // [ 55] U+0057 row 9 + 0x00, 0x00, 0x80, 0x9F, 0xDF, 0x60, 0x00, 0x00, // [ 56] U+0058 row 9 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 57] U+0059 row 9 + 0x00, 0x00, 0xAF, 0x80, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 9 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 9 + 0x00, 0x00, 0x00, 0x20, 0xEF, 0x00, 0x00, 0x00, // [ 60] U+005C row 9 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 9 + 0x00, 0x8F, 0xDF, 0x80, 0x80, 0xBF, 0xBF, 0x00, // [ 65] U+0061 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 66] U+0062 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 68] U+0064 row 9 + 0x00, 0xFF, 0xBF, 0xBF, 0xBF, 0xBF, 0x8F, 0x00, // [ 69] U+0065 row 9 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 71] U+0067 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 72] U+0068 row 9 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 73] U+0069 row 9 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 9 + 0x00, 0x80, 0xFF, 0xDF, 0x80, 0x00, 0x00, 0x00, // [ 75] U+006B row 9 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 9 + 0x40, 0xBF, 0x00, 0x80, 0x80, 0x00, 0xFF, 0x00, // [ 77] U+006D row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 78] U+006E row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 79] U+006F row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x40, 0xBF, 0x00, // [ 80] U+0070 row 9 + 0x00, 0xFF, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 81] U+0071 row 9 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 9 + 0x00, 0x00, 0x40, 0x9F, 0xDF, 0xCF, 0x20, 0x00, // [ 83] U+0073 row 9 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 85] U+0075 row 9 + 0x00, 0x00, 0xDF, 0x40, 0x70, 0x9F, 0x00, 0x00, // [ 86] U+0076 row 9 + 0x00, 0x9F, 0x80, 0x80, 0xAF, 0x80, 0x70, 0x00, // [ 87] U+0077 row 9 + 0x00, 0x00, 0x10, 0xDF, 0xBF, 0x00, 0x00, 0x00, // [ 88] U+0078 row 9 + 0x00, 0x00, 0xBF, 0x50, 0x80, 0x8F, 0x00, 0x00, // [ 89] U+0079 row 9 + 0x00, 0x00, 0x10, 0xEF, 0x30, 0x00, 0x00, 0x00, // [ 90] U+007A row 9 + 0x00, 0x00, 0x10, 0xEF, 0x30, 0x00, 0x00, 0x00, // [ 91] U+007B row 9 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 9 + 0x00, 0x00, 0x00, 0x60, 0xCF, 0x00, 0x00, 0x00, // [ 93] U+007D row 9 + 0x20, 0xDF, 0x00, 0x30, 0xDF, 0xEF, 0x20, 0x00, // [ 94] U+007E row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 96] U+00B5 row 9 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 97] U+03A9 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 9 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 10 + 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, // [ 1] U+0021 row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 10 + 0x00, 0x8F, 0x70, 0x00, 0xCF, 0x40, 0x00, 0x00, // [ 3] U+0023 row 10 + 0x00, 0xDF, 0x50, 0x80, 0x40, 0x80, 0xAF, 0x00, // [ 4] U+0024 row 10 + 0x00, 0x40, 0xDF, 0x00, 0xBF, 0x00, 0x80, 0x40, // [ 5] U+0025 row 10 + 0x00, 0xEF, 0x20, 0x00, 0x40, 0xFF, 0x70, 0x00, // [ 6] U+0026 row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 10 + 0x00, 0x00, 0xBF, 0x60, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 10 + 0x00, 0x00, 0x00, 0x00, 0x8F, 0x8F, 0x00, 0x00, // [ 9] U+0029 row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 10 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 11] U+002B row 10 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, // [ 12] U+002C row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 10 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, // [ 14] U+002E row 10 + 0x00, 0x00, 0x8F, 0x9F, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 16] U+0030 row 10 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 10 + 0x00, 0x10, 0xCF, 0x60, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 10 + 0x00, 0xBF, 0x20, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 19] U+0033 row 10 + 0x00, 0x80, 0x80, 0x80, 0x9F, 0xDF, 0x60, 0x00, // [ 20] U+0034 row 10 + 0x00, 0x9F, 0x30, 0x00, 0x00, 0x70, 0xAF, 0x00, // [ 21] U+0035 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 22] U+0036 row 10 + 0x00, 0x00, 0x00, 0xDF, 0x40, 0x00, 0x00, 0x00, // [ 23] U+0037 row 10 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 24] U+0038 row 10 + 0x00, 0xBF, 0x20, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 25] U+0039 row 10 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, // [ 26] U+003A row 10 + 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, // [ 27] U+003B row 10 + 0x00, 0x00, 0x00, 0x20, 0xBF, 0xBF, 0x20, 0x00, // [ 28] U+003C row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 10 + 0x00, 0x40, 0xDF, 0x9F, 0x10, 0x00, 0x00, 0x00, // [ 30] U+003E row 10 + 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, // [ 31] U+003F row 10 + 0x40, 0xBF, 0x00, 0xDF, 0xDF, 0x80, 0xEF, 0x00, // [ 32] U+0040 row 10 + 0x00, 0xAF, 0x70, 0x00, 0x00, 0x9F, 0x70, 0x00, // [ 33] U+0041 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 34] U+0042 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0x80, 0x00, // [ 35] U+0043 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x10, 0xDF, 0x40, 0x00, // [ 36] U+0044 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 10 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 39] U+0047 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 10 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 41] U+0049 row 10 + 0x20, 0xBF, 0x00, 0x00, 0x00, 0xCF, 0x40, 0x00, // [ 42] U+004A row 10 + 0x00, 0x80, 0x80, 0x00, 0x80, 0x9F, 0x00, 0x00, // [ 43] U+004B row 10 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 10 + 0x40, 0xFF, 0x00, 0xDF, 0xBF, 0x00, 0xFF, 0x00, // [ 45] U+004D row 10 + 0x00, 0xBF, 0x40, 0x00, 0x80, 0xBF, 0xBF, 0x00, // [ 46] U+004E row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 47] U+004F row 10 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 10 + 0x00, 0xEF, 0x40, 0x00, 0xCF, 0xAF, 0xBF, 0x00, // [ 49] U+0051 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x60, 0xDF, 0x00, 0x00, // [ 50] U+0052 row 10 + 0x00, 0xBF, 0x20, 0x00, 0x00, 0x50, 0xBF, 0x00, // [ 51] U+0053 row 10 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 10 + 0x00, 0xBF, 0x50, 0x00, 0x00, 0x80, 0x9F, 0x00, // [ 53] U+0055 row 10 + 0x00, 0x00, 0x40, 0xBF, 0xEF, 0x10, 0x00, 0x00, // [ 54] U+0056 row 10 + 0x00, 0x50, 0xFF, 0x40, 0x60, 0xEF, 0x20, 0x00, // [ 55] U+0057 row 10 + 0x00, 0x10, 0xEF, 0x20, 0x60, 0xDF, 0x00, 0x00, // [ 56] U+0058 row 10 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 57] U+0059 row 10 + 0x00, 0x40, 0xEF, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 10 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 10 + 0x00, 0x00, 0x00, 0x00, 0xCF, 0x50, 0x00, 0x00, // [ 60] U+005C row 10 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 10 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 65] U+0061 row 10 + 0x00, 0xBF, 0x50, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 66] U+0062 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0x80, 0x00, // [ 67] U+0063 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x8F, 0xBF, 0x00, // [ 68] U+0064 row 10 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x10, 0x30, 0x00, // [ 69] U+0065 row 10 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x8F, 0xBF, 0x00, // [ 71] U+0067 row 10 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 72] U+0068 row 10 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 73] U+0069 row 10 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 10 + 0x00, 0x80, 0x8F, 0x10, 0xEF, 0x40, 0x00, 0x00, // [ 75] U+006B row 10 + 0x00, 0x00, 0x00, 0xFF, 0x10, 0x00, 0x00, 0x00, // [ 76] U+006C row 10 + 0x40, 0xBF, 0x00, 0x80, 0x80, 0x00, 0xFF, 0x00, // [ 77] U+006D row 10 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 78] U+006E row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 79] U+006F row 10 + 0x00, 0xBF, 0x50, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 80] U+0070 row 10 + 0x00, 0xEF, 0x40, 0x00, 0x00, 0x8F, 0xBF, 0x00, // [ 81] U+0071 row 10 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 10 + 0x00, 0x40, 0x10, 0x00, 0x00, 0x8F, 0x9F, 0x00, // [ 83] U+0073 row 10 + 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 10 + 0x00, 0xBF, 0x50, 0x00, 0x00, 0x8F, 0xBF, 0x00, // [ 85] U+0075 row 10 + 0x00, 0x00, 0x70, 0x9F, 0xCF, 0x50, 0x00, 0x00, // [ 86] U+0076 row 10 + 0x00, 0x70, 0xCF, 0x40, 0x80, 0xCF, 0x40, 0x00, // [ 87] U+0077 row 10 + 0x00, 0x00, 0x8F, 0x9F, 0xCF, 0x70, 0x00, 0x00, // [ 88] U+0078 row 10 + 0x00, 0x00, 0x60, 0x9F, 0xDF, 0x30, 0x00, 0x00, // [ 89] U+0079 row 10 + 0x00, 0x10, 0xCF, 0x60, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 10 + 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, // [ 91] U+007B row 10 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 10 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 93] U+007D row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 10 + 0x00, 0xBF, 0x50, 0x00, 0x00, 0x8F, 0xBF, 0x00, // [ 96] U+00B5 row 10 + 0x00, 0x8F, 0xAF, 0x10, 0x20, 0xCF, 0x50, 0x00, // [ 97] U+03A9 row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 10 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 11 + 0x00, 0x00, 0x00, 0xEF, 0xBF, 0x00, 0x00, 0x00, // [ 1] U+0021 row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 11 + 0x00, 0xCF, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x00, // [ 3] U+0023 row 11 + 0x00, 0x40, 0xEF, 0xEF, 0xDF, 0xDF, 0x30, 0x00, // [ 4] U+0024 row 11 + 0x00, 0xCF, 0x40, 0x00, 0xBF, 0x50, 0xBF, 0x40, // [ 5] U+0025 row 11 + 0x00, 0x8F, 0xBF, 0x40, 0x80, 0xEF, 0xAF, 0x00, // [ 6] U+0026 row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 11 + 0x00, 0x00, 0x60, 0xBF, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 11 + 0x00, 0x00, 0x00, 0x10, 0xEF, 0x30, 0x00, 0x00, // [ 9] U+0029 row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 11 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 12] U+002C row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 11 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 14] U+002E row 11 + 0x00, 0x00, 0xDF, 0x30, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 16] U+0030 row 11 + 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, // [ 17] U+0031 row 11 + 0x00, 0xAF, 0xEF, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 18] U+0032 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xCF, 0x70, 0x00, // [ 19] U+0033 row 11 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 20] U+0034 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x40, 0x00, // [ 21] U+0035 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 22] U+0036 row 11 + 0x00, 0x00, 0x40, 0xDF, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 24] U+0038 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 25] U+0039 row 11 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 26] U+003A row 11 + 0x00, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00, 0x00, // [ 27] U+003B row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x9F, 0x00, // [ 28] U+003C row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 11 + 0x00, 0x9F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 11 + 0x00, 0x00, 0x00, 0xEF, 0xBF, 0x00, 0x00, 0x00, // [ 31] U+003F row 11 + 0x40, 0xCF, 0x00, 0x50, 0x80, 0x80, 0x20, 0x00, // [ 32] U+0040 row 11 + 0x00, 0xEF, 0x30, 0x00, 0x00, 0x60, 0xBF, 0x00, // [ 33] U+0041 row 11 + 0x00, 0xBF, 0x9F, 0x80, 0x8F, 0xEF, 0x50, 0x00, // [ 34] U+0042 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 35] U+0043 row 11 + 0x00, 0xBF, 0x9F, 0x80, 0xCF, 0x9F, 0x00, 0x00, // [ 36] U+0044 row 11 + 0x00, 0xBF, 0x9F, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 37] U+0045 row 11 + 0x00, 0x40, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xEF, 0x40, 0x00, // [ 39] U+0047 row 11 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 40] U+0048 row 11 + 0x00, 0x00, 0x80, 0xDF, 0xBF, 0x60, 0x00, 0x00, // [ 41] U+0049 row 11 + 0x00, 0xCF, 0xAF, 0x40, 0xAF, 0xDF, 0x00, 0x00, // [ 42] U+004A row 11 + 0x00, 0x80, 0x80, 0x00, 0x10, 0xEF, 0x40, 0x00, // [ 43] U+004B row 11 + 0x00, 0x40, 0xDF, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 44] U+004C row 11 + 0x40, 0xFF, 0x00, 0xBF, 0x80, 0x00, 0xFF, 0x00, // [ 45] U+004D row 11 + 0x00, 0xBF, 0x40, 0x00, 0x20, 0xFF, 0xBF, 0x00, // [ 46] U+004E row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 47] U+004F row 11 + 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x80, 0xFF, 0x60, 0x00, // [ 49] U+0051 row 11 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0xBF, 0x60, 0x00, // [ 50] U+0052 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 51] U+0053 row 11 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 52] U+0054 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x40, 0x00, // [ 53] U+0055 row 11 + 0x00, 0x00, 0x00, 0xFF, 0xCF, 0x00, 0x00, 0x00, // [ 54] U+0056 row 11 + 0x00, 0x40, 0xFF, 0x00, 0x40, 0xFF, 0x00, 0x00, // [ 55] U+0057 row 11 + 0x00, 0x80, 0x9F, 0x00, 0x00, 0xDF, 0x60, 0x00, // [ 56] U+0058 row 11 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 57] U+0059 row 11 + 0x00, 0xAF, 0xCF, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 58] U+005A row 11 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 11 + 0x00, 0x00, 0x00, 0x00, 0x70, 0xAF, 0x00, 0x00, // [ 60] U+005C row 11 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 11 + 0x00, 0xBF, 0xAF, 0x40, 0x60, 0xDF, 0xBF, 0x00, // [ 65] U+0061 row 11 + 0x00, 0xBF, 0xAF, 0x60, 0x60, 0xEF, 0x60, 0x00, // [ 66] U+0062 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 67] U+0063 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xDF, 0xBF, 0x00, // [ 68] U+0064 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x50, 0xCF, 0x80, 0x00, // [ 69] U+0065 row 11 + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xDF, 0xBF, 0x00, // [ 71] U+0067 row 11 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 72] U+0068 row 11 + 0x00, 0x60, 0x80, 0xBF, 0xBF, 0x80, 0x40, 0x00, // [ 73] U+0069 row 11 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 11 + 0x00, 0x80, 0x80, 0x00, 0x40, 0xEF, 0x10, 0x00, // [ 75] U+006B row 11 + 0x00, 0x00, 0x00, 0xBF, 0xAF, 0x80, 0x20, 0x00, // [ 76] U+006C row 11 + 0x40, 0xBF, 0x00, 0x80, 0x80, 0x00, 0xFF, 0x00, // [ 77] U+006D row 11 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 78] U+006E row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xEF, 0x50, 0x00, // [ 79] U+006F row 11 + 0x00, 0xBF, 0xAF, 0x60, 0x60, 0xEF, 0x60, 0x00, // [ 80] U+0070 row 11 + 0x00, 0x8F, 0xCF, 0x50, 0x60, 0xDF, 0xBF, 0x00, // [ 81] U+0071 row 11 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 11 + 0x00, 0xAF, 0xAF, 0x40, 0x50, 0xCF, 0x70, 0x00, // [ 83] U+0073 row 11 + 0x00, 0x00, 0x10, 0xEF, 0x8F, 0x80, 0x80, 0x00, // [ 84] U+0074 row 11 + 0x00, 0x80, 0xCF, 0x50, 0x60, 0xDF, 0xBF, 0x00, // [ 85] U+0075 row 11 + 0x00, 0x00, 0x20, 0xEF, 0xDF, 0x00, 0x00, 0x00, // [ 86] U+0076 row 11 + 0x00, 0x40, 0xFF, 0x20, 0x50, 0xFF, 0x10, 0x00, // [ 87] U+0077 row 11 + 0x00, 0x40, 0xEF, 0x10, 0x30, 0xEF, 0x20, 0x00, // [ 88] U+0078 row 11 + 0x00, 0x00, 0x10, 0xEF, 0xDF, 0x00, 0x00, 0x00, // [ 89] U+0079 row 11 + 0x00, 0xAF, 0xCF, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 90] U+007A row 11 + 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, // [ 91] U+007B row 11 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 11 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 93] U+007D row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 11 + 0x00, 0xBF, 0xCF, 0x50, 0x60, 0xCF, 0xCF, 0x20, // [ 96] U+00B5 row 11 + 0x00, 0x60, 0xDF, 0x40, 0x80, 0xCF, 0x60, 0x00, // [ 97] U+03A9 row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 11 + 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x80, // [100] fallback box row 11 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 12 + 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, // [ 1] U+0021 row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 12 + 0x00, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, // [ 3] U+0023 row 12 + 0x00, 0x00, 0x00, 0x8F, 0x80, 0x00, 0x00, 0x00, // [ 4] U+0024 row 12 + 0x20, 0x70, 0x00, 0x00, 0x30, 0x9F, 0x70, 0x00, // [ 5] U+0025 row 12 + 0x00, 0x00, 0x70, 0xAF, 0x70, 0x20, 0x80, 0x10, // [ 6] U+0026 row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 12 + 0x00, 0x00, 0x00, 0xCF, 0x8F, 0x00, 0x00, 0x00, // [ 8] U+0028 row 12 + 0x00, 0x00, 0x00, 0xAF, 0x8F, 0x00, 0x00, 0x00, // [ 9] U+0029 row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 12 + 0x00, 0x00, 0x00, 0x8F, 0xCF, 0x00, 0x00, 0x00, // [ 12] U+002C row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 12 + 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x00, // [ 14] U+002E row 12 + 0x00, 0x40, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 16] U+0030 row 12 + 0x00, 0x00, 0x00, 0x20, 0x60, 0x00, 0x00, 0x00, // [ 17] U+0031 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 18] U+0032 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x9F, 0x50, 0x00, 0x00, // [ 19] U+0033 row 12 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x60, 0x00, 0x00, // [ 20] U+0034 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 21] U+0035 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 22] U+0036 row 12 + 0x00, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 24] U+0038 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 25] U+0039 row 12 + 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x00, // [ 26] U+003A row 12 + 0x00, 0x00, 0x00, 0x8F, 0xCF, 0x00, 0x00, 0x00, // [ 27] U+003B row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 12 + 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, // [ 31] U+003F row 12 + 0x00, 0xCF, 0x8F, 0x40, 0x40, 0x40, 0x20, 0x00, // [ 32] U+0040 row 12 + 0x10, 0x80, 0x00, 0x00, 0x00, 0x20, 0x80, 0x00, // [ 33] U+0041 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x20, 0x00, 0x00, // [ 34] U+0042 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 35] U+0043 row 12 + 0x00, 0x60, 0x80, 0x80, 0x60, 0x00, 0x00, 0x00, // [ 36] U+0044 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 37] U+0045 row 12 + 0x00, 0x20, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 39] U+0047 row 12 + 0x00, 0x60, 0x20, 0x00, 0x00, 0x40, 0x60, 0x00, // [ 40] U+0048 row 12 + 0x00, 0x00, 0x80, 0x80, 0x80, 0x60, 0x00, 0x00, // [ 41] U+0049 row 12 + 0x00, 0x10, 0x70, 0xBF, 0x80, 0x10, 0x00, 0x00, // [ 42] U+004A row 12 + 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, 0x50, 0x00, // [ 43] U+004B row 12 + 0x00, 0x20, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 44] U+004C row 12 + 0x20, 0x80, 0x00, 0x50, 0x40, 0x00, 0x80, 0x00, // [ 45] U+004D row 12 + 0x00, 0x60, 0x20, 0x00, 0x00, 0x60, 0x60, 0x00, // [ 46] U+004E row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 47] U+004F row 12 + 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x8F, 0xBF, 0x00, // [ 49] U+0051 row 12 + 0x00, 0x60, 0x20, 0x00, 0x00, 0x30, 0x60, 0x00, // [ 50] U+0052 row 12 + 0x00, 0x00, 0x70, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 51] U+0053 row 12 + 0x00, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x00, // [ 52] U+0054 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 53] U+0055 row 12 + 0x00, 0x00, 0x00, 0x60, 0x50, 0x00, 0x00, 0x00, // [ 54] U+0056 row 12 + 0x00, 0x00, 0x80, 0x00, 0x20, 0x70, 0x00, 0x00, // [ 55] U+0057 row 12 + 0x00, 0x70, 0x20, 0x00, 0x00, 0x40, 0x60, 0x00, // [ 56] U+0058 row 12 + 0x00, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x00, // [ 57] U+0059 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 58] U+005A row 12 + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 12 + 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0x10, 0x00, // [ 60] U+005C row 12 + 0x00, 0x00, 0x00, 0x00, 0x40, 0xBF, 0x00, 0x00, // [ 61] U+005D row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 63] U+005F row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 12 + 0x00, 0x10, 0x80, 0xAF, 0x70, 0x40, 0x60, 0x00, // [ 65] U+0061 row 12 + 0x00, 0x60, 0x30, 0x70, 0xAF, 0x60, 0x00, 0x00, // [ 66] U+0062 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 67] U+0063 row 12 + 0x00, 0x00, 0x70, 0xAF, 0x60, 0x40, 0x60, 0x00, // [ 68] U+0064 row 12 + 0x00, 0x00, 0x60, 0x9F, 0x9F, 0x60, 0x00, 0x00, // [ 69] U+0065 row 12 + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 12 + 0x00, 0x00, 0x70, 0xAF, 0x60, 0x80, 0xBF, 0x00, // [ 71] U+0067 row 12 + 0x00, 0x60, 0x20, 0x00, 0x00, 0x40, 0x60, 0x00, // [ 72] U+0068 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x40, 0x00, // [ 73] U+0069 row 12 + 0x00, 0x00, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x00, // [ 74] U+006A row 12 + 0x00, 0x40, 0x40, 0x00, 0x00, 0x50, 0x50, 0x00, // [ 75] U+006B row 12 + 0x00, 0x00, 0x00, 0x10, 0x70, 0x80, 0x20, 0x00, // [ 76] U+006C row 12 + 0x20, 0x60, 0x00, 0x40, 0x40, 0x00, 0x80, 0x00, // [ 77] U+006D row 12 + 0x00, 0x60, 0x20, 0x00, 0x00, 0x40, 0x60, 0x00, // [ 78] U+006E row 12 + 0x00, 0x00, 0x60, 0x9F, 0x8F, 0x40, 0x00, 0x00, // [ 79] U+006F row 12 + 0x00, 0xBF, 0x50, 0x70, 0xAF, 0x60, 0x00, 0x00, // [ 80] U+0070 row 12 + 0x00, 0x00, 0x70, 0xAF, 0x60, 0x80, 0xBF, 0x00, // [ 81] U+0071 row 12 + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 12 + 0x00, 0x10, 0x70, 0xAF, 0x9F, 0x60, 0x00, 0x00, // [ 83] U+0073 row 12 + 0x00, 0x00, 0x00, 0x20, 0x80, 0x80, 0x80, 0x00, // [ 84] U+0074 row 12 + 0x00, 0x00, 0x70, 0xAF, 0x70, 0x40, 0x60, 0x00, // [ 85] U+0075 row 12 + 0x00, 0x00, 0x00, 0x70, 0x50, 0x00, 0x00, 0x00, // [ 86] U+0076 row 12 + 0x00, 0x10, 0x80, 0x00, 0x20, 0x80, 0x00, 0x00, // [ 87] U+0077 row 12 + 0x00, 0x60, 0x40, 0x00, 0x00, 0x50, 0x50, 0x00, // [ 88] U+0078 row 12 + 0x00, 0x00, 0x00, 0xBF, 0x70, 0x00, 0x00, 0x00, // [ 89] U+0079 row 12 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 90] U+007A row 12 + 0x00, 0x00, 0x00, 0xAF, 0x70, 0x00, 0x00, 0x00, // [ 91] U+007B row 12 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 12 + 0x00, 0x00, 0x00, 0x9F, 0x80, 0x00, 0x00, 0x00, // [ 93] U+007D row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 12 + 0x00, 0xBF, 0x9F, 0xAF, 0x70, 0x10, 0x8F, 0x60, // [ 96] U+00B5 row 12 + 0x00, 0x60, 0x80, 0x20, 0x40, 0x80, 0x60, 0x00, // [ 97] U+03A9 row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 12 + 0x40, 0xFF, 0x80, 0x80, 0x80, 0x80, 0xDF, 0x80, // [100] fallback box row 12 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 13 + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, // [ 4] U+0024 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 13 + 0x00, 0x00, 0x00, 0x10, 0xCF, 0xAF, 0x10, 0x00, // [ 8] U+0028 row 13 + 0x00, 0x10, 0xBF, 0x9F, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 13 + 0x00, 0x00, 0x00, 0x70, 0x8F, 0x00, 0x00, 0x00, // [ 12] U+002C row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 13 + 0x00, 0x9F, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 13 + 0x00, 0x00, 0x00, 0x70, 0x8F, 0x00, 0x00, 0x00, // [ 27] U+003B row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 13 + 0x00, 0x10, 0x8F, 0xBF, 0xBF, 0xBF, 0x60, 0x00, // [ 32] U+0040 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, // [ 49] U+0051 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 13 + 0x00, 0x00, 0xFF, 0x80, 0x80, 0x80, 0x00, 0x00, // [ 59] U+005B row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xAF, 0x60, 0x00, // [ 60] U+005C row 13 + 0x00, 0x20, 0x80, 0x80, 0x9F, 0xBF, 0x00, 0x00, // [ 61] U+005D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 13 + 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x60, 0x00, // [ 63] U+005F row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 13 + 0x00, 0x70, 0x20, 0x00, 0x00, 0x8F, 0x8F, 0x00, // [ 71] U+0067 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 13 + 0x00, 0x00, 0x00, 0x00, 0xCF, 0x40, 0x00, 0x00, // [ 74] U+006A row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 13 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 81] U+0071 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 13 + 0x00, 0x00, 0x10, 0xFF, 0x20, 0x00, 0x00, 0x00, // [ 89] U+0079 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 13 + 0x00, 0x00, 0x00, 0x40, 0xEF, 0x8F, 0x00, 0x00, // [ 91] U+007B row 13 + 0x00, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, // [ 92] U+007C row 13 + 0x00, 0x20, 0x9F, 0xEF, 0x20, 0x00, 0x00, 0x00, // [ 93] U+007D row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 13 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 13 + 0x30, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0x60, // [100] fallback box row 13 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 4] U+0024 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 14 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x70, 0x00, 0x00, // [ 8] U+0028 row 14 + 0x00, 0x10, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 14 + 0x00, 0x00, 0x20, 0xDF, 0x10, 0x00, 0x00, 0x00, // [ 12] U+002C row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 14 + 0x00, 0x70, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 14 + 0x00, 0x00, 0x20, 0xDF, 0x10, 0x00, 0x00, 0x00, // [ 27] U+003B row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 32] U+0040 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 14 + 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, // [ 59] U+005B row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x60, 0x00, // [ 60] U+005C row 14 + 0x00, 0x20, 0x80, 0x80, 0x80, 0x60, 0x00, 0x00, // [ 61] U+005D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 14 + 0x00, 0x8F, 0xDF, 0x80, 0x9F, 0xEF, 0x20, 0x00, // [ 71] U+0067 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 14 + 0x00, 0x60, 0x80, 0xBF, 0xCF, 0x00, 0x00, 0x00, // [ 74] U+006A row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 14 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF, 0x00, // [ 81] U+0071 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 14 + 0x00, 0x60, 0xCF, 0xAF, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 14 + 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00, // [ 91] U+007B row 14 + 0x00, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x00, // [ 92] U+007C row 14 + 0x00, 0x20, 0x70, 0x20, 0x00, 0x00, 0x00, 0x00, // [ 93] U+007D row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 14 + 0x00, 0xBF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 14 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 4] U+0024 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 15 + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 15 + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 32] U+0040 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 61] U+005D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 15 + 0x00, 0x00, 0x30, 0x80, 0x70, 0x10, 0x00, 0x00, // [ 71] U+0067 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 15 + 0x00, 0x30, 0x40, 0x40, 0x10, 0x00, 0x00, 0x00, // [ 74] U+006A row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 15 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x30, 0x00, // [ 81] U+0071 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 15 + 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 91] U+007B row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 92] U+007C row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 93] U+007D row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 15 + 0x00, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 15 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 0] U+0020 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 1] U+0021 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 2] U+0022 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 3] U+0023 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 4] U+0024 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 5] U+0025 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 6] U+0026 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 7] U+0027 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 8] U+0028 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 9] U+0029 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 10] U+002A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 11] U+002B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 12] U+002C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 13] U+002D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 14] U+002E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 15] U+002F row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 16] U+0030 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 17] U+0031 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 18] U+0032 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 19] U+0033 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 20] U+0034 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 21] U+0035 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 22] U+0036 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 23] U+0037 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 24] U+0038 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 25] U+0039 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 26] U+003A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 27] U+003B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 28] U+003C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 29] U+003D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 30] U+003E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 31] U+003F row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 32] U+0040 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 33] U+0041 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 34] U+0042 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 35] U+0043 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 36] U+0044 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 37] U+0045 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 38] U+0046 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 39] U+0047 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 40] U+0048 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 41] U+0049 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 42] U+004A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 43] U+004B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 44] U+004C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 45] U+004D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 46] U+004E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 47] U+004F row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 48] U+0050 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 49] U+0051 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 50] U+0052 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 51] U+0053 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 52] U+0054 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 53] U+0055 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 54] U+0056 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 55] U+0057 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 56] U+0058 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 57] U+0059 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 58] U+005A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 59] U+005B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 60] U+005C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 61] U+005D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 62] U+005E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 63] U+005F row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 64] U+0060 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 65] U+0061 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 66] U+0062 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 67] U+0063 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 68] U+0064 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 69] U+0065 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 70] U+0066 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 71] U+0067 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 72] U+0068 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 73] U+0069 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 74] U+006A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 75] U+006B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 76] U+006C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 77] U+006D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 78] U+006E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 79] U+006F row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 80] U+0070 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 81] U+0071 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 82] U+0072 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 83] U+0073 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 84] U+0074 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 85] U+0075 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 86] U+0076 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 87] U+0077 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 88] U+0078 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 89] U+0079 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 90] U+007A row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 91] U+007B row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 92] U+007C row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 93] U+007D row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 94] U+007E row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 95] U+00B0 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 96] U+00B5 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 97] U+03A9 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 98] U+2013 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [ 99] U+2014 row 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [100] fallback box row 16 +}; + +/// One slot's glyph: the cell it occupies in the atlas, drawn from the cell's +/// top-left (the pen) with no extra bearing -- the bearing is baked into the +/// cell's ink -- and the advance the pen moves by. +constexpr Glyph make_glyph(u32 slot) noexcept { + const f32 u0 = static_cast(slot * kCellW) / static_cast(kAtlasW); + const f32 du = static_cast(kCellW) / static_cast(kAtlasW); + const f32 dv = static_cast(kCellH) / static_cast(kAtlasH); + return Glyph{u0, 0.0f, u0 + du, dv, 0.0f, 0.0f, + static_cast(kAdvanceUnits[slot]) * kScale}; +} + +template +constexpr std::array make_glyphs( + std::integer_sequence) noexcept { + return {make_glyph(Slots)...}; +} + +constexpr std::array kGlyphs = + make_glyphs(std::make_integer_sequence{}); + +} // namespace + +u32 EmbeddedMonoFont::glyph_index(u32 codepoint) noexcept { + if (codepoint >= kAsciiFirst && codepoint < kAsciiFirst + kAsciiGlyphs) { + return codepoint - kAsciiFirst; + } + switch (codepoint) { + case 0x00B0u: + return kAsciiGlyphs + 0u; + case 0x00B5u: + return kAsciiGlyphs + 1u; + case 0x03A9u: + return kAsciiGlyphs + 2u; + case 0x2013u: + return kAsciiGlyphs + 3u; + case 0x2014u: + return kAsciiGlyphs + 4u; + default: + return kFallbackSlot; + } +} + +f32 EmbeddedMonoFont::advance_codepoint(u32 codepoint) noexcept { + return static_cast(kAdvanceUnits[glyph_index(codepoint)]) * kScale; +} + +f32 EmbeddedMonoFont::measure(const char* utf8) const { + if (utf8 == nullptr) { + return 0.0f; + } + f32 width = 0.0f; + for (const char* p = utf8; *p != '\0';) { + width += advance_codepoint(next_codepoint(p)); + } + return width; +} + +f32 EmbeddedMonoFont::height() const { return static_cast(kCellH); } + +f32 EmbeddedMonoFont::ascent() const { return kAscentPx; } + +f32 EmbeddedMonoFont::em_px() const { return static_cast(kEmPx); } + +const Glyph* EmbeddedMonoFont::glyph(u32 codepoint) const { + return &kGlyphs[glyph_index(codepoint)]; +} + +Vec2 EmbeddedMonoFont::cell_uv() const { + return Vec2{static_cast(kCellW) / static_cast(kAtlasW), + static_cast(kCellH) / static_cast(kAtlasH)}; +} + +const u8* EmbeddedMonoFont::atlas_pixels() const { return kAtlas; } + +u32 EmbeddedMonoFont::atlas_width() const { return kAtlasW; } + +u32 EmbeddedMonoFont::atlas_height() const { return kAtlasH; } + +u32 EmbeddedMonoFont::revision() const { return 0u; } + +u32 EmbeddedMonoFont::glyph_count() noexcept { return kGlyphCount; } + +u32 EmbeddedMonoFont::codepoint_of_slot(u32 slot) noexcept { + return slot < kGlyphCount ? kCodepoints[slot] : kFallbackCodepoint; +} + +u32 EmbeddedMonoFont::cell_width() noexcept { return kCellW; } + +u32 EmbeddedMonoFont::cell_height() noexcept { return kCellH; } + +Vec2 EmbeddedMonoFont::atlas_origin(u32 slot) noexcept { + const u32 s = slot < kGlyphCount ? slot : kFallbackSlot; + return Vec2{static_cast(s * kCellW), 0.0f}; +} + +const u8* EmbeddedMonoFont::atlas_image() noexcept { return kAtlas; } + +} // namespace plot::render diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index 8e61fe9..b7fda10 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -88,23 +88,25 @@ f32 walk_text_clipped(Vec2 pen, const char* utf8, f32 budget_px, const render::T void warn_replaced_text_backend(const render::TextBackend* previous) noexcept { std::fprintf(stderr, "plot::Renderer::draw: replacing the Plot's TextBackend (%p) with the renderer's " - "own bitmap font; a plot drawn by a Renderer measures the labels it draws.\n", + "own embedded font; a plot drawn by a Renderer measures the labels it draws.\n", static_cast(previous)); } #endif } // namespace -void Renderer::AxisFont::reset(const render::BitmapFont& font, f32 requested_px) noexcept { +void Renderer::AxisFont::reset(const render::TextBackend& font, f32 requested_px) noexcept { font_ = &font; - const f32 native = static_cast(render::BitmapFont::kGlyphPx); - // Whole multiples of the native cell only: a 1-bit glyph drawn at 1.5x lands - // its strokes on uneven pixel counts, and the layout has to agree with what the - // pipeline draws, so the requested size picks the nearest multiple. - f32 steps = std::round(requested_px / native); - if (!(steps >= 1.0f)) steps = 1.0f; // also catches NaN - cell_px_ = steps * native; - scale_ = cell_px_ / native; + // The requested size is an em size, exactly as `em_px()` reports the size the + // atlas was baked at, so the ratio between them is all the scaling there is: + // an anti-aliased face is legible at any ratio, and a style asking for 12 px + // from a 13 px bake gets 12/13 rather than a snapped whole multiple. A backend + // that cannot say (`em_px()` at or below zero, or NaN) keeps a 1:1 scale rather + // than dividing by it. + const f32 baked = font.em_px(); + const f32 ratio = requested_px / baked; + scale_ = (baked > 0.0f && ratio > 0.0f) ? ratio : 1.0f; + cell_px_ = font.height() * scale_; } f32 Renderer::AxisFont::measure(const char* utf8) const { @@ -119,6 +121,12 @@ f32 Renderer::AxisFont::ascent() const { return font_ != nullptr ? font_->ascent() * scale_ : 0.0f; } +f32 Renderer::AxisFont::em_px() const { + // The em is the one metric that does not scale: it is the face's own unit, and + // it is what the scale above was computed from. + return font_ != nullptr ? font_->em_px() : 0.0f; +} + const render::Glyph* Renderer::AxisFont::glyph(u32 codepoint) const { if (font_ == nullptr) { return nullptr; // nothing to draw with, so nothing to describe @@ -169,7 +177,7 @@ bool Renderer::init(Status* st) noexcept { const render::UploadMode mode = persistent ? render::UploadMode::PersistentMapped : render::UploadMode::Orphan; - font_backend_.reset(font_, style_.axis_font_px); + font_backend_.reset(default_font_, style_.axis_font_px); // The atlas is the backend's: `sync()` uploads whatever it exposes, at whatever // size it says, and restores the host's unpack alignment around the upload @@ -263,7 +271,7 @@ void Renderer::draw(Plot& plot) { // The plot reserves its label strips from TextBackend metrics, so it must // measure with the size the text pipeline draws with, and re-read the style // each frame so a changed axis_font_px takes effect on the next one. - font_backend_.reset(font_, style_.axis_font_px); + font_backend_.reset(default_font_, style_.axis_font_px); // The cell every glyph staged this frame is drawn in, declared from the backend // rather than left to whichever glyph is staged first: the instances carry rect // origins, and the draw carries this size (see TextBackend::cell_uv). @@ -280,7 +288,7 @@ void Renderer::draw(Plot& plot) { } #ifndef NDEBUG // The renderer owns the text of the frames it draws: every label goes through - // the text pipeline in `font_`, so the plot is handed this font's metrics + // the text pipeline in `default_font_`, so the plot is handed this backend's metrics // whatever a host installed. A host backend is therefore replaced on the first // draw -- said out loud in a debug build, since its disappearance is otherwise // silent -- and a Plot nobody draws keeps it (see Plot::set_text_backend()). @@ -456,17 +464,17 @@ void Renderer::stage_xy(const f32* xy, u32 n, bool log_x, bool log_y) { Vec2 Renderer::stage_text(Vec2 pos_px, const char* utf8) { // Text is staged in viewport pixels: the text program has no offset uniform. - return glyphs_.add_text(utf8, to_viewport(pos_px), font_backend_.scale(), font_); + return glyphs_.add_text(utf8, to_viewport(pos_px), font_backend_.scale(), default_font_); } f32 Renderer::clipped_text_width(const char* utf8, f32 budget_px) const { - return walk_text_clipped(Vec2{}, utf8, budget_px, font_, font_backend_.scale(), + return walk_text_clipped(Vec2{}, utf8, budget_px, default_font_, font_backend_.scale(), [](Vec2, const render::Glyph&) {}); } f32 Renderer::stage_text_clipped(Vec2 pos_px, const char* utf8, f32 budget_px) { const f32 scale = font_backend_.scale(); - return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, font_, scale, + return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, default_font_, scale, [this, scale](Vec2 pen, const render::Glyph& glyph) { glyphs_.add_glyph(pen, glyph, scale); }); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2262367..4d19a1c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,7 +70,12 @@ target_include_directories(plot_tests PRIVATE "${CMAKE_SOURCE_DIR}/adapters") target_compile_definitions(plot_tests PRIVATE PLOT_PYRAMID_COUNT_COVERS PLOT_PYRAMID_COUNT_MERGES PLOT_RING_STATS - PLOT_COUNT_MEASURE_RECOMPUTES) + PLOT_COUNT_MEASURE_RECOMPUTES + # The committed source root, so the + # atlas-generator case can run + # tools/fonts/gen_atlas.py --check + # against the committed atlas. + PLOT_SOURCE_DIR="${CMAKE_SOURCE_DIR}") plot_apply_warnings(plot_tests) add_test(NAME plot_tests COMMAND plot_tests) diff --git a/tests/test_gl_context.cpp b/tests/test_gl_context.cpp index a1ee7f3..517321a 100644 --- a/tests/test_gl_context.cpp +++ b/tests/test_gl_context.cpp @@ -6,13 +6,14 @@ #include #include #include -#include +#include #include #include #include #include #include +#include #include #include #include @@ -341,10 +342,10 @@ PLOT_TEST(line_pipeline_draws_an_aa_strip) { // The text pipeline end to end: the R8 atlas upload, the per-instance glyph UV // rect, the NEAREST texel-centre sampling, the tint and the same single y flip. -// Every one of the cell's 64 pixels is checked against the glyph bitmap, so a -// wrong atlas cell, a wrong UV or a transposed axis cannot pass. +// Every texel of the cell is checked against the backend's own coverage byte, so +// a wrong atlas cell, a wrong UV or a transposed axis cannot pass. PLOT_TEST(text_pipeline_draws_a_glyph) { - using plot::render::BitmapFont; + using plot::render::EmbeddedMonoFont; plottest::HeadlessContext ctx(256, 256); plot::Status st; PLOT_CHECK(ctx.create(&st)); @@ -352,12 +353,12 @@ PLOT_TEST(text_pipeline_draws_a_glyph) { // The backend's atlas, through the owner the renderer uses: single channel, // GL_NEAREST, straight alpha, uploaded from the backend's own bytes. - BitmapFont font; + EmbeddedMonoFont font; plot::render::TextAtlas atlas; PLOT_CHECK(atlas.sync(font, &st)); PLOT_CHECK(!atlas.empty()); - PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast(BitmapFont::kAtlasWidth), 1e-6); - PLOT_CHECK_NEAR(atlas.texel_size().y, static_cast(BitmapFont::kAtlasHeight), 1e-6); + PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast(font.atlas_width()), 1e-6); + PLOT_CHECK_NEAR(atlas.texel_size().y, static_cast(font.atlas_height()), 1e-6); plot::render::TextProgram program; PLOT_CHECK(program.create(&st)); @@ -383,29 +384,44 @@ PLOT_TEST(text_pipeline_draws_a_glyph) { program.bind(); program.set_viewport({static_cast(ctx.width), static_cast(ctx.height)}); - program.set_px_range({static_cast(BitmapFont::kGlyphPx), static_cast(BitmapFont::kGlyphPx)}); + // The backend's own cell, on screen at 1:1: the draw's one rect size. + const plot::f32 cell_w = static_cast(EmbeddedMonoFont::cell_width()); + const plot::f32 cell_h = static_cast(EmbeddedMonoFont::cell_height()); + program.set_px_range({cell_w, cell_h}); program.set_glyph_uv(font.cell_uv()); program.set_texture_size(atlas.texel_size()); - program.set_color(plot::Color::hex(0xFF00FF)); + // White, so the blended pixel value *is* the coverage byte: the shader emits + // `vec4(rgb, a * coverage)`, and a white tint over the black clear lands at + // `1.0 * coverage` in every channel. + program.set_color(plot::Color::hex(0xFFFFFF)); atlas.bind(); glyphs.bind(); plot::gl::glDrawArraysInstanced(plot::gl::GL_TRIANGLE_STRIP, 0, 4, 1); - // One cell is 8x8 texels and u_px_range is 8, so texel (gx, gy) lands on one - // target pixel: Rect x = 64 + gx, and Rect y = 100 + gy is GL row 155 - gy. - const plot::u8* rows = font.glyph_bitmap('A'); + // The cell is cell_w x cell_h texels and u_px_range is the same, so texel + // (gx, gy) lands on exactly one target pixel: Rect x = 64 + gx, and Rect y = + // 100 + gy is GL row 155 - gy. A transposed axis, a wrong cell or a wrong UV + // all move ink away from where the coverage byte says it is. + const plot::u32 slot = EmbeddedMonoFont::glyph_index(static_cast('A')); + const plot::Vec2 origin = EmbeddedMonoFont::atlas_origin(slot); + const plot::u8* atlas_bytes = font.atlas_pixels(); + plot::u32 antialiased = 0; plot::u8 px[4] = {}; - for (plot::u32 gy = 0; gy < BitmapFont::kGlyphPx; ++gy) { - for (plot::u32 gx = 0; gx < BitmapFont::kGlyphPx; ++gx) { - const bool ink = ((rows[gy] >> gx) & 1u) != 0u; + for (plot::u32 gy = 0; gy < EmbeddedMonoFont::cell_height(); ++gy) { + for (plot::u32 gx = 0; gx < EmbeddedMonoFont::cell_width(); ++gx) { + const plot::u32 ax = static_cast(origin.x) + gx; + const plot::u32 ay = static_cast(origin.y) + gy; + const plot::u8 want = atlas_bytes[static_cast(ay) * font.atlas_width() + ax]; PLOT_CHECK(ctx.read_pixel(64u + gx, 155u - gy, px)); - if (ink) { - PLOT_CHECK(px[0] > 200 && px[1] < 60 && px[2] > 200); - } else { - PLOT_CHECK(px[0] < 40 && px[2] < 40); - } + PLOT_CHECK_NEAR(px[0], static_cast(want), 2.0); + PLOT_CHECK_NEAR(px[1], static_cast(want), 2.0); + PLOT_CHECK_NEAR(px[2], static_cast(want), 2.0); + if (want > 0u && want < 255u) ++antialiased; } } + // 'A' has grey edge texels and they reached the framebuffer: a 1-bit atlas + // could only ever have produced 0 or 255 here. + PLOT_CHECK(antialiased > 0u); PLOT_CHECK(plot::gl::glGetError() == plot::gl::GL_NO_ERROR); ctx.destroy(); } diff --git a/tests/test_plot.cpp b/tests/test_plot.cpp index 0668048..e37b031 100644 --- a/tests/test_plot.cpp +++ b/tests/test_plot.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include @@ -73,7 +73,7 @@ PLOT_TEST(layout_without_text_backend_uses_default_label_areas) { } PLOT_TEST(layout_derives_label_areas_from_font_metrics) { - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::Plot p; p.set_text_backend(&font); p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f}); @@ -103,7 +103,7 @@ PLOT_TEST(layout_derives_label_areas_from_font_metrics) { /// Every titled axis of a direction gets its own text line inside its strip; /// without that, the second titled axis of an edge is drawn on top of the first. PLOT_TEST(layout_stacks_one_title_line_per_titled_axis) { - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::Plot p; p.set_text_backend(&font); p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f}); @@ -127,7 +127,7 @@ PLOT_TEST(layout_stacks_one_title_line_per_titled_axis) { /// An axis with no title reserves nothing: the strips exist so that a titled axis /// has somewhere outside `axes` to draw in, not as a fixed cost per plot. PLOT_TEST(layout_reserves_no_title_strip_without_a_title) { - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::Plot p; p.set_text_backend(&font); p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f}); diff --git a/tests/test_primitives.cpp b/tests/test_primitives.cpp index 397465e..952c0f3 100644 --- a/tests/test_primitives.cpp +++ b/tests/test_primitives.cpp @@ -1,12 +1,17 @@ // CPU-side contract of the render layer: the staging batches that feed the -// instanced line and text pipelines, and the bitmap font's metrics and atlas. +// instanced line and text pipelines, and the embedded font's metrics and atlas. // Nothing here touches GL, so it runs on any machine that can build the tests. #include "harness.hpp" -#include +#include #include #include +#include +#include +#include +#include +#include PLOT_TEST(line_batch_emits_two_verts_per_segment) { plot::render::LineBatch b; @@ -99,152 +104,293 @@ PLOT_TEST(line_batch_reuses_capacity_across_frames) { PLOT_CHECK(b.data().data() == store); } -PLOT_TEST(text_batch_measures_ascii) { - plot::render::BitmapFont f; - const f32 w = f.measure("12.5 ms"); - PLOT_CHECK(w > 0.0f); - PLOT_CHECK_NEAR(f.measure(""), 0.0, 1e-6); - PLOT_CHECK(f.measure("ab") < f.measure("abc")); // monotonic in length - PLOT_CHECK(f.measure("iiii") < f.measure("WWWW")); // proportional-ish, not fixed-width-blank +namespace { + +using plot::render::EmbeddedMonoFont; + +/// The coverage byte of one texel of `slot`'s cell: 0 background, 255 full ink. +plot::u8 cell_texel(const EmbeddedMonoFont& font, plot::u32 slot, plot::u32 col, plot::u32 row) { + const plot::Vec2 origin = EmbeddedMonoFont::atlas_origin(slot); + const plot::u32 x = static_cast(origin.x) + col; + const plot::u32 y = static_cast(origin.y) + row; + const std::size_t index = static_cast(y) * font.atlas_width() + x; + return font.atlas_image()[index]; } -PLOT_TEST(bitmap_font_advance_follows_ink_extent) { - plot::render::BitmapFont f; - PLOT_CHECK(f.advance('i') < f.advance('W')); - // Whitespace is a blank glyph with a nonzero advance, never zero. - PLOT_CHECK(f.advance(' ') >= 2.0f); - // Advance is ink width + 1px of spacing, floored at 2px: derived here from - // the same bitmaps the atlas uploads, so a second width table cannot drift. - auto ink_width = [&f](char c) { - const plot::u8* rows = f.glyph_bitmap(c); - int lo = 8; - int hi = -1; - for (plot::u32 y = 0; y < f.glyph_rows(c); ++y) { - for (int x = 0; x < 8; ++x) { - if ((rows[y] >> x) & 1u) { - if (x < lo) lo = x; - if (x > hi) hi = x; - } - } +/// Total coverage of one cell: zero means the cell rasterised blank. +plot::u32 cell_ink_units(const EmbeddedMonoFont& font, plot::u32 slot) { + plot::u32 total = 0u; + for (plot::u32 y = 0; y < EmbeddedMonoFont::cell_height(); ++y) { + for (plot::u32 x = 0; x < EmbeddedMonoFont::cell_width(); ++x) { + total += cell_texel(font, slot, x, y); } - return hi < 0 ? 0 : hi - lo + 1; - }; - const char* samples = "iW.1_m"; - for (const char* c = samples; *c != '\0'; ++c) { - const int ink = ink_width(*c); - const f32 want = ink + 1 < 2 ? 2.0f : static_cast(ink + 1); - PLOT_CHECK_NEAR(f.advance(*c), want, 1e-6); } - // measure() is exactly the sum of the glyph advances it stages. - f32 sum = 0.0f; - for (const char* c = "12.5 ms"; *c != '\0'; ++c) sum += f.advance(*c); - PLOT_CHECK_NEAR(f.measure("12.5 ms"), sum, 1e-6); + return total; } -PLOT_TEST(bitmap_font_decodes_si_units_and_falls_back_visibly) { - plot::render::BitmapFont f; - // plot::format_si emits U+00B5 MICRO SIGN, U+00B0 DEGREE SIGN and U+03A9 OHM. +/// The fallback slot, found by the marker codepoint rather than assumed. +plot::u32 fallback_slot() { + for (plot::u32 slot = 0; slot < EmbeddedMonoFont::glyph_count(); ++slot) { + if (EmbeddedMonoFont::codepoint_of_slot(slot) == EmbeddedMonoFont::kFallbackCodepoint) { + return slot; + } + } + return EmbeddedMonoFont::glyph_count(); +} + +/// The plot library's own source root, so the generator test can run the committed +/// script against the committed output. Injected by tests/CMakeLists.txt. +constexpr const char* kSourceDir = PLOT_SOURCE_DIR; + +} // namespace + +// The embedded atlas' metrics: a line taller than its ascent (descenders need the +// room), a non-empty advance, and -- the point of the face -- a fixed advance, so +// a label's width is its length and nothing else. A text backend with variable +// advances is the tile the plot library deliberately does not lay out (see the +// spec's non-goals), so `i` and `W` measure the same and a caller cannot be +// surprised by a label's reserved strip. +PLOT_TEST(embedded_font_metrics_are_sane) { + const EmbeddedMonoFont f; + PLOT_CHECK(f.height() > f.ascent()); + PLOT_CHECK(f.ascent() > 0.0f); + PLOT_CHECK_NEAR(f.measure(""), 0.0, 1e-6); + PLOT_CHECK_NEAR(f.measure(nullptr), 0.0, 1e-6); + // One advance per codepoint, whatever the glyph: this is `i` and `W` alike. + PLOT_CHECK_NEAR(f.measure("i"), f.measure("W"), 1e-6); + PLOT_CHECK_NEAR(f.measure("i"), EmbeddedMonoFont::advance_codepoint('i'), 1e-6); + // Monotone in length, and exactly the sum of the advances it stages. + PLOT_CHECK(f.measure("ab") < f.measure("abc")); + PLOT_CHECK(f.measure("abc") < f.measure("abcd")); + PLOT_CHECK_NEAR(f.measure("12.5 ms"), + (f.glyph('1')->advance + f.glyph('2')->advance + f.glyph('.')->advance + + f.glyph('5')->advance + f.glyph(' ')->advance + f.glyph('m')->advance + + f.glyph('s')->advance), + 1e-6); + // The em is the size the table was baked at, and the scale the renderer draws + // with is a ratio of it -- so it must be a positive number and not `height()`. + PLOT_CHECK_NEAR(f.em_px(), static_cast(EmbeddedMonoFont::kEmPx), 1e-6); + PLOT_CHECK(f.em_px() > 0.0f); +} + +// Every codepoint the atlas claims to cover has its own cell, the cell is inside +// the atlas and is the declared cell, and its ink is non-empty -- the generator +// refuses to emit a blank cell for a covered codepoint, and this pins that the +// committed atlas is the generator's output and not a hand edit. U+0020 is the +// one documented blank: a space has no ink by definition and still advances. +PLOT_TEST(embedded_atlas_covers_every_expected_codepoint) { + const EmbeddedMonoFont f; + const plot::Vec2 cell = f.cell_uv(); + PLOT_CHECK_NEAR(cell.x, static_cast(EmbeddedMonoFont::cell_width()) / + static_cast(f.atlas_width()), + 1e-9); + PLOT_CHECK_NEAR(cell.y, static_cast(EmbeddedMonoFont::cell_height()) / + static_cast(f.atlas_height()), + 1e-9); + PLOT_CHECK(f.atlas_pixels() != nullptr); + + std::vector expected; + for (plot::u32 cp = 0x20u; cp <= 0x7Eu; ++cp) expected.push_back(cp); + for (plot::u32 cp : {0x00B0u, 0x00B5u, 0x03A9u, 0x2013u, 0x2014u}) expected.push_back(cp); + for (plot::u32 cp : expected) { + const plot::u32 slot = EmbeddedMonoFont::glyph_index(cp); + PLOT_CHECK(slot < EmbeddedMonoFont::glyph_count()); + PLOT_CHECK(EmbeddedMonoFont::codepoint_of_slot(slot) == cp); + const plot::render::Glyph* g = f.glyph(cp); + PLOT_CHECK(g != nullptr); + // The rect is the atlas cell, and it lies inside the atlas. + PLOT_CHECK(plot::render::glyph_is_cell(*g, cell)); + PLOT_CHECK(g->u0 >= 0.0f && g->u1 <= 1.0f && g->v0 >= 0.0f && g->v1 <= 1.0f); + if (cp == 0x20u) { + PLOT_CHECK(cell_ink_units(f, slot) == 0u); // a space is blank ... + PLOT_CHECK(g->advance > 0.0f); // ... and still advances + } else { + PLOT_CHECK(cell_ink_units(f, slot) > 0u); // a covered glyph has ink + } + } + + // The fallback box is a real cell with real ink, at a fixed slot, and every + // codepoint outside the coverage set draws it -- so a label the atlas cannot + // spell is visible rather than silently blank. + const plot::u32 fallback = fallback_slot(); + PLOT_CHECK(fallback < EmbeddedMonoFont::glyph_count()); + PLOT_CHECK(cell_ink_units(f, fallback) > 0u); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x4E00u) == fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0xFFFFu) == fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(plot::render::kInvalidCodepoint) == fallback); + PLOT_CHECK(f.glyph(0x4E00u)->advance > 0.0f); +} + +// The atlas carries more than one coverage value: an anti-aliased raster has grey +// edges, and a 1-bit table could only ever be 0 or 255. This is the data-level +// half of the anti-aliasing evidence (the frame-level half is in the render +// smoke test): a table that passed through a threshold would fail it. +PLOT_TEST(embedded_atlas_holds_antialiased_coverage) { + const EmbeddedMonoFont f; + const plot::u8* atlas = f.atlas_pixels(); + const std::size_t size = static_cast(f.atlas_width()) * f.atlas_height(); + plot::u32 partial = 0u; + plot::u32 full = 0u; + plot::u32 blank = 0u; + for (std::size_t i = 0; i < size; ++i) { + if (atlas[i] == 0u) ++blank; + else if (atlas[i] == 255u) ++full; + else ++partial; + } + PLOT_CHECK(full > 0u); + PLOT_CHECK(blank > 0u); + // Every glyph's stroke edge contributes: the partial population is a large + // fraction of the inked texels, not a handful of rounding artefacts. + PLOT_CHECK(partial > full / 2u); +} + +// The face is monospaced, so every codepoint's advance is the same -- but it is +// the font's own `hmtx` advance carried through the bake, not a constant invented +// here: `advance_codepoint` and the per-glyph `advance` agree, and the whitespace +// glyphs that must never be zero are not. +PLOT_TEST(embedded_font_advance_matches_the_font_units) { + const EmbeddedMonoFont f; + const plot::f32 advance = EmbeddedMonoFont::advance_codepoint('A'); + PLOT_CHECK(advance > 0.0f); + PLOT_CHECK(EmbeddedMonoFont::advance_codepoint(' ') > 0.0f); + const char* samples = "iW.1_m "; + for (const char* c = samples; *c != '\0'; ++c) { + PLOT_CHECK_NEAR(f.glyph(static_cast(*c))->advance, advance, 1e-6); + } + // `advance_codepoint` is a lookup, not a walk: it agrees with `measure` for a + // single decoded codepoint, and disagrees with nothing in the table. + PLOT_CHECK_NEAR(f.measure("W"), EmbeddedMonoFont::advance_codepoint('W'), 1e-6); + for (plot::u32 cp = 0x20u; cp <= 0x7Eu; ++cp) { + PLOT_CHECK_NEAR(f.glyph(cp)->advance, EmbeddedMonoFont::advance_codepoint(cp), 1e-6); + } +} + +// Decoding and the fallback, on the codepoints `plot::format_si` and a plotting +// label actually emit: the three SI symbols are real glyphs, everything else is +// the visible fallback box, and a malformed byte is not measured as zero. +PLOT_TEST(embedded_font_decodes_si_units_and_falls_back_visibly) { + const EmbeddedMonoFont f; + const plot::u32 fallback = fallback_slot(); const f32 micro = f.measure("\xC2\xB5s"); const f32 degree = f.measure("\xC2\xB0"); const f32 ohm = f.measure("\xCE\xA9"); PLOT_CHECK(micro > 0.0f); PLOT_CHECK(degree > 0.0f); PLOT_CHECK(ohm > 0.0f); - PLOT_CHECK_NEAR(micro, f.advance_codepoint(0x00B5u) + f.advance('s'), 1e-6); - PLOT_CHECK_NEAR(degree, f.advance_codepoint(0x00B0u), 1e-6); - PLOT_CHECK_NEAR(ohm, f.advance_codepoint(0x03A9u), 1e-6); - // Those three map to their own renderable glyph, not to the missing-glyph box. - const plot::u32 fallback = f.glyph_index(0x4E00u); // CJK, deliberately absent - PLOT_CHECK(f.glyph_index(0x00B5u) != fallback); - PLOT_CHECK(f.glyph_index(0x00B0u) != fallback); - PLOT_CHECK(f.glyph_index(0x03A9u) != fallback); - PLOT_CHECK(f.glyph_index(0x00B5u) != f.glyph_index(0x00B0u)); - PLOT_CHECK(f.glyph_index(0x00B0u) != f.glyph_index(0x03A9u)); - // Anything else non-ASCII is a visible box with a nonzero advance, and a - // malformed UTF-8 byte is treated the same way rather than measured as zero. - PLOT_CHECK_NEAR(f.measure("\xE4\xB8\x80"), f.advance_codepoint(0x4E00u), 1e-6); - PLOT_CHECK(f.advance_codepoint(0x4E00u) > 0.0f); - PLOT_CHECK(f.advance(static_cast(0x80)) > 0.0f); - PLOT_CHECK_NEAR(f.advance(static_cast(0x80)), f.advance_codepoint(0x4E00u), 1e-6); - // The fallback box glyph has ink; a blank bitmap would hide the problem. - bool any_ink = false; - const plot::u8* box = plot::render::BitmapFont::glyph_bitmap_of(fallback); - for (plot::u32 y = 0; y < plot::render::BitmapFont::kGlyphPx; ++y) { - any_ink = any_ink || box[y] != 0; - } - PLOT_CHECK(any_ink); + PLOT_CHECK_NEAR(micro, EmbeddedMonoFont::advance_codepoint(0x00B5u) + f.glyph('s')->advance, 1e-6); + // Each maps to its own cell, not to the box. + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x00B5u) != fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x00B0u) != fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x03A9u) != fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x00B5u) != EmbeddedMonoFont::glyph_index(0x00B0u)); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x00B0u) != EmbeddedMonoFont::glyph_index(0x03A9u)); + // The two dashes the coverage adds are real cells too, not the box. + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x2013u) != fallback); + PLOT_CHECK(EmbeddedMonoFont::glyph_index(0x2014u) != fallback); + // A CJK codepoint and a bare malformed byte both draw the box, and both + // advance: a missing glyph is visible and never a zero-width hole. + PLOT_CHECK_NEAR(f.measure("\xE4\xB8\x80"), EmbeddedMonoFont::advance_codepoint(0x4E00u), 1e-6); + PLOT_CHECK_NEAR(f.measure("\x80"), EmbeddedMonoFont::advance_codepoint(0x4E00u), 1e-6); } -// The packed atlas must be left-to-right in absolute terms: `(row >> x) & 1` is -// also how every other expectation here is derived, so a table transcribed with -// the opposite in-row bit order would keep those tests green while drawing -// mirrored text. 'A' cannot catch that (it is -// symmetric about its vertical axis); 'L', 'b' and '/' can. -PLOT_TEST(bitmap_font_bit_order_runs_left_to_right) { - using plot::render::BitmapFont; - const plot::u8* atlas = BitmapFont::atlas_image(); - auto ink = [atlas](char c, plot::u32 col, plot::u32 row) { - const plot::u32 slot = static_cast(c) - BitmapFont::kFirstChar; - const plot::Vec2 origin = BitmapFont::atlas_origin(slot); - const auto x = static_cast(origin.x) + col; - const auto y = static_cast(origin.y) + row; - return atlas[y * BitmapFont::kAtlasWidth + x] != 0u; +// The atlas must be left-to-right in absolute terms. The coverage bytes are what +// the GPU samples, so a generator that rasterised with the opposite x sense -- or +// an atlas packed with the cells reversed -- would keep every rect check green +// while drawing mirrored text. 'A' cannot catch that (it is symmetric about its +// vertical axis); 'L', 'b' and '/' can, and they are asserted against absolute +// texel coordinates rather than against the table itself. +PLOT_TEST(embedded_atlas_orientation_runs_left_to_right) { + const EmbeddedMonoFont f; + using plot::render::EmbeddedMonoFont; + auto ink = [&f](char c, plot::u32 col, plot::u32 row) { + const plot::u32 slot = EmbeddedMonoFont::glyph_index(static_cast(c)); + return cell_texel(f, slot, col, row) != 0u; }; - // 'L': stem and top serif on the left, the foot running right along the - // bottom row and stopping one texel short of the right edge. - PLOT_CHECK(ink('L', 0u, 0u)); - PLOT_CHECK(!ink('L', 7u, 0u)); - PLOT_CHECK(ink('L', 1u, 3u)); - PLOT_CHECK(!ink('L', 6u, 3u)); - PLOT_CHECK(ink('L', 6u, 6u)); - PLOT_CHECK(!ink('L', 7u, 6u)); - // 'b': the stem is the leftmost column of the upper rows. - PLOT_CHECK(ink('b', 0u, 0u)); - PLOT_CHECK(!ink('b', 7u, 0u)); - // '/': starts at the right of the top row and ends at the left of the last - // one — the opposite of '\'. - PLOT_CHECK(ink('/', 6u, 0u)); - PLOT_CHECK(!ink('/', 0u, 0u)); - PLOT_CHECK(ink('/', 0u, 6u)); - PLOT_CHECK(!ink('/', 6u, 6u)); + // 'L': the stem runs down the left, and the foot runs right along the bottom -- + // so at cap height there is ink on the left and none on the right, and the + // bottom row has ink to the right of where the stem stopped. + PLOT_CHECK(ink('L', 1u, 6u)); + PLOT_CHECK(!ink('L', 6u, 6u)); + PLOT_CHECK(ink('L', 5u, 11u)); + PLOT_CHECK(!ink('L', 5u, 3u)); + // 'b': the stem is the left column of the upper rows, the bowl the right. + PLOT_CHECK(ink('b', 1u, 4u)); + PLOT_CHECK(!ink('b', 6u, 4u)); + PLOT_CHECK(ink('b', 6u, 8u)); + // '/': starts at the top right and ends at the bottom left -- the opposite of + // '\'. This is the pair that pins the diagonal's direction. + PLOT_CHECK(ink('/', 5u, 2u)); + PLOT_CHECK(!ink('/', 2u, 2u)); + PLOT_CHECK(ink('/', 2u, 12u)); + PLOT_CHECK(!ink('/', 5u, 12u)); } -PLOT_TEST(bitmap_font_atlas_is_uploadable) { - using plot::render::BitmapFont; - PLOT_CHECK(BitmapFont::kAtlasWidth == BitmapFont::kAtlasCols * BitmapFont::kGlyphPx); - PLOT_CHECK(BitmapFont::kAtlasHeight % BitmapFont::kGlyphPx == 0); - PLOT_CHECK(BitmapFont::kAtlasCols * BitmapFont::kAtlasRows >= BitmapFont::kGlyphCount); - PLOT_CHECK(BitmapFont::kAsciiGlyphs == 95); +// The atlas is a fixed-cell grid of one texel format: a whole number of cells +// across, the cell dividing the height, and every glyph's rect the declared cell +// -- so the pipeline's single per-draw rect size is the right one for every glyph. +PLOT_TEST(embedded_atlas_is_uploadable) { + using plot::render::EmbeddedMonoFont; + PLOT_CHECK(EmbeddedMonoFont::cell_width() > 0u); + PLOT_CHECK(EmbeddedMonoFont::cell_height() > 0u); + PLOT_CHECK(EmbeddedMonoFont::glyph_count() == 95u + 5u + 1u); // ASCII, SI/dashes, box - plot::render::BitmapFont f; - PLOT_CHECK(f.glyph_rows('A') == 8); + const EmbeddedMonoFont f; + PLOT_CHECK(f.atlas_width() == EmbeddedMonoFont::cell_width() * EmbeddedMonoFont::glyph_count()); + PLOT_CHECK(f.atlas_height() == EmbeddedMonoFont::cell_height()); + PLOT_CHECK(f.atlas_pixels() != nullptr); PLOT_CHECK(f.height() > 0.0f); PLOT_CHECK(f.ascent() > 0.0f); - PLOT_CHECK(f.ascent() <= f.height()); + PLOT_CHECK(f.ascent() < f.height()); - // Each glyph occupies its own atlas cell, and that cell is the bitmap byte - // for byte: 255 where a row bit is set, 0 elsewhere (straight alpha). - const plot::u8* atlas = BitmapFont::atlas_image(); - const plot::u32 slots[] = {'A' - 32, 'g' - 32, 'i' - 32, 'W' - 32, 0, '.' - 32, - BitmapFont::kFallbackSlot, BitmapFont::kMicroSlot}; - for (const plot::u32 slot : slots) { - const plot::Vec2 o = BitmapFont::atlas_origin(slot); - const plot::u8* rows = BitmapFont::glyph_bitmap_of(slot); - for (plot::u32 y = 0; y < BitmapFont::kGlyphPx; ++y) { - for (plot::u32 x = 0; x < BitmapFont::kGlyphPx; ++x) { - const plot::u8 want = ((rows[y] >> x) & 1u) != 0u ? 0xFFu : 0x00u; - const auto row = static_cast(o.y) + y; - const auto col = static_cast(o.x) + x; - PLOT_CHECK(atlas[row * BitmapFont::kAtlasWidth + col] == want); - } + // Every cell, the fallback included, is the declared cell and holds at least + // one non-zero byte (bar the space), and the ASCII codepoints are a direct + // index into the table. + for (plot::u32 slot = 0; slot < EmbeddedMonoFont::glyph_count(); ++slot) { + const plot::u32 cp = EmbeddedMonoFont::codepoint_of_slot(slot); + if (cp != EmbeddedMonoFont::kFallbackCodepoint) { + PLOT_CHECK(EmbeddedMonoFont::glyph_index(cp) == slot); } + const plot::render::Glyph* g = f.glyph(cp); + PLOT_CHECK(plot::render::glyph_is_cell(*g, f.cell_uv())); } - // The atlas is a whole number of cells and no glyph leaves its cell. - PLOT_CHECK(BitmapFont::kAtlasWidth == 16u * BitmapFont::kGlyphPx); + for (plot::u32 cp = 0x20u; cp <= 0x7Eu; ++cp) { + PLOT_CHECK(EmbeddedMonoFont::glyph_index(cp) == cp - 0x20u); + } + // A single-row atlas: the cell spans the whole height, so v is 0..1. + PLOT_CHECK_NEAR(f.cell_uv().y, 1.0, 1e-9); + PLOT_CHECK_NEAR(EmbeddedMonoFont::atlas_origin(0u).x, 0.0, 1e-9); + PLOT_CHECK_NEAR(EmbeddedMonoFont::atlas_origin(1u).x, + static_cast(EmbeddedMonoFont::cell_width()), 1e-9); +} + +// The committed atlas is the generator's output, not a hand edit, and the +// generator is deterministic: the same source font, em size and supersample +// factor produce it byte for byte. The check runs the committed script against +// the committed file, so a table touched by hand, a stale bake after a coverage +// change, or a non-deterministic generator all fail here. +// +// `fontTools` is an authoring-time tool and the test machine is not required to +// have it. Its absence is *reported*, not silently passed: the generator exits +// with a distinct code for it, and this case says so on stdout. +PLOT_TEST(generator_is_deterministic_and_current) { + const std::string command = std::string("python3 ") + kSourceDir + + "/tools/fonts/gen_atlas.py --check --out " + kSourceDir + + "/src/render/embedded_mono_font.cpp"; + const int status = std::system(command.c_str()); + PLOT_CHECK(status != -1); + PLOT_CHECK(WIFEXITED(status)); + const int code = WEXITSTATUS(status); + if (code == 3) { // `gen_atlas.py`'s "fontTools is not installed" code + std::printf( + "SKIPPED: generator_is_deterministic_and_current -- fontTools is not " + "installed, so the atlas could not be re-baked\n"); + return; + } + PLOT_CHECK(code == 0); } PLOT_TEST(text_batch_stages_one_quad_per_glyph) { - plot::render::BitmapFont f; + plot::render::EmbeddedMonoFont f; plot::render::TextBatch b; b.begin(); const plot::Vec2 end = b.add_text("12.5 ms", {0.0f, 0.0f}, 1.0f, f); @@ -254,7 +400,7 @@ PLOT_TEST(text_batch_stages_one_quad_per_glyph) { PLOT_CHECK_NEAR(end.y, 0.0, 1e-6); PLOT_CHECK_NEAR(b.data()[0], 0.0, 1e-6); // first cell at the pen PLOT_CHECK_NEAR(b.data()[1], 0.0, 1e-6); - PLOT_CHECK_NEAR(b.data()[6], f.advance('1'), 1e-6); // pen advanced per glyph + PLOT_CHECK_NEAR(b.data()[6], f.glyph('1')->advance, 1e-6); // pen advanced per glyph // The staged cell's UV is the glyph's own atlas rect (see `Glyph`), and the // basis of an unrotated run is the screen's +x. '.' is the string's third // glyph, so its instance is the third: floats 12..17. @@ -284,7 +430,7 @@ PLOT_TEST(text_batch_stages_one_quad_per_glyph) { PLOT_CHECK(b.glyph_count() == 2); PLOT_CHECK_NEAR(scaled.x, 10.0f + 2.0f * f.measure("AW"), 1e-6); PLOT_CHECK_NEAR(scaled.y, 4.0, 1e-6); - PLOT_CHECK_NEAR(b.data()[6], 10.0f + 2.0f * f.advance('A'), 1e-6); // second cell's pen + PLOT_CHECK_NEAR(b.data()[6], 10.0f + 2.0f * f.glyph('A')->advance, 1e-6); // second cell's pen } // The pen follows the basis, not the screen axes. A run staged with `axis` has to @@ -292,7 +438,7 @@ PLOT_TEST(text_batch_stages_one_quad_per_glyph) { // what reserves the room for a rotated y label -- and its cells have to march along // the same direction, or the ink lands beside the pen instead of up from it. PLOT_TEST(text_batch_advances_along_its_basis) { - plot::render::BitmapFont f; + plot::render::EmbeddedMonoFont f; plot::render::TextBatch b; const plot::Vec2 pen{100.0f, 40.0f}; const f32 width = f.measure("12.5 ms"); @@ -318,7 +464,7 @@ PLOT_TEST(text_batch_advances_along_its_basis) { // The second cell is one advance further up than the pen, which is the same rule // the unrotated case follows in x. PLOT_CHECK_NEAR(b.data()[6], pen.x, 1e-6); - PLOT_CHECK_NEAR(b.data()[7], pen.y - f.advance('1'), 1e-6); + PLOT_CHECK_NEAR(b.data()[7], pen.y - f.glyph('1')->advance, 1e-6); // A diagonal direction is normalised, so a caller can hand over any direction // and the basis the shader receives is still unit. @@ -340,6 +486,7 @@ class TrimmedCellBackend final : public plot::render::TextBackend { plot::f32 measure(const char*) const override { return 8.0f; } plot::f32 height() const override { return 8.0f; } plot::f32 ascent() const override { return 8.0f; } + plot::f32 em_px() const override { return 8.0f; } plot::Vec2 cell_uv() const override { return plot::Vec2{0.5f, 0.25f}; } plot::u32 revision() const override { return 1u; } // baked once, never re-baked const plot::render::Glyph* glyph(plot::u32 codepoint) const override { @@ -396,17 +543,19 @@ PLOT_TEST(a_trimmed_glyph_is_not_the_atlas_cell) { PLOT_CHECK(plot::render::glyph_is_cell(rounded, cell)); // A real font's grid holds for every glyph it publishes, not just the first. - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; for (plot::u32 codepoint = 32u; codepoint < 127u; ++codepoint) { const plot::render::Glyph* g = font.glyph(codepoint); PLOT_CHECK(g != nullptr); PLOT_CHECK(plot::render::glyph_is_cell(*g, font.cell_uv())); } - PLOT_CHECK_NEAR(font.cell_uv().x, static_cast(plot::render::BitmapFont::kGlyphPx) / - static_cast(plot::render::BitmapFont::kAtlasWidth), + PLOT_CHECK_NEAR(font.cell_uv().x, + static_cast(plot::render::EmbeddedMonoFont::cell_width()) / + static_cast(font.atlas_width()), 1e-9); - PLOT_CHECK_NEAR(font.cell_uv().y, static_cast(plot::render::BitmapFont::kGlyphPx) / - static_cast(plot::render::BitmapFont::kAtlasHeight), + PLOT_CHECK_NEAR(font.cell_uv().y, + static_cast(plot::render::EmbeddedMonoFont::cell_height()) / + static_cast(font.atlas_height()), 1e-9); } @@ -418,6 +567,7 @@ class HalfCellBackend final : public plot::render::TextBackend { plot::f32 measure(const char*) const override { return 4.0f; } plot::f32 height() const override { return 8.0f; } plot::f32 ascent() const override { return 6.0f; } + plot::f32 em_px() const override { return 8.0f; } plot::Vec2 cell_uv() const override { return plot::Vec2{0.5f, 0.25f}; } plot::u32 revision() const override { return 1u; } const plot::render::Glyph* glyph(plot::u32) const override { return &cell_; } @@ -471,7 +621,7 @@ PLOT_TEST(text_batch_takes_its_cell_from_the_backend) { // And the real font's grid, staged the same way, reports the font's cell -- the // two never mix silently, and the batch's value tracks the backend it was given. - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; b.begin(); b.add_text("A", {0.0f, 0.0f}, 1.0f, font); PLOT_CHECK_NEAR(b.glyph_cell_uv().x, font.cell_uv().x, 1e-9); @@ -503,6 +653,7 @@ class HoleInCoverageBackend final : public plot::render::TextBackend { } plot::f32 height() const override { return 8.0f; } plot::f32 ascent() const override { return 6.0f; } + plot::f32 em_px() const override { return 8.0f; } plot::Vec2 cell_uv() const override { return plot::Vec2{0.5f, 1.0f}; } plot::u32 revision() const override { return 1u; } const plot::render::Glyph* glyph(plot::u32 codepoint) const override { @@ -523,7 +674,7 @@ class HoleInCoverageBackend final : public plot::render::TextBackend { // it and does not advance the pen, so a run stays exactly as long as the same // backend measured it and the glyphs it *can* draw keep their places. A backend // that would rather show something publishes a fallback box as that codepoint's -// glyph (which is what `BitmapFont` does); this is the other legal answer, and the +// glyph (which is what `EmbeddedMonoFont` does); this is the other legal answer, and the // staging path has to honour it rather than draw whatever was staged last or // advance by a width nobody published. PLOT_TEST(add_text_skips_a_codepoint_the_backend_cannot_draw) { @@ -568,7 +719,7 @@ PLOT_TEST(add_text_skips_a_codepoint_the_backend_cannot_draw) { // A real font never hits this path (every codepoint maps to its box), so the // same string through it stages one glyph per codepoint: the two answers to // "no glyph" are the backend's choice, not the pipeline's. - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; batch.begin(); batch.add_text("A?B", pen, 1.0f, font); PLOT_CHECK(batch.glyph_count() == 3); diff --git a/tests/test_render_smoke.cpp b/tests/test_render_smoke.cpp index c6d67fe..6b881c0 100644 --- a/tests/test_render_smoke.cpp +++ b/tests/test_render_smoke.cpp @@ -119,6 +119,24 @@ bool pixel_is(const plot::u8 px[4], plot::Color c) { return near(px[0], c.r) && near(px[1], c.g) && near(px[2], c.b); } +/// True when `px` holds `c` at some coverage -- the anti-aliased case `pixel_is` +/// cannot see. The embedded atlas is greyscale and the renderer draws it at +/// `Style::axis_font_px` from a 13 px bake, so at the default size a glyph stroke +/// is barely a pixel wide and *no* pixel is ever fully covered: demanding the +/// exact colour finds no text at all. +/// +/// A partially covered pixel is `c` scaled toward the frame's dark chrome, so this +/// asks for every channel to be at least a third of `c`'s: bright enough to be +/// glyph ink, dark enough to exclude a background, and deliberately blind to which +/// of two chalk-toned chrome colours a pixel is -- which is why every caller scans +/// a region only one element draws in. +bool text_ink(const plot::u8 px[4], plot::Color c) { + const auto lifted = [](plot::u8 got, plot::f32 want) { + return static_cast(got) >= want * 255.0f * 0.35f; + }; + return lifted(px[0], c.r) && lifted(px[1], c.g) && lifted(px[2], c.b); +} + /// No column held ink. constexpr plot::u32 kNoColumn = 0xFFFFFFFFu; @@ -132,6 +150,7 @@ class OnePxPerStringBackend final : public plot::render::TextBackend { plot::f32 measure(const char*) const override { return 1.0f; } plot::f32 height() const override { return 1.0f; } plot::f32 ascent() const override { return 1.0f; } + plot::f32 em_px() const override { return 1.0f; } const plot::render::Glyph* glyph(plot::u32) const override { return nullptr; } plot::Vec2 cell_uv() const override { return plot::Vec2{}; } // no atlas, no cell const plot::u8* atlas_pixels() const override { return nullptr; } @@ -190,6 +209,8 @@ class BakeableBackend final : public plot::render::TextBackend { plot::f32 measure(const char*) const override { return 8.0f; } plot::f32 height() const override { return static_cast(size_); } plot::f32 ascent() const override { return static_cast(size_); } + /// Its cell is the whole atlas, so its em is the bake size: one unit is one pixel. + plot::f32 em_px() const override { return static_cast(size_); } const plot::render::Glyph* glyph(plot::u32) const override { return &glyph_; } plot::Vec2 cell_uv() const override { return pixels_.empty() ? plot::Vec2{} : plot::Vec2{1.0f, 1.0f}; } const plot::u8* atlas_pixels() const override { return pixels_.empty() ? nullptr : pixels_.data(); } @@ -217,14 +238,16 @@ struct InkBox { plot::u32 height() const noexcept { return any() ? y1 - y0 + 1u : 0u; } }; -/// Scans the whole framebuffer for fully covered `text`-coloured pixels. +/// Scans the whole framebuffer for `text`-coloured ink, anti-aliased fringe and +/// all (see `text_ink`). These cases draw nothing but the run, so everything lit +/// in the target's colour is the run. InkBox scan_ink_box(plottest::HeadlessContext& ctx, plot::Color text) { InkBox box; for (plot::u32 y = 0; y < ctx.height; ++y) { for (plot::u32 x = 0; x < ctx.width; ++x) { plot::u8 px[4] = {}; PLOT_CHECK(ctx.read_pixel(x, ctx.height - 1u - y, px)); - if (!pixel_is(px, text)) continue; + if (!text_ink(px, text)) continue; if (!box.any()) { box.x0 = x; box.y0 = y; @@ -290,8 +313,8 @@ struct TextInk { plot::u32 widest_row_run = 0; }; -/// Scans the top-down pixels of [x0, x1) x [y, y + rows) for fully covered text -/// pixels in `text`. +/// Scans the top-down pixels of [x0, x1) x [y, y + rows) for `text`-coloured ink, +/// anti-aliased fringe and all (see `text_ink`). TextInk scan_text_ink(plottest::HeadlessContext& ctx, plot::u32 x0, plot::u32 x1, plot::u32 y, plot::u32 rows, plot::Color text) { TextInk ink; @@ -301,7 +324,7 @@ TextInk scan_text_ink(plottest::HeadlessContext& ctx, plot::u32 x0, plot::u32 x1 for (plot::u32 x = x0; x < x1; ++x) { plot::u8 px[4] = {}; PLOT_CHECK(ctx.read_pixel(x, ctx.height - 1u - (y + r), px)); - if (!pixel_is(px, text)) continue; + if (!text_ink(px, text)) continue; if (first == kNoColumn) first = x; last = x; if (ink.leftmost == kNoColumn) ink.leftmost = x; @@ -661,8 +684,12 @@ PLOT_TEST(renderer_measures_the_labels_it_draws) { const plot::u32 y = static_cast(labels.y); const plot::u32 rows = static_cast(labels.h); const plot::u32 strip_x = static_cast(labels.x); + // Exactly the strip, and no overhang: the axes' own border line runs down its + // right edge through these same rows, and a scanner that reached past the strip + // would read that line as label ink -- it is the same chalk-toned colour family + // as the text (see `text_ink`). const TextInk ink = - scan_text_ink(ctx, strip_x, strip_x + static_cast(labels.w) + 16u, y, rows, text); + scan_text_ink(ctx, strip_x, strip_x + static_cast(labels.w), y, rows, text); PLOT_CHECK(ink.leftmost != kNoColumn); // labels were drawn PLOT_CHECK(ink.leftmost >= strip_x); // inside their strip PLOT_CHECK(static_cast(ink.widest_row_run) >= 0.6f * widest); // full width drawn @@ -1017,7 +1044,7 @@ PLOT_TEST(titled_axis_pairs_get_their_own_rows) { plot::u8 px[4] = {}; PLOT_CHECK(ctx.read_pixel(static_cast(strip.x) + column, ctx.height - 1u - (static_cast(strip.y) + row), px)); - has = pixel_is(px, title); + has = text_ink(px, title); } if (has && !previous) ++bands; if (has) ++rows; @@ -1261,11 +1288,13 @@ PLOT_TEST(rotated_text_advances_along_its_basis) { PLOT_CHECK(ctx.create(&st)); PLOT_CHECK(plot::gl::load(nullptr)); - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::render::TextAtlas atlas; const plot::Vec2 pen{300.0f, 180.0f}; - const plot::f32 px = 2.0f; // 16 px cells: six glyphs is over a hundred pixels - const plot::u32 cell = static_cast(8.0f * px); + const plot::f32 px = 2.0f; // one cell is 16 x 34 screen pixels at this scale + const plot::u32 cell = plot::render::EmbeddedMonoFont::cell_height() * 2u; + const plot::u32 advance_px = + static_cast(font.glyph(static_cast('M'))->advance * px); const InkBox flat = draw_text_run(ctx, atlas, font, "MWMWMW", pen, px, {1.0f, 0.0f}); PLOT_CHECK(flat.any()); @@ -1273,6 +1302,7 @@ PLOT_TEST(rotated_text_advances_along_its_basis) { PLOT_CHECK(flat.x0 >= static_cast(pen.x)); PLOT_CHECK(flat.y0 >= static_cast(pen.y)); PLOT_CHECK(flat.height() <= cell); + PLOT_CHECK(flat.width() > 4u * advance_px); // six monospaced glyphs, and then some const InkBox up = draw_text_run(ctx, atlas, font, "MWMWMW", pen, px, {0.0f, -1.0f}); PLOT_CHECK(up.any()); @@ -1281,42 +1311,45 @@ PLOT_TEST(rotated_text_advances_along_its_basis) { PLOT_CHECK(up.width() <= cell); // one cell wide, so nothing marched right PLOT_CHECK(up.x0 >= static_cast(pen.x)); PLOT_CHECK(up.x1 < static_cast(pen.x) + cell); - // Ink above the pen: the first cell starts at the pen and every later one is a - // cell further up, so the run's rows run from well above the pen down to the + // Ink above the pen: the first cell starts at the pen and every later one is an + // advance further up, so the run's rows run from well above the pen down to the // pen's own cell -- and the pen's own cell is the lowest row of ink. - PLOT_CHECK(up.y0 + 4u * cell < static_cast(pen.y)); + PLOT_CHECK(up.y0 + 4u * advance_px < static_cast(pen.y)); PLOT_CHECK(up.y1 < static_cast(pen.y) + cell); ctx.destroy(); } // The orientation rule, stated rather than hardcoded: a glyph's texel `(col, row)` -// lands at the pen plus `col` steps along the cell's +v axis and `row` steps along -// its +x axis. That is the mapping kTextVS implements, so with `axis = (0, -1)` the -// 'L' -- whose ink is a stem down its left column and a foot along its bottom row -- -// has to come out as a top bar with the foot running up the right-hand side. +// lands at the pen plus `col` steps along the cell's +x axis (`ux` here) and `row` +// steps along its +v axis (the perpendicular). That is the mapping kTextVS +// implements, so with `axis = (0, -1)` the 'L' -- whose ink is a stem down its left +// columns and a foot along its bottom rows -- has to come out as a bar standing up +// from the pen with the foot running right. // -// The check is two-sided: the basis-derived corner holds ink and the corner a -// mirrored perpendicular would put it in does not, so a glyph flipped about its own -// diagonal fails even though it is the same size and shape. +// The check is two-sided: the texels the basis says hold ink do, and the texels a +// mirrored perpendicular would put that ink in hold none -- so a glyph flipped +// about its own diagonal fails even though it is the same size and shape. The +// coverage is anti-aliased, so "held ink" is a coverage threshold rather than an +// exact white: a texel of the stem is fully covered and a texel of the foot is +// half, and both are ink. PLOT_TEST(rotated_glyph_orientation_is_not_mirrored) { plottest::HeadlessContext ctx(kW, kH); plot::Status st; PLOT_CHECK(ctx.create(&st)); PLOT_CHECK(plot::gl::load(nullptr)); - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::render::TextAtlas atlas; const plot::Vec2 pen{300.0f, 200.0f}; - const plot::f32 px = 4.0f; // an 8x8 glyph at 32 px: one texel is a 4x4 pixel block + const plot::f32 px = 4.0f; // one texel is a 4x4 pixel block const plot::Vec2 ux{0.0f, -1.0f}; // a 90 degree counter-clockwise label const plot::Vec2 uy{-ux.y, ux.x}; // (1, 0): the cell's own +v axis - // Where the centre of the glyph's texel (col, row) is drawn. Every cell is - // `kGlyphPx` texels across, the cell's top-left is the pen because the bitmap - // font's bearings are zero, and the rule is the shader's own: the atlas' u runs - // along the cell's +x axis (`ux` here, `a_axis` there) and its v along the - // perpendicular (`v_axis`), which is what `v_uv = a_uv + corner * u_glyph_uv` - // maps corner by corner. + // Where the centre of the glyph's texel (col, row) is drawn. The cell's top-left + // is the pen (the embedded font bakes its bearings into the cell), and the rule + // is the shader's own: the atlas' u runs along the cell's +x axis (`ux` here, + // `a_axis` there) and its v along the perpendicular (`v_axis`), which is what + // `v_uv = a_uv + corner * u_glyph_uv` maps corner by corner. auto texel_centre = [&](plot::f32 col, plot::f32 row, plot::Vec2 v_axis) { const plot::f32 du = (col + 0.5f) * px; const plot::f32 dv = (row + 0.5f) * px; @@ -1326,28 +1359,28 @@ PLOT_TEST(rotated_glyph_orientation_is_not_mirrored) { plot::u8 pxv[4] = {}; PLOT_CHECK(ctx.read_pixel(static_cast(at.x), ctx.height - 1u - static_cast(at.y), pxv)); - return pixel_is(pxv, plot::Color::hex(0xFFFFFF)); + return pxv[0] >= 96u; // white tint over black: the pixel *is* the coverage }; PLOT_CHECK(draw_text_run(ctx, atlas, font, "L", pen, px, ux).any()); - // 'L' is inked at texel (0, 0) -- the stem's top -- and at (6, 6), the far end of - // its foot. Both corners are where the basis puts them ... - PLOT_CHECK(inked(texel_centre(0.0f, 0.0f, uy))); - PLOT_CHECK(inked(texel_centre(6.0f, 6.0f, uy))); - // ... and the two *off-diagonal* texels of its ink state the rule the diagonal - // pair cannot: the stem meets the foot at (0, 6), which is where the cell's - // first texel row and last texel column meet, while (6, 0) is blank. A shader - // that swapped the cell's two axes would trade those two, and both of these - // would fail -- the diagonal probes would not. - PLOT_CHECK(inked(texel_centre(0.0f, 6.0f, uy))); - PLOT_CHECK(!inked(texel_centre(6.0f, 0.0f, uy))); - // ... and the mirrored basis -- the other perpendicular a shader could build -- - // would put the stem's top on the opposite side of the pen, where this frame has - // no ink at all. + // The stem (cell columns 1-2, rows 3-12) and the foot (columns 2-6, rows 11-12) + // put ink at these texels ... + PLOT_CHECK(inked(texel_centre(2.0f, 4.0f, uy))); // stem, fully covered + PLOT_CHECK(inked(texel_centre(2.0f, 11.0f, uy))); // stem meets the foot + PLOT_CHECK(inked(texel_centre(5.0f, 11.0f, uy))); // the foot's far end + // ... and none at these, which are the cell's blank margin: a shader that + // swapped the cell's two axes would put the ink in the wrong half and fail both + // sides. + PLOT_CHECK(!inked(texel_centre(2.0f, 0.0f, uy))); // above the first row of ink + PLOT_CHECK(!inked(texel_centre(6.0f, 4.0f, uy))); // right of the stem at cap height + PLOT_CHECK(!inked(texel_centre(0.0f, 4.0f, uy))); // the cell's empty first column + // The mirrored perpendicular -- the other basis a shader could build -- would + // draw the whole cell on the opposite side of the pen, where this frame has no + // ink at all. const plot::Vec2 mirrored{-uy.x, -uy.y}; - PLOT_CHECK(!inked(texel_centre(0.0f, 0.0f, mirrored))); - PLOT_CHECK(!inked(texel_centre(6.0f, 6.0f, mirrored))); + PLOT_CHECK(!inked(texel_centre(2.0f, 4.0f, mirrored))); + PLOT_CHECK(!inked(texel_centre(5.0f, 11.0f, mirrored))); ctx.destroy(); } @@ -1505,7 +1538,7 @@ PLOT_TEST(text_atlas_reports_a_status_when_the_entry_points_are_missing) { PLOT_CHECK(plot::gl::load(nullptr)); PLOT_CHECK(plot::gl::available()); - plot::render::BitmapFont font; + plot::render::EmbeddedMonoFont font; plot::render::TextAtlas atlas; { RefuseGetIntegerv refused; @@ -1522,8 +1555,8 @@ PLOT_TEST(text_atlas_reports_a_status_when_the_entry_points_are_missing) { // the Status it got. PLOT_CHECK(atlas.sync(font, &st)); PLOT_CHECK(!atlas.empty()); - PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast(plot::render::BitmapFont::kAtlasWidth), - 1e-6); + PLOT_CHECK_NEAR(atlas.texel_size().x, static_cast(font.atlas_width()), 1e-6); + PLOT_CHECK_NEAR(atlas.texel_size().y, static_cast(font.atlas_height()), 1e-6); PLOT_CHECK(plot::gl::glGetError() == plot::gl::GL_NO_ERROR); ctx.destroy(); } diff --git a/tools/fonts/gen_atlas.py b/tools/fonts/gen_atlas.py new file mode 100755 index 0000000..90d9193 --- /dev/null +++ b/tools/fonts/gen_atlas.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python3 +"""Bake the embedded mono font's anti-aliased atlas, at authoring time. + +Reads a TrueType/OpenType file and writes a C++ translation unit holding a +greyscale coverage atlas, one fixed cell per glyph, plus the per-glyph rect, +bearing and advance table the text pipeline stages from. + + tools/fonts/gen_atlas.py [--font PATH] [--em N] [--supersample N] [--out PATH] [--check] + +This is an **authoring-time** tool. `plot_core` and every public header under +`include/plot/` stay third-party-free: nothing here runs at build time, the +atlas is committed as data, and `fontTools` is never needed to compile or run the +library. Regenerating is a deliberate act followed by a commit. + +Rasterisation is ours, not a rasteriser's: the glyph outlines are flattened with +`fontTools.pens`, scanline-filled at `supersample` times the em, and box-filtered +down to one coverage byte per texel. Nothing shells out. + +Determinism +----------- +Same font file plus the same em size and supersample factor must produce a +byte-identical translation unit -- `--check` depends on it, and so does a +reviewable diff. Everything emitted is derived from integers (cell geometry, the +font's own advance units) or from a fixed, sorted walk of the coverage list; no +timestamp, no set/dict iteration order, no float formatting, no host path beyond +the source font's file name. + +Blank ink is a hard failure +--------------------------- +Every requested codepoint must rasterise to at least one inked texel. A silent +blank cell is exactly the failure that makes a font swap look like it worked, so +the generator exits non-zero instead. The single documented exception is U+0020, +which is blank *by definition* -- an intentional blank with a full advance. + +Provenance +---------- +The default face is Adwaita Mono, `AdwaitaMono-Regular.ttf`, released under the +SIL Open Font License 1.1 (a copy is committed at `licences/OFL-1.1.txt`). The +backend is named neutrally (`EmbeddedMonoFont`) because OFL reserves the font +name for modified redistributions. The generated file records the source file +name, the font's own name/version strings, the em size, the supersample factor +and the exact coverage set. +""" + +from __future__ import annotations + +import argparse +import math +import os +import sys +from pathlib import Path + +DEFAULT_FONT = "/usr/share/fonts/Adwaita/AdwaitaMono-Regular.ttf" +DEFAULT_EM = 13 +DEFAULT_SUPERSAMPLE = 4 +DEFAULT_OUT = "src/render/embedded_mono_font.cpp" + +SCRIPT = "tools/fonts/gen_atlas.py" + +# The fallback box, in font units with the origin at the pen on the baseline, y up +# like the outlines it stands in for. Its two contours wind oppositely, so the +# non-zero fill leaves a hollow box: an outer rectangle and an inner hole, each +# stroke 100 units (1.3 px at 13 px em). It is authored here, not taken from the +# font -- it is what an uncovered codepoint draws. +FALLBACK_OUTER = ((60.0, -100.0), (580.0, -100.0), (580.0, 700.0), (60.0, 700.0)) +FALLBACK_INNER = ((160.0, 0.0), (160.0, 600.0), (480.0, 600.0), (480.0, 0.0)) + +# The codepoint side of the coverage set: printable ASCII, the three codepoints +# `plot::format_si` emits, and the two dashes a plotting label uses. The fallback +# slot is appended after them. +ASCII = list(range(0x20, 0x7F)) +SPECIALS = [0xB0, 0xB5, 0x03A9, 0x2013, 0x2014] + +FALLBACK_CODEPOINT = 0xFFFFFFFF # not a Unicode scalar; cannot collide + +# Sub-curve flattening steps. Fixed, not tolerance-adaptive: at a 13 px em they +# are far below one supersampled texel even at 4x, and a constant keeps the +# output independent of anything but the font file. +QUAD_STEPS = 12 +CUBIC_STEPS = 16 + +EXIT_OK = 0 +EXIT_MISMATCH = 1 +EXIT_BAD_FONT = 2 +EXIT_NO_FONTTOOLS = 3 + + +def _fail(code: int, message: str) -> int: + print(f"{SCRIPT}: {message}", file=sys.stderr) + return code + + +def _require_fonttools(): + try: + from fontTools.pens.basePen import BasePen # noqa: F401 + from fontTools.ttLib import TTFont # noqa: F401 + except ImportError: + raise SystemExit( + _fail( + EXIT_NO_FONTTOOLS, + "fontTools is not installed; this is an authoring-time tool. " + "Install it (pip install fonttools) to regenerate the atlas.", + ) + ) + + +def _contour_pen_class(): + from fontTools.pens.basePen import BasePen + + class ContourPen(BasePen): + """Flattens one glyph to contour point lists in cell space. + + `xf` maps a font-unit point (y up, origin at the pen on the baseline) to + supersampled cell coordinates (y down, origin at the cell's top-left), + so every geometric decision below is made once, here. + """ + + def __init__(self, glyph_set, xf): + super().__init__(glyph_set) + self._xf = xf + self.contours = [] + self._current = [] + + def _map(self, point): + return self._xf(point[0], point[1]) + + def _moveTo(self, pt): + self._current = [self._map(pt)] + + def _lineTo(self, pt): + self._current.append(self._map(pt)) + + def _qCurveToOne(self, control, point): + p0 = self._current[-1] + p1 = self._map(control) + p2 = self._map(point) + for step in range(1, QUAD_STEPS + 1): + t = step / QUAD_STEPS + u = 1.0 - t + self._current.append( + ( + u * u * p0[0] + 2.0 * u * t * p1[0] + t * t * p2[0], + u * u * p0[1] + 2.0 * u * t * p1[1] + t * t * p2[1], + ) + ) + + def _curveToOne(self, c1, c2, point): + p0 = self._current[-1] + p1 = self._map(c1) + p2 = self._map(c2) + p3 = self._map(point) + for step in range(1, CUBIC_STEPS + 1): + t = step / CUBIC_STEPS + u = 1.0 - t + b0 = u * u * u + b1 = 3.0 * u * u * t + b2 = 3.0 * u * t * t + b3 = t * t * t + self._current.append( + ( + b0 * p0[0] + b1 * p1[0] + b2 * p2[0] + b3 * p3[0], + b0 * p0[1] + b1 * p1[1] + b2 * p2[1] + b3 * p3[1], + ) + ) + + def _closePath(self): + if len(self._current) >= 3: + self.contours.append(self._current) + self._current = [] + + def _endPath(self): + self._closePath() + + return ContourPen + + +def _scanline_mask(contours, width, height): + """Non-zero-winding scanline fill of `contours` into a `height` x `width` 0/1 grid. + + The fill rule is non-zero rather than even-odd because TrueType orients a + hole opposite its outer contour and a font is entitled to overlapping + contours; even-odd would punch a hole out of any overlap. + """ + mask = [[0] * width for _ in range(height)] + edges = [] + for contour in contours: + count = len(contour) + for i in range(count): + x0, y0 = contour[i] + x1, y1 = contour[(i + 1) % count] + if y0 != y1: + edges.append((x0, y0, x1, y1)) + for row in range(height): + yc = row + 0.5 + crossings = [] + for x0, y0, x1, y1 in edges: + if (y0 <= yc) != (y1 <= yc): + t = (yc - y0) / (y1 - y0) + crossings.append((x0 + t * (x1 - x0), 1 if y1 > y0 else -1)) + if not crossings: + continue + crossings.sort() + winding = 0 + span_start = 0.0 + for x, direction in crossings: + previous = winding + winding += direction + if previous == 0 and winding != 0: + span_start = x + elif previous != 0 and winding == 0: + first = max(0, int(math.ceil(span_start - 0.5))) + last = min(width, int(math.floor(x - 0.5)) + 1) + target = mask[row] + for column in range(first, last): + target[column] = 1 + return mask + + +def _coverage_cell(contours, cell_w, cell_h, supersample): + """Box-filter `contours` into `cell_h` rows of `cell_w` coverage bytes. + + `contours` are in supersampled cell space, so each output texel averages the + `supersample` x `supersample` sub-samples that land on it -- a plain coverage + count, which is what makes the result greyscale. + """ + area = supersample * supersample + mask = _scanline_mask(contours, cell_w * supersample, cell_h * supersample) + rows = [] + for ty in range(cell_h): + row = [] + for tx in range(cell_w): + covered = 0 + for sy in range(supersample): + sub = mask[ty * supersample + sy] + base = tx * supersample + for sx in range(supersample): + covered += sub[base + sx] + # Round-half-up in integers: deterministic, and independent of the + # host's floating-point formatting. + row.append((covered * 255 + area // 2) // area) + rows.append(row) + return rows + + +def _cell_transform(em, units_per_em, ascent_px, supersample): + scale = em / units_per_em + + def xf(x_units, y_units): + return (x_units * scale * supersample, (ascent_px - y_units * scale) * supersample) + + return xf + + +def _read_font(path): + from fontTools.ttLib import TTFont + + if not os.path.isfile(path): + raise SystemExit(_fail(EXIT_BAD_FONT, f"no such font file: {path}")) + try: + return TTFont(path, recalcBBoxes=False, recalcTimestamp=False) + except Exception as exc: # fontTools raises a spread of parser errors + raise SystemExit(_fail(EXIT_BAD_FONT, f"cannot parse {path}: {exc}")) + + +def build(font_path, em, supersample): + """Rasterise the coverage set at `em` px; returns the geometry and cells.""" + font = _read_font(font_path) + head = font["head"] + os2 = font["OS/2"] + units_per_em = head.unitsPerEm + glyph_set = font.getGlyphSet() + cmap = font.getBestCmap() + hmtx = font["hmtx"] + + name_record = font["name"] + + def name(identifier, fallback): + value = name_record.getDebugName(identifier) + return value if value else fallback + + scale = em / units_per_em + ascent_units = os2.sTypoAscender + descent_units = -os2.sTypoDescender + ascent_px = ascent_units * scale + + contours_class = _contour_pen_class() + from fontTools.pens.boundsPen import BoundsPen + + codepoints = list(ASCII) + list(SPECIALS) + + # The cell: wide enough for the widest advance (the pen must never leave the + # cell) and for the widest ink; tall enough for the face's ascent-to-descent + # extent. Never trimmed to any one glyph's ink -- a fixed-cell grid is what + # lets the pipeline carry one rect size per draw. + widest_advance = 0 + widest_ink = 0.0 + for codepoint in codepoints: + glyph_name = cmap.get(codepoint) + if glyph_name is None: + raise SystemExit( + _fail(EXIT_BAD_FONT, f"{font_path} has no glyph for U+{codepoint:04X}") + ) + advance, _bearing = hmtx[glyph_name] + widest_advance = max(widest_advance, advance) + bounds = BoundsPen(glyph_set) + glyph_set[glyph_name].draw(bounds) + if bounds.bounds is not None: + widest_ink = max(widest_ink, bounds.bounds[2]) + + cell_w = int(math.ceil(max(widest_advance * scale, widest_ink * scale) - 1e-9)) + cell_h = int(math.ceil(ascent_px + descent_units * scale - 1e-9)) + if cell_w <= 0 or cell_h <= 0: + raise SystemExit(_fail(EXIT_BAD_FONT, "the computed cell is empty")) + + xf = _cell_transform(em, units_per_em, ascent_px, supersample) + + cells = [] + advances = [] + for codepoint in codepoints: + glyph_name = cmap[codepoint] + pen = contours_class(glyph_set, xf) + glyph_set[glyph_name].draw(pen) + rows = _coverage_cell(pen.contours, cell_w, cell_h, supersample) + ink = sum(sum(row) for row in rows) + if codepoint != 0x20 and ink == 0: + # A silent blank cell is the failure mode this whole exercise exists + # to remove: refuse to ship it. + raise SystemExit( + _fail( + EXIT_BAD_FONT, + f"U+{codepoint:04X} ({glyph_name}) rasterised to no ink at " + f"{em} px; the cell is too small or the glyph is missing", + ) + ) + cells.append(rows) + advances.append(hmtx[glyph_name][0]) + + fallback_pen = contours_class(glyph_set, xf) + fallback_pen.contours = [ + [fallback_pen._xf(x, y) for x, y in FALLBACK_OUTER], + [fallback_pen._xf(x, y) for x, y in FALLBACK_INNER], + ] + fallback_rows = _coverage_cell(fallback_pen.contours, cell_w, cell_h, supersample) + if sum(sum(row) for row in fallback_rows) == 0: + raise SystemExit(_fail(EXIT_BAD_FONT, "the fallback box rasterised to no ink")) + cells.append(fallback_rows) + advances.append(widest_advance) + + geometry = { + "em": em, + "supersample": supersample, + "units_per_em": units_per_em, + "scale": scale, + "ascent_units": ascent_units, + "descent_units": descent_units, + "ascent_px": ascent_px, + "cell_w": cell_w, + "cell_h": cell_h, + "glyph_count": len(codepoints) + 1, + "fallback_slot": len(codepoints), + "codepoints": codepoints, + "advances": advances, + "cells": cells, + } + provenance = { + "font_file": os.path.basename(font_path), + "font_name": name(4, "unknown"), + "font_version": name(5, "unknown"), + } + font.close() + return geometry, provenance + + +def _codepoint_label(codepoint): + if codepoint == FALLBACK_CODEPOINT: + return "fallback box" + return f"U+{codepoint:04X}" + + +def emit(geometry, provenance): + """Render one `build()` result as the generated translation unit.""" + em = geometry["em"] + supersample = geometry["supersample"] + units_per_em = geometry["units_per_em"] + cell_w = geometry["cell_w"] + cell_h = geometry["cell_h"] + glyph_count = geometry["glyph_count"] + fallback_slot = geometry["fallback_slot"] + atlas_w = cell_w * glyph_count + atlas_h = cell_h + codepoints = geometry["codepoints"] + advances = geometry["advances"] + slot_codepoints = list(codepoints) + [FALLBACK_CODEPOINT] + + ascii_lo = ASCII[0] + ascii_hi = ASCII[-1] + + lines = [] + add = lines.append + + add("// GENERATED FILE -- do not edit by hand.") + add("//") + add(f"// Regenerate with: {SCRIPT}") + add(f"// The generator takes the source font, the em size and the supersample factor;") + add(f"// its defaults bake the face this file holds. `{SCRIPT} --check`") + add("// regenerates into memory and fails if this file has drifted from its input.") + add("//") + add("// Provenance") + add("// source font : " + provenance["font_file"]) + add("// font name : " + provenance["font_name"]) + add("// font version : " + provenance["font_version"]) + add("// licence : SIL Open Font License 1.1 (licences/OFL-1.1.txt)") + add(f"// units per em : {units_per_em}") + add(f"// em size : {em} px") + add(f"// supersample : {supersample}x ({supersample * supersample} samples per texel)") + add(f"// coverage : U+{ascii_lo:04X}..U+{ascii_hi:04X}, " + + ", ".join(f"U+{cp:04X}" for cp in SPECIALS) + + ", plus a fallback box") + add(f"// cell : {cell_w} x {cell_h} texels, one row of {glyph_count}") + add(f"// atlas : {atlas_w} x {atlas_h} texels") + add("//") + add("// The cell is the face's maximum advance wide and its ascent-to-descent extent") + add("// tall; every glyph is drawn into that cell with its bearing preserved, never") + add("// trimmed to its ink, because the pipeline stages one rect *origin* per glyph and") + add("// carries one rect *size* for the whole draw (see render::TextBackend::cell_uv).") + add("// Coverage is a plain supersampled coverage count, so a stroke's edges are grey") + add("// and not a 1-bit threshold.") + add("") + add("#include ") + add("") + add("#include ") + add("#include ") + add("") + add("namespace plot::render {") + add("namespace {") + add("") + add("// Geometry, all of it derived from the bake above by the generator.") + add(f"constexpr u32 kEmPx = {em}u;") + add(f"constexpr u32 kSupersample = {supersample}u;") + add(f"constexpr u32 kUnitsPerEm = {units_per_em}u;") + add(f"constexpr u32 kCellW = {cell_w}u;") + add(f"constexpr u32 kCellH = {cell_h}u;") + add(f"constexpr u32 kGlyphCount = {glyph_count}u;") + add(f"constexpr u32 kFallbackSlot = {fallback_slot}u;") + add("constexpr u32 kAtlasW = kCellW * kGlyphCount;") + add("constexpr u32 kAtlasH = kCellH;") + add("constexpr u32 kAsciiFirst = " + f"{ascii_lo}u;") + add("constexpr u32 kAsciiGlyphs = " + f"{len(ASCII)}u;") + add("constexpr u32 kFallbackCodepoint = " + f"0x{FALLBACK_CODEPOINT:08X}u;") + add("") + add(f"constexpr u32 kAscentUnits = {geometry['ascent_units']}u;") + add("// Font pixels per font unit. The one non-integer factor, and the only place a") + add("// float enters: everything else here is an integer.") + add("constexpr f32 kScale = static_cast(kEmPx) / static_cast(kUnitsPerEm);") + add("constexpr f32 kAscentPx = static_cast(kAscentUnits) * kScale;") + add("") + add("// The public header repeats the em size, the supersample factor and the") + add("// fallback marker so a caller can read them without opening this table.") + add("// These asserts are what make that a pin and not a copy: re-baking at another") + add("// em or supersample factor, or moving the fallback marker, fails to compile") + add("// until the header agrees, so the two can never silently disagree.") + add("static_assert(kEmPx == EmbeddedMonoFont::kEmPx,") + add(" \"embedded_mono_font.hpp kEmPx disagrees with the bake\");") + add("static_assert(kSupersample == EmbeddedMonoFont::kSupersample,") + add(" \"embedded_mono_font.hpp kSupersample disagrees with the bake\");") + add("static_assert(kFallbackCodepoint == EmbeddedMonoFont::kFallbackCodepoint,") + add(" \"embedded_mono_font.hpp kFallbackCodepoint disagrees with the bake\");") + add("") + add("/// The codepoint each slot was baked for, in slot order; the fallback slot") + add("/// carries `kFallbackCodepoint`, which is not a Unicode scalar.") + add(f"constexpr u32 kCodepoints[kGlyphCount] = {{") + for slot, codepoint in enumerate(codepoints): + add(f" 0x{codepoint:04X}u, // [{slot:3d}] {_codepoint_label(codepoint)}") + add(f" 0x{FALLBACK_CODEPOINT:08X}u, // [{fallback_slot:3d}] {_codepoint_label(FALLBACK_CODEPOINT)}") + add("};") + add("") + add("/// The advance each slot contributes to the pen, in font units. The face is") + add("/// monospaced, so these are all equal -- but the table is per glyph, because the") + add("/// advance is the font's own `hmtx` value and not an assumption about the face.") + add(f"constexpr u32 kAdvanceUnits[kGlyphCount] = {{") + for slot, advance in enumerate(advances): + codepoint = codepoints[slot] if slot < len(codepoints) else FALLBACK_CODEPOINT + add(f" {advance}u, // [{slot:3d}] {_codepoint_label(codepoint)}") + add("};") + add("") + add("/// The packed atlas: one row of cells, top row first, straight coverage, 0 for") + add("/// background and 255 for full ink. GL_R8-ready, no dilation, no gamma.") + add(f"constexpr u8 kAtlas[kAtlasW * kAtlasH] = {{") + for row in range(atlas_h): + for slot in range(glyph_count): + values = geometry["cells"][slot][row] + body = ", ".join(f"0x{value:02X}" for value in values) + label = _codepoint_label(slot_codepoints[slot]) + add(f" {body}, // [{slot:3d}] {label} row {row:2d}") + add("};") + add("") + add("/// One slot's glyph: the cell it occupies in the atlas, drawn from the cell's") + add("/// top-left (the pen) with no extra bearing -- the bearing is baked into the") + add("/// cell's ink -- and the advance the pen moves by.") + add("constexpr Glyph make_glyph(u32 slot) noexcept {") + add(" const f32 u0 = static_cast(slot * kCellW) / static_cast(kAtlasW);") + add(" const f32 du = static_cast(kCellW) / static_cast(kAtlasW);") + add(" const f32 dv = static_cast(kCellH) / static_cast(kAtlasH);") + add(" return Glyph{u0, 0.0f, u0 + du, dv, 0.0f, 0.0f,") + add(" static_cast(kAdvanceUnits[slot]) * kScale};") + add("}") + add("") + add("template ") + add("constexpr std::array make_glyphs(") + add(" std::integer_sequence) noexcept {") + add(" return {make_glyph(Slots)...};") + add("}") + add("") + add("constexpr std::array kGlyphs =") + add(" make_glyphs(std::make_integer_sequence{});") + add("") + add("} // namespace") + add("") + add("u32 EmbeddedMonoFont::glyph_index(u32 codepoint) noexcept {") + add(" if (codepoint >= kAsciiFirst && codepoint < kAsciiFirst + kAsciiGlyphs) {") + add(" return codepoint - kAsciiFirst;") + add(" }") + add(" switch (codepoint) {") + for offset, codepoint in enumerate(SPECIALS): + add(f" case 0x{codepoint:04X}u:") + add(f" return kAsciiGlyphs + {offset}u;") + add(" default:") + add(" return kFallbackSlot;") + add(" }") + add("}") + add("") + add("f32 EmbeddedMonoFont::advance_codepoint(u32 codepoint) noexcept {") + add(" return static_cast(kAdvanceUnits[glyph_index(codepoint)]) * kScale;") + add("}") + add("") + add("f32 EmbeddedMonoFont::measure(const char* utf8) const {") + add(" if (utf8 == nullptr) {") + add(" return 0.0f;") + add(" }") + add(" f32 width = 0.0f;") + add(" for (const char* p = utf8; *p != '\\0';) {") + add(" width += advance_codepoint(next_codepoint(p));") + add(" }") + add(" return width;") + add("}") + add("") + add("f32 EmbeddedMonoFont::height() const { return static_cast(kCellH); }") + add("") + add("f32 EmbeddedMonoFont::ascent() const { return kAscentPx; }") + add("") + add("f32 EmbeddedMonoFont::em_px() const { return static_cast(kEmPx); }") + add("") + add("const Glyph* EmbeddedMonoFont::glyph(u32 codepoint) const {") + add(" return &kGlyphs[glyph_index(codepoint)];") + add("}") + add("") + add("Vec2 EmbeddedMonoFont::cell_uv() const {") + add(" return Vec2{static_cast(kCellW) / static_cast(kAtlasW),") + add(" static_cast(kCellH) / static_cast(kAtlasH)};") + add("}") + add("") + add("const u8* EmbeddedMonoFont::atlas_pixels() const { return kAtlas; }") + add("") + add("u32 EmbeddedMonoFont::atlas_width() const { return kAtlasW; }") + add("") + add("u32 EmbeddedMonoFont::atlas_height() const { return kAtlasH; }") + add("") + add("u32 EmbeddedMonoFont::revision() const { return 0u; }") + add("") + add("u32 EmbeddedMonoFont::glyph_count() noexcept { return kGlyphCount; }") + add("") + add("u32 EmbeddedMonoFont::codepoint_of_slot(u32 slot) noexcept {") + add(" return slot < kGlyphCount ? kCodepoints[slot] : kFallbackCodepoint;") + add("}") + add("") + add("u32 EmbeddedMonoFont::cell_width() noexcept { return kCellW; }") + add("") + add("u32 EmbeddedMonoFont::cell_height() noexcept { return kCellH; }") + add("") + add("Vec2 EmbeddedMonoFont::atlas_origin(u32 slot) noexcept {") + add(" const u32 s = slot < kGlyphCount ? slot : kFallbackSlot;") + add(" return Vec2{static_cast(s * kCellW), 0.0f};") + add("}") + add("") + add("const u8* EmbeddedMonoFont::atlas_image() noexcept { return kAtlas; }") + add("") + add("} // namespace plot::render") + add("") + return "\n".join(lines) + + +def generate(font_path, em, supersample): + geometry, provenance = build(font_path, em, supersample) + return emit(geometry, provenance), geometry + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--font", default=DEFAULT_FONT, help="source TrueType/OpenType file") + parser.add_argument("--em", type=int, default=DEFAULT_EM, help="em size in pixels") + parser.add_argument( + "--supersample", type=int, default=DEFAULT_SUPERSAMPLE, help="coverage samples per texel" + ) + parser.add_argument("--out", default=DEFAULT_OUT, help="translation unit to write") + parser.add_argument( + "--check", + action="store_true", + help="regenerate and fail if the committed file differs, instead of writing", + ) + args = parser.parse_args(argv) + + if args.em <= 0 or args.supersample <= 0: + return _fail(EXIT_BAD_FONT, "em size and supersample factor must be positive") + + _require_fonttools() + + if args.check: + out = Path(args.out) + committed = out.read_bytes() if out.is_file() else None + fresh, _ = generate(args.font, args.em, args.supersample) + if committed is None: + return _fail(EXIT_MISMATCH, f"{out} does not exist; run {SCRIPT} to generate it") + if committed != fresh.encode(): + return _fail( + EXIT_MISMATCH, + f"{out} differs from a fresh bake; regenerate with {SCRIPT} and commit the result", + ) + print(f"{SCRIPT}: {out} matches a fresh bake", file=sys.stderr) + return EXIT_OK + + text, geometry = generate(args.font, args.em, args.supersample) + Path(args.out).write_bytes(text.encode()) + ink = sum(sum(row) for cell in geometry["cells"] for row in cell) + print( + f"{SCRIPT}: {args.out}: {geometry['glyph_count']} glyphs, " + f"cell {geometry['cell_w']}x{geometry['cell_h']}, " + f"atlas {geometry['cell_w'] * geometry['glyph_count']}x{geometry['cell_h']}, " + f"{args.em} px em, {args.supersample}x, {ink} ink units", + file=sys.stderr, + ) + return EXIT_OK + + +if __name__ == "__main__": + raise SystemExit(main())