fontisan 0.4.9 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +53 -39
- data/docs/STITCHER_GUIDE.adoc +12 -2
- data/lib/fontisan/converters/svg_generator.rb +30 -82
- data/lib/fontisan/sfnt_font.rb +12 -2
- data/lib/fontisan/stitcher/format_metadata.rb +51 -0
- data/lib/fontisan/stitcher/partition_strategy/base.rb +32 -3
- data/lib/fontisan/stitcher/partition_strategy/by_block.rb +429 -0
- data/lib/fontisan/stitcher/partition_strategy/by_plane.rb +59 -29
- data/lib/fontisan/stitcher/partition_strategy/by_script.rb +305 -0
- data/lib/fontisan/stitcher/partition_strategy.rb +2 -0
- data/lib/fontisan/stitcher/source.rb +89 -8
- data/lib/fontisan/stitcher.rb +9 -27
- data/lib/fontisan/subset/table_subsetter.rb +123 -5
- data/lib/fontisan/svg/font_generator.rb +2 -2
- data/lib/fontisan/svg/glyph_generator.rb +44 -9
- data/lib/fontisan/tasks/fixture_downloader.rb +162 -0
- data/lib/fontisan/tasks.rb +11 -0
- data/lib/fontisan/unicode/plane.rb +0 -12
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +2 -0
- metadata +7 -2
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
class Stitcher
|
|
5
|
+
module PartitionStrategy
|
|
6
|
+
# Partition codepoints by Unicode Blocks.txt block.
|
|
7
|
+
#
|
|
8
|
+
# Each non-empty Unicode block becomes one partition. If a single
|
|
9
|
+
# block alone exceeds +cap+, raises PartitionCapExceededError —
|
|
10
|
+
# every block is treated as atomic because there is no finer
|
|
11
|
+
# Unicode-defined boundary inside a block. (Callers who need to
|
|
12
|
+
# split a block further must use ByPlane with explicit carve-outs
|
|
13
|
+
# or implement a custom partitioner.)
|
|
14
|
+
#
|
|
15
|
+
# Partition names follow the canonical Unicode block name with
|
|
16
|
+
# spaces replaced by underscores and a +block_+ prefix:
|
|
17
|
+
#
|
|
18
|
+
# "Basic Latin" => :block_basic_latin
|
|
19
|
+
# "CJK Unified Ideographs" => :block_cjk_unified_ideographs
|
|
20
|
+
#
|
|
21
|
+
# Codepoints not covered by any block in {BLOCKS} (unassigned or
|
|
22
|
+
# in a block omitted from this list) fall into +:block_other+.
|
|
23
|
+
class ByBlock < Base
|
|
24
|
+
# Unicode 16.0 block ranges. Source: Unicode Blocks.txt.
|
|
25
|
+
# Covers all assigned blocks in BMP, SMP, SIP, TIP, and SSP.
|
|
26
|
+
# Unassigned planes (4..13) are omitted — codepoints there
|
|
27
|
+
# fall into +:block_other+, which is the right behavior for
|
|
28
|
+
# partitioning fonts that target current Unicode.
|
|
29
|
+
#
|
|
30
|
+
# The data is inlined (rather than loaded from an external
|
|
31
|
+
# file) so the partitioner is self-contained: no YAML load
|
|
32
|
+
# at startup, no data file to ship. If the list ever needs to
|
|
33
|
+
# be data-driven, swap BLOCKS for a CSV/YAML loader behind
|
|
34
|
+
# the same constant — callers don't care about the source
|
|
35
|
+
# (OCP).
|
|
36
|
+
# rubocop:disable Metrics/CollectionLiteralLength
|
|
37
|
+
BLOCKS = {
|
|
38
|
+
# BMP (Plane 0)
|
|
39
|
+
"Basic_Latin" => 0x0000..0x007F,
|
|
40
|
+
"Latin-1_Supplement" => 0x0080..0x00FF,
|
|
41
|
+
"Latin_Extended-A" => 0x0100..0x017F,
|
|
42
|
+
"Latin_Extended-B" => 0x0180..0x024F,
|
|
43
|
+
"IPA_Extensions" => 0x0250..0x02AF,
|
|
44
|
+
"Spacing_Modifier_Letters" => 0x02B0..0x02FF,
|
|
45
|
+
"Combining_Diacritical_Marks" => 0x0300..0x036F,
|
|
46
|
+
"Greek_and_Coptic" => 0x0370..0x03FF,
|
|
47
|
+
"Cyrillic" => 0x0400..0x04FF,
|
|
48
|
+
"Cyrillic_Supplement" => 0x0500..0x052F,
|
|
49
|
+
"Armenian" => 0x0530..0x058F,
|
|
50
|
+
"Hebrew" => 0x0590..0x05FF,
|
|
51
|
+
"Arabic" => 0x0600..0x06FF,
|
|
52
|
+
"Syriac" => 0x0700..0x074F,
|
|
53
|
+
"Arabic_Supplement" => 0x0750..0x077F,
|
|
54
|
+
"Arabic_Extended-A" => 0x08A0..0x08FF,
|
|
55
|
+
"Arabic_Extended-B" => 0x0870..0x089F,
|
|
56
|
+
"Thaana" => 0x0780..0x07BF,
|
|
57
|
+
"NKo" => 0x07C0..0x07FF,
|
|
58
|
+
"Samaritan" => 0x0800..0x083F,
|
|
59
|
+
"Mandaic" => 0x0840..0x085F,
|
|
60
|
+
"Syriac_Supplement" => 0x0860..0x086F,
|
|
61
|
+
"Devanagari" => 0x0900..0x097F,
|
|
62
|
+
"Bengali" => 0x0980..0x09FF,
|
|
63
|
+
"Gurmukhi" => 0x0A00..0x0A7F,
|
|
64
|
+
"Gujarati" => 0x0A80..0x0AFF,
|
|
65
|
+
"Oriya" => 0x0B00..0x0B7F,
|
|
66
|
+
"Tamil" => 0x0B80..0x0BFF,
|
|
67
|
+
"Telugu" => 0x0C00..0x0C7F,
|
|
68
|
+
"Kannada" => 0x0C80..0x0CFF,
|
|
69
|
+
"Malayalam" => 0x0D00..0x0D7F,
|
|
70
|
+
"Sinhala" => 0x0D80..0x0DFF,
|
|
71
|
+
"Thai" => 0x0E00..0x0E7F,
|
|
72
|
+
"Lao" => 0x0E80..0x0EFF,
|
|
73
|
+
"Tibetan" => 0x0F00..0x0FFF,
|
|
74
|
+
"Myanmar" => 0x1000..0x109F,
|
|
75
|
+
"Georgian" => 0x10A0..0x10FF,
|
|
76
|
+
"Hangul_Jamo" => 0x1100..0x11FF,
|
|
77
|
+
"Ethiopic" => 0x1200..0x137F,
|
|
78
|
+
"Ethiopic_Supplement" => 0x1380..0x139F,
|
|
79
|
+
"Cherokee" => 0x13A0..0x13FF,
|
|
80
|
+
"Unified_Canadian_Aboriginal_Syllabics" => 0x1400..0x167F,
|
|
81
|
+
"Ogham" => 0x1680..0x169F,
|
|
82
|
+
"Runic" => 0x16A0..0x16FF,
|
|
83
|
+
"Tagalog" => 0x1700..0x171F,
|
|
84
|
+
"Hanunoo" => 0x1720..0x173F,
|
|
85
|
+
"Buhid" => 0x1740..0x175F,
|
|
86
|
+
"Tagbanwa" => 0x1760..0x177F,
|
|
87
|
+
"Khmer" => 0x1780..0x17FF,
|
|
88
|
+
"Mongolian" => 0x1800..0x18AF,
|
|
89
|
+
"Unified_Canadian_Aboriginal_Syllabics_Extended" => 0x18B0..0x18FF,
|
|
90
|
+
"Limbu" => 0x1900..0x194F,
|
|
91
|
+
"Tai_Le" => 0x1950..0x197F,
|
|
92
|
+
"New_Tai_Lue" => 0x1980..0x19DF,
|
|
93
|
+
"Khmer_Symbols" => 0x19E0..0x19FF,
|
|
94
|
+
"Buginese" => 0x1A00..0x1A1F,
|
|
95
|
+
"Tai_Tham" => 0x1A20..0x1AAF,
|
|
96
|
+
"Combining_Diacritical_Marks_Extended" => 0x1AB0..0x1AFF,
|
|
97
|
+
"Balinese" => 0x1B00..0x1B7F,
|
|
98
|
+
"Sundanese" => 0x1B80..0x1BBF,
|
|
99
|
+
"Batak" => 0x1BC0..0x1BFF,
|
|
100
|
+
"Lepcha" => 0x1C00..0x1C4F,
|
|
101
|
+
"Ol_Chiki" => 0x1C50..0x1C7F,
|
|
102
|
+
"Cyrillic_Extended-C" => 0x1C80..0x1C8F,
|
|
103
|
+
"Georgian_Extended" => 0x1C90..0x1CBF,
|
|
104
|
+
"Sundanese_Supplement" => 0x1CC0..0x1CCF,
|
|
105
|
+
"Vedic_Extensions" => 0x1CD0..0x1CFF,
|
|
106
|
+
"Phonetic_Extensions" => 0x1D00..0x1D7F,
|
|
107
|
+
"Phonetic_Extensions_Supplement" => 0x1D80..0x1DBF,
|
|
108
|
+
"Combining_Diacritical_Marks_Supplement" => 0x1DC0..0x1DFF,
|
|
109
|
+
"Latin_Extended_Additional" => 0x1E00..0x1EFF,
|
|
110
|
+
"Greek_Extended" => 0x1F00..0x1FFF,
|
|
111
|
+
"General_Punctuation" => 0x2000..0x206F,
|
|
112
|
+
"Superscripts_and_Subscripts" => 0x2070..0x209F,
|
|
113
|
+
"Currency_Symbols" => 0x20A0..0x20CF,
|
|
114
|
+
"Combining_Diacritical_Marks_for_Symbols" => 0x20D0..0x20FF,
|
|
115
|
+
"Letterlike_Symbols" => 0x2100..0x214F,
|
|
116
|
+
"Number_Forms" => 0x2150..0x218F,
|
|
117
|
+
"Arrows" => 0x2190..0x21FF,
|
|
118
|
+
"Mathematical_Operators" => 0x2200..0x22FF,
|
|
119
|
+
"Miscellaneous_Technical" => 0x2300..0x23FF,
|
|
120
|
+
"Control_Pictures" => 0x2400..0x243F,
|
|
121
|
+
"Optical_Character_Recognition" => 0x2440..0x245F,
|
|
122
|
+
"Enclosed_Alphanumerics" => 0x2460..0x24FF,
|
|
123
|
+
"Box_Drawing" => 0x2500..0x257F,
|
|
124
|
+
"Block_Elements" => 0x2580..0x259F,
|
|
125
|
+
"Geometric_Shapes" => 0x25A0..0x25FF,
|
|
126
|
+
"Miscellaneous_Symbols" => 0x2600..0x26FF,
|
|
127
|
+
"Dingbats" => 0x2700..0x27BF,
|
|
128
|
+
"Miscellaneous_Mathematical_Symbols-A" => 0x27C0..0x27EF,
|
|
129
|
+
"Supplemental_Arrows-A" => 0x27F0..0x27FF,
|
|
130
|
+
"Braille_Patterns" => 0x2800..0x28FF,
|
|
131
|
+
"Supplemental_Arrows-B" => 0x2900..0x297F,
|
|
132
|
+
"Miscellaneous_Mathematical_Symbols-B" => 0x2980..0x29FF,
|
|
133
|
+
"Supplemental_Mathematical_Operators" => 0x2A00..0x2AFF,
|
|
134
|
+
"Miscellaneous_Symbols_and_Arrows" => 0x2B00..0x2BFF,
|
|
135
|
+
"Glagolitic" => 0x2C00..0x2C5F,
|
|
136
|
+
"Latin_Extended-C" => 0x2C60..0x2C7F,
|
|
137
|
+
"Coptic" => 0x2C80..0x2CFF,
|
|
138
|
+
"Georgian_Supplement" => 0x2D00..0x2D2F,
|
|
139
|
+
"Tifinagh" => 0x2D30..0x2D7F,
|
|
140
|
+
"Ethiopic_Extended" => 0x2D80..0x2DDF,
|
|
141
|
+
"Supplemental_Punctuation" => 0x2E00..0x2E7F,
|
|
142
|
+
"CJK_Radicals_Supplement" => 0x2E80..0x2EFF,
|
|
143
|
+
"Kangxi_Radicals" => 0x2F00..0x2FDF,
|
|
144
|
+
"Ideographic_Description_Characters" => 0x2FF0..0x2FFF,
|
|
145
|
+
"CJK_Symbols_and_Punctuation" => 0x3000..0x303F,
|
|
146
|
+
"Hiragana" => 0x3040..0x309F,
|
|
147
|
+
"Katakana" => 0x30A0..0x30FF,
|
|
148
|
+
"Bopomofo" => 0x3100..0x312F,
|
|
149
|
+
"Hangul_Compatibility_Jamo" => 0x3130..0x318F,
|
|
150
|
+
"Kanbun" => 0x3190..0x319F,
|
|
151
|
+
"Bopomofo_Extended" => 0x31A0..0x31BF,
|
|
152
|
+
"CJK_Strokes" => 0x31C0..0x31EF,
|
|
153
|
+
"Katakana_Phonetic_Extensions" => 0x31F0..0x31FF,
|
|
154
|
+
"Enclosed_CJK_Letters_and_Months" => 0x3200..0x32FF,
|
|
155
|
+
"CJK_Compatibility" => 0x3300..0x33FF,
|
|
156
|
+
"CJK_Unified_Ideographs_Extension_A" => 0x3400..0x4DBF,
|
|
157
|
+
"Yijing_Hexagram_Symbols" => 0x4DC0..0x4DFF,
|
|
158
|
+
"CJK_Unified_Ideographs" => 0x4E00..0x9FFF,
|
|
159
|
+
"Yi_Syllables" => 0xA000..0xA48F,
|
|
160
|
+
"Yi_Radicals" => 0xA490..0xA4CF,
|
|
161
|
+
"Lisu" => 0xA4D0..0xA4FF,
|
|
162
|
+
"Vai" => 0xA500..0xA63F,
|
|
163
|
+
"Cyrillic_Extended-B" => 0xA640..0xA69F,
|
|
164
|
+
"Bamum" => 0xA6A0..0xA6FF,
|
|
165
|
+
"Modifier_Tone_Letters" => 0xA700..0xA71F,
|
|
166
|
+
"Latin_Extended-D" => 0xA720..0xA7FF,
|
|
167
|
+
"Syloti_Nagri" => 0xA800..0xA82F,
|
|
168
|
+
"Common_Indic_Number_Forms" => 0xA830..0xA83F,
|
|
169
|
+
"Phags-pa" => 0xA840..0xA87F,
|
|
170
|
+
"Saurashtra" => 0xA880..0xA8DF,
|
|
171
|
+
"Devanagari_Extended" => 0xA8E0..0xA8FF,
|
|
172
|
+
"Kayah_Li" => 0xA900..0xA92F,
|
|
173
|
+
"Rejang" => 0xA930..0xA95F,
|
|
174
|
+
"Hangul_Jamo_Extended-A" => 0xA960..0xA97F,
|
|
175
|
+
"Javanese" => 0xA980..0xA9DF,
|
|
176
|
+
"Myanmar_Extended-B" => 0xA9E0..0xA9FF,
|
|
177
|
+
"Cham" => 0xAA00..0xAA5F,
|
|
178
|
+
"Myanmar_Extended-A" => 0xAA60..0xAA7F,
|
|
179
|
+
"Tai_Viet" => 0xAA80..0xAADF,
|
|
180
|
+
"Meetei_Mayek_Extensions" => 0xAAE0..0xAAFF,
|
|
181
|
+
"Ethiopic_Extended-A" => 0xAB00..0xAB2F,
|
|
182
|
+
"Latin_Extended-E" => 0xAB30..0xAB6F,
|
|
183
|
+
"Cherokee_Supplement" => 0xAB70..0xABBF,
|
|
184
|
+
"Meetei_Mayek" => 0xABC0..0xABFF,
|
|
185
|
+
"Hangul_Syllables" => 0xAC00..0xD7AF,
|
|
186
|
+
"Hangul_Jamo_Extended-B" => 0xD7B0..0xD7FF,
|
|
187
|
+
"High_Surrogates" => 0xD800..0xDB7F,
|
|
188
|
+
"High_Private_Use_Surrogates" => 0xDB80..0xDBFF,
|
|
189
|
+
"Low_Surrogates" => 0xDC00..0xDFFF,
|
|
190
|
+
"Private_Use_Area" => 0xE000..0xF8FF,
|
|
191
|
+
"CJK_Compatibility_Ideographs" => 0xF900..0xFAFF,
|
|
192
|
+
"Alphabetic_Presentation_Forms" => 0xFB00..0xFB4F,
|
|
193
|
+
"Arabic_Presentation_Forms-A" => 0xFB50..0xFDFF,
|
|
194
|
+
"Variation_Selectors" => 0xFE00..0xFE0F,
|
|
195
|
+
"Vertical_Forms" => 0xFE10..0xFE1F,
|
|
196
|
+
"Combining_Half_Marks" => 0xFE20..0xFE2F,
|
|
197
|
+
"CJK_Compatibility_Forms" => 0xFE30..0xFE4F,
|
|
198
|
+
"Small_Form_Variants" => 0xFE50..0xFE6F,
|
|
199
|
+
"Arabic_Presentation_Forms-B" => 0xFE70..0xFEFF,
|
|
200
|
+
"Halfwidth_and_Fullwidth_Forms" => 0xFF00..0xFFEF,
|
|
201
|
+
"Specials" => 0xFFF0..0xFFFF,
|
|
202
|
+
|
|
203
|
+
# SMP (Plane 1)
|
|
204
|
+
"Linear_B_Syllabary" => 0x10000..0x1003F,
|
|
205
|
+
"Linear_B_Ideograms" => 0x10080..0x100FF,
|
|
206
|
+
"Aegean_Numbers" => 0x10100..0x1013F,
|
|
207
|
+
"Ancient_Greek_Numbers" => 0x10140..0x1018F,
|
|
208
|
+
"Ancient_Symbols" => 0x10190..0x101CF,
|
|
209
|
+
"Phaistos_Disc" => 0x101D0..0x101FF,
|
|
210
|
+
"Lycian" => 0x10280..0x1029F,
|
|
211
|
+
"Carian" => 0x102A0..0x102DF,
|
|
212
|
+
"Coptic_Epact_Numbers" => 0x102E0..0x102FF,
|
|
213
|
+
"Old_Italic" => 0x10300..0x1032F,
|
|
214
|
+
"Gothic" => 0x10330..0x1034F,
|
|
215
|
+
"Old_Permic" => 0x10350..0x1037F,
|
|
216
|
+
"Ugaritic" => 0x10380..0x1039F,
|
|
217
|
+
"Old_Persian" => 0x103A0..0x103DF,
|
|
218
|
+
"Deseret" => 0x10400..0x1044F,
|
|
219
|
+
"Shavian" => 0x10450..0x1047F,
|
|
220
|
+
"Osmanya" => 0x10480..0x104AF,
|
|
221
|
+
"Osage" => 0x104B0..0x104FF,
|
|
222
|
+
"Elbasan" => 0x10500..0x1052F,
|
|
223
|
+
"Caucasian_Albanian" => 0x10530..0x1056F,
|
|
224
|
+
"Vithkuqi" => 0x10570..0x105BF,
|
|
225
|
+
"Linear_A" => 0x10600..0x1077F,
|
|
226
|
+
"Latin_Extended-F" => 0x10780..0x107BF,
|
|
227
|
+
"Cypriot_Syllabary" => 0x10800..0x1083F,
|
|
228
|
+
"Imperial_Aramaic" => 0x10840..0x1085F,
|
|
229
|
+
"Palmyrene" => 0x10860..0x1087F,
|
|
230
|
+
"Nabataean" => 0x10880..0x108AF,
|
|
231
|
+
"Hatran" => 0x108E0..0x108FF,
|
|
232
|
+
"Phoenician" => 0x10900..0x1091F,
|
|
233
|
+
"Lydian" => 0x10920..0x1093F,
|
|
234
|
+
"Meroitic_Hieroglyphs" => 0x10980..0x1099F,
|
|
235
|
+
"Meroitic_Cursive" => 0x109A0..0x109FF,
|
|
236
|
+
"Kharoshthi" => 0x10A00..0x10A5F,
|
|
237
|
+
"Old_South_Arabian" => 0x10A60..0x10A7F,
|
|
238
|
+
"Old_North_Arabian" => 0x10A80..0x10A9F,
|
|
239
|
+
"Manichaean" => 0x10AC0..0x10AFF,
|
|
240
|
+
"Avestan" => 0x10B00..0x10B3F,
|
|
241
|
+
"Inscriptional_Parthian" => 0x10B40..0x10B5F,
|
|
242
|
+
"Inscriptional_Pahlavi" => 0x10B60..0x10B7F,
|
|
243
|
+
"Psalter_Pahlavi" => 0x10B80..0x10BAF,
|
|
244
|
+
"Old_Turkic" => 0x10C00..0x10C4F,
|
|
245
|
+
"Old_Hungarian" => 0x10C80..0x10CFF,
|
|
246
|
+
"Hanifi_Rohingya" => 0x10D00..0x10D3F,
|
|
247
|
+
"Garay" => 0x10D40..0x10D8F,
|
|
248
|
+
"Rumi_Numeral_Symbols" => 0x10E60..0x10E7F,
|
|
249
|
+
"Yezidi" => 0x10E80..0x10EBF,
|
|
250
|
+
"Arabic_Extended-C" => 0x10EC0..0x10EFF,
|
|
251
|
+
"Old_Sogdian" => 0x10F00..0x10F2F,
|
|
252
|
+
"Sogdian" => 0x10F30..0x10F6F,
|
|
253
|
+
"Old_Uyghur" => 0x10F70..0x10FAF,
|
|
254
|
+
"Chorasmian" => 0x10FB0..0x10FBF,
|
|
255
|
+
"Elymaic" => 0x10FE0..0x10FEF,
|
|
256
|
+
"Brahmi" => 0x11000..0x1107F,
|
|
257
|
+
"Kaithi" => 0x11080..0x110CF,
|
|
258
|
+
"Sora_Sompeng" => 0x110D0..0x110FF,
|
|
259
|
+
"Chakma" => 0x11100..0x1114F,
|
|
260
|
+
"Mahajani" => 0x11150..0x1117F,
|
|
261
|
+
"Sharada" => 0x11180..0x111DF,
|
|
262
|
+
"Sinhala_Archaic_Numbers" => 0x111E0..0x111FF,
|
|
263
|
+
"Khojki" => 0x11200..0x1124F,
|
|
264
|
+
"Multani" => 0x11280..0x112AF,
|
|
265
|
+
"Khudawadi" => 0x112B0..0x112FF,
|
|
266
|
+
"Grantha" => 0x11300..0x1137F,
|
|
267
|
+
"Tulu-Tigalari" => 0x11380..0x113FF,
|
|
268
|
+
"Newa" => 0x11400..0x1147F,
|
|
269
|
+
"Tirhuta" => 0x11480..0x114DF,
|
|
270
|
+
"Siddham" => 0x11580..0x115FF,
|
|
271
|
+
"Modi" => 0x11600..0x1165F,
|
|
272
|
+
"Mongolian_Supplement" => 0x11660..0x1167F,
|
|
273
|
+
"Takri" => 0x11680..0x116CF,
|
|
274
|
+
"Myanmar_Extended-C" => 0x116D0..0x116FF,
|
|
275
|
+
"Ahom" => 0x11700..0x1174F,
|
|
276
|
+
"Dogra" => 0x11800..0x1184F,
|
|
277
|
+
"Warang_Citi" => 0x118A0..0x118FF,
|
|
278
|
+
"Dives_Akuru" => 0x11900..0x1195F,
|
|
279
|
+
"Nandinagari" => 0x119A0..0x119FF,
|
|
280
|
+
"Zanabazar_Square" => 0x11A00..0x11A4F,
|
|
281
|
+
"Soyombo" => 0x11A50..0x11AAF,
|
|
282
|
+
"Unified_Canadian_Aboriginal_Syllabics_Extended-A" => 0x11AB0..0x11ABF,
|
|
283
|
+
"Pau_Cin_Hmo" => 0x11AC0..0x11AFF,
|
|
284
|
+
"Bhaiksuki" => 0x11C00..0x11C6F,
|
|
285
|
+
"Marchen" => 0x11C70..0x11CBF,
|
|
286
|
+
"Masaram_Gondi" => 0x11D00..0x11D5F,
|
|
287
|
+
"Gunjala_Gondi" => 0x11D60..0x11DAF,
|
|
288
|
+
"Makasar" => 0x11EE0..0x11EFF,
|
|
289
|
+
"Kawi" => 0x11F00..0x11F5F,
|
|
290
|
+
"Lisu_Supplement" => 0x11FB0..0x11FBF,
|
|
291
|
+
"Tamil_Supplement" => 0x11FC0..0x11FFF,
|
|
292
|
+
"Cuneiform" => 0x12000..0x123FF,
|
|
293
|
+
"Cuneiform_Numbers_and_Punctuation" => 0x12400..0x1247F,
|
|
294
|
+
"Early_Dynastic_Cuneiform" => 0x12480..0x1254F,
|
|
295
|
+
"Cypro-Minoan" => 0x12F90..0x12FFF,
|
|
296
|
+
"Egyptian_Hieroglyphs" => 0x13000..0x1342F,
|
|
297
|
+
"Egyptian_Hieroglyph_Format_Controls" => 0x13430..0x1345F,
|
|
298
|
+
"Egyptian_Hieroglyphs_Extended-A" => 0x13460..0x143FF,
|
|
299
|
+
"Anatolian_Hieroglyphs" => 0x14400..0x1467F,
|
|
300
|
+
"Bamum_Supplement" => 0x16800..0x16A3F,
|
|
301
|
+
"Mro" => 0x16A40..0x16A6F,
|
|
302
|
+
"Tangsa" => 0x16A70..0x16ACF,
|
|
303
|
+
"Bassa_Vah" => 0x16AD0..0x16AFF,
|
|
304
|
+
"Pahawh_Hmong" => 0x16B00..0x16B8F,
|
|
305
|
+
"Medefaidrin" => 0x16E40..0x16E9F,
|
|
306
|
+
"Miao" => 0x16F00..0x16F9F,
|
|
307
|
+
"Ideographic_Symbols_and_Punctuation" => 0x16FE0..0x16FFF,
|
|
308
|
+
"Tangut" => 0x17000..0x187FF,
|
|
309
|
+
"Tangut_Components" => 0x18800..0x18AFF,
|
|
310
|
+
"Khitan_Small_Script" => 0x18B00..0x18CFF,
|
|
311
|
+
"Tangut_Supplement" => 0x18D00..0x18D7F,
|
|
312
|
+
"Kana_Supplement" => 0x1B000..0x1B0FF,
|
|
313
|
+
"Kana_Extended-A" => 0x1B100..0x1B12F,
|
|
314
|
+
"Small_Kana_Extension" => 0x1B130..0x1B16F,
|
|
315
|
+
"Nushu" => 0x1B170..0x1B2FF,
|
|
316
|
+
"Duployan" => 0x1BC00..0x1BC9F,
|
|
317
|
+
"Shorthand_Format_Controls" => 0x1BCA0..0x1BCAF,
|
|
318
|
+
"Znamenny_Musical_Notation" => 0x1CF00..0x1CFCF,
|
|
319
|
+
"Byzantine_Musical_Symbols" => 0x1D000..0x1D0FF,
|
|
320
|
+
"Musical_Symbols" => 0x1D100..0x1D1FF,
|
|
321
|
+
"Ancient_Greek_Musical_Notation" => 0x1D200..0x1D24F,
|
|
322
|
+
"Kaktovik_Numerals" => 0x1D2C0..0x1D2FF,
|
|
323
|
+
"Tai_Xuan_Jing_Symbols" => 0x1D300..0x1D35F,
|
|
324
|
+
"Counting_Rod_Numerals" => 0x1D360..0x1D37F,
|
|
325
|
+
"Mathematical_Alphanumeric_Symbols" => 0x1D400..0x1D7FF,
|
|
326
|
+
"Sutton_SignWriting" => 0x1D800..0x1DAAF,
|
|
327
|
+
"Latin_Extended-G" => 0x1DF00..0x1DFFF,
|
|
328
|
+
"Glagolitic_Supplement" => 0x1E000..0x1E02F,
|
|
329
|
+
"Cyrillic_Extended-D" => 0x1E030..0x1E08F,
|
|
330
|
+
"Nyiakeng_Puachue_Hmong" => 0x1E100..0x1E14F,
|
|
331
|
+
"Toto" => 0x1E290..0x1E2BF,
|
|
332
|
+
"Wancho" => 0x1E2C0..0x1E2FF,
|
|
333
|
+
"Nag_Mundari" => 0x1E4D0..0x1E4FF,
|
|
334
|
+
"Ethiopian_Extended-B" => 0x1E7E0..0x1E7FF,
|
|
335
|
+
"Mende_Kikakui" => 0x1E800..0x1E8DF,
|
|
336
|
+
"Adlam" => 0x1E900..0x1E95F,
|
|
337
|
+
"Indic_Siyaq_Numbers" => 0x1EC70..0x1ECBF,
|
|
338
|
+
"Ottoman_Siyaq_Numbers" => 0x1ED00..0x1ED4F,
|
|
339
|
+
"Arabic_Mathematical_Alphabetic_Symbols" => 0x1EE00..0x1EEFF,
|
|
340
|
+
"Mahjong_Tiles" => 0x1F000..0x1F02F,
|
|
341
|
+
"Domino_Tiles" => 0x1F030..0x1F09F,
|
|
342
|
+
"Playing_Cards" => 0x1F0A0..0x1F0FF,
|
|
343
|
+
"Enclosed_Alphanumeric_Supplement" => 0x1F100..0x1F1FF,
|
|
344
|
+
"Enclosed_Ideographic_Supplement" => 0x1F200..0x1F2FF,
|
|
345
|
+
"Miscellaneous_Symbols_and_Pictographs" => 0x1F300..0x1F5FF,
|
|
346
|
+
"Emoticons" => 0x1F600..0x1F64F,
|
|
347
|
+
"Ornamental_Dingbats" => 0x1F650..0x1F67F,
|
|
348
|
+
"Transport_and_Map_Symbols" => 0x1F680..0x1F6FF,
|
|
349
|
+
"Alchemical_Symbols" => 0x1F700..0x1F77F,
|
|
350
|
+
"Geometric_Shapes_Extended" => 0x1F780..0x1F7FF,
|
|
351
|
+
"Supplemental_Arrows-C" => 0x1F800..0x1F8FF,
|
|
352
|
+
"Supplemental_Symbols_and_Pictographs" => 0x1F900..0x1F9FF,
|
|
353
|
+
"Chess_Symbols" => 0x1FA00..0x1FA6F,
|
|
354
|
+
"Symbols_and_Pictographs_Extended-A" => 0x1FA70..0x1FAFF,
|
|
355
|
+
"Symbols_for_Legacy_Computing" => 0x1FB00..0x1FBFF,
|
|
356
|
+
|
|
357
|
+
# SIP (Plane 2)
|
|
358
|
+
"CJK_Unified_Ideographs_Extension-B" => 0x20000..0x2A6DF,
|
|
359
|
+
"CJK_Unified_Ideographs_Extension-C" => 0x2A700..0x2B73F,
|
|
360
|
+
"CJK_Unified_Ideographs_Extension-D" => 0x2B740..0x2B81F,
|
|
361
|
+
"CJK_Unified_Ideographs_Extension-E" => 0x2B820..0x2CEAF,
|
|
362
|
+
"CJK_Unified_Ideographs_Extension-F" => 0x2CEB0..0x2EBEF,
|
|
363
|
+
"CJK_Unified_Ideographs_Extension-I" => 0x2EBF0..0x2EE5F,
|
|
364
|
+
"CJK_Compatibility_Ideographs_Supplement" => 0x2F800..0x2FA1F,
|
|
365
|
+
|
|
366
|
+
# TIP (Plane 3)
|
|
367
|
+
# Note: Unicode 16.0 also defines Extension-H (U+31350..U+323AF)
|
|
368
|
+
# which overlaps with Extension-G (U+30000..U+313AF). Omitted
|
|
369
|
+
# here to keep the no-overlap invariant clean; codepoints in
|
|
370
|
+
# the Extension-H range fall through to Extension-G, which is
|
|
371
|
+
# correct for every codepoint outside the rare overlap region.
|
|
372
|
+
"CJK_Unified_Ideographs_Extension-G" => 0x30000..0x313AF,
|
|
373
|
+
|
|
374
|
+
# SSP (Plane 14)
|
|
375
|
+
"Tags" => 0xE0000..0xE007F,
|
|
376
|
+
"Variation_Selectors_Supplement" => 0xE0100..0xE01EF,
|
|
377
|
+
}.freeze
|
|
378
|
+
# rubocop:enable Metrics/CollectionLiteralLength
|
|
379
|
+
|
|
380
|
+
# @param cp_map [Hash{Integer=>Object}] codepoint → donor label
|
|
381
|
+
# @param cap [Integer] max codepoints per partition
|
|
382
|
+
# @return [Blueprint]
|
|
383
|
+
def call(cp_map, cap: DEFAULT_CAP)
|
|
384
|
+
grouped = group_by_block(cp_map)
|
|
385
|
+
partitions = grouped.map do |label, entries|
|
|
386
|
+
if entries.size > cap
|
|
387
|
+
raise PartitionCapExceededError.new(
|
|
388
|
+
block_label: label,
|
|
389
|
+
actual: entries.size,
|
|
390
|
+
cap: cap,
|
|
391
|
+
)
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
Partition.new(
|
|
395
|
+
name: partition_name(label),
|
|
396
|
+
cps: entries.map(&:first),
|
|
397
|
+
donor_map: entries.to_h,
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
Blueprint.new(partitions: partitions)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private
|
|
404
|
+
|
|
405
|
+
def group_by_block(cp_map)
|
|
406
|
+
cp_map.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(cp, label), h|
|
|
407
|
+
block_label = find_block_label(cp) || :other
|
|
408
|
+
h[block_label] << [cp, label]
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Linear scan is fine for ~340 entries at typical partition-time
|
|
413
|
+
# call volumes (once per Stitcher.run). If profiling shows it
|
|
414
|
+
# hot, swap for a sorted [start, end, label] array + binary
|
|
415
|
+
# search — the data structure can change without touching the
|
|
416
|
+
# public API.
|
|
417
|
+
def find_block_label(codepoint)
|
|
418
|
+
BLOCKS.find { |_label, range| range.cover?(codepoint) }&.first
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def partition_name(label)
|
|
422
|
+
return :block_other if label == :other
|
|
423
|
+
|
|
424
|
+
:"block_#{label.downcase}"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
@@ -8,15 +8,47 @@ module Fontisan
|
|
|
8
8
|
# For each plane with codepoints:
|
|
9
9
|
# - if the count fits under +cap+, emit one partition named
|
|
10
10
|
# +:plane_<n>+ (e.g. +:plane_0+, +:plane_2+);
|
|
11
|
-
# - otherwise sub-split
|
|
12
|
-
# boundaries, naming partitions
|
|
13
|
-
# and so on.
|
|
11
|
+
# - otherwise sub-split along the recognized sub-plane block
|
|
12
|
+
# boundaries (+RECOGNIZED_BLOCKS+), naming partitions
|
|
13
|
+
# +:plane_<n>_a+, +:plane_<n>_b+, and so on.
|
|
14
14
|
#
|
|
15
|
-
# If a single
|
|
16
|
-
# {Fontisan::
|
|
15
|
+
# If a single +ATOMIC_BLOCKS+ entry alone exceeds +cap+, raises
|
|
16
|
+
# {Fontisan::PartitionCapExceededError} — the partitioner cannot
|
|
17
17
|
# satisfy the cap and the caller must use a smaller cap, split
|
|
18
18
|
# manually, or switch to a format with a higher glyph limit.
|
|
19
|
+
# +CARVE_OUT_BLOCKS+ entries are chunkable and will be sliced
|
|
20
|
+
# like +:other+ if they exceed +cap+.
|
|
19
21
|
class ByPlane < Base
|
|
22
|
+
# Atomic sub-plane blocks: cannot be sub-split further because
|
|
23
|
+
# no finer-grained Unicode boundary exists inside them. If one
|
|
24
|
+
# alone exceeds +cap+, raise PartitionCapExceededError.
|
|
25
|
+
#
|
|
26
|
+
# Labels follow the Unicode Blocks.txt block names with spaces
|
|
27
|
+
# replaced by underscores.
|
|
28
|
+
ATOMIC_BLOCKS = {
|
|
29
|
+
"CJK_Ext_B" => 0x2A700..0x2B73F,
|
|
30
|
+
"CJK_Ext_C" => 0x2B740..0x2B81F,
|
|
31
|
+
"CJK_Ext_D" => 0x2B820..0x2CEAF,
|
|
32
|
+
"CJK_Ext_E" => 0x2CEB0..0x2EBEF,
|
|
33
|
+
"CJK_Ext_F" => 0x2EBF0..0x2EE5F,
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# Carve-out boundaries: sub-plane regions large enough to
|
|
37
|
+
# warrant their own partition bucket when a plane overflows
|
|
38
|
+
# +cap+. Unlike atomic blocks, these are chunkable — if one
|
|
39
|
+
# alone exceeds +cap+, it gets sliced into +cap+-sized chunks
|
|
40
|
+
# like +:other+ does, never raised.
|
|
41
|
+
CARVE_OUT_BLOCKS = {
|
|
42
|
+
"CJK_Unified_Ideographs" => 0x4E00..0x9FFF,
|
|
43
|
+
"Hangul_Syllables" => 0xAC00..0xD7AF,
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
# Every codepoint range recognized as a distinct sub-plane
|
|
47
|
+
# bucket. Atomic blocks first, then carve-out blocks. Used by
|
|
48
|
+
# the bucketing pass to decide what gets its own partition vs.
|
|
49
|
+
# falls into +:other+.
|
|
50
|
+
RECOGNIZED_BLOCKS = ATOMIC_BLOCKS.merge(CARVE_OUT_BLOCKS).freeze
|
|
51
|
+
|
|
20
52
|
# @param cp_map [Hash{Integer=>Object}] codepoint → donor label
|
|
21
53
|
# @param cap [Integer] max codepoints per partition
|
|
22
54
|
# @return [Blueprint]
|
|
@@ -50,20 +82,21 @@ module Fontisan
|
|
|
50
82
|
)
|
|
51
83
|
end
|
|
52
84
|
|
|
53
|
-
# When a plane overflows +cap+, carve it along the
|
|
54
|
-
#
|
|
55
|
-
# outside
|
|
56
|
-
#
|
|
57
|
-
# if a single block alone
|
|
58
|
-
#
|
|
59
|
-
# boundaries to use),
|
|
85
|
+
# When a plane overflows +cap+, carve it along the recognized
|
|
86
|
+
# sub-plane block boundaries. The +:other+ bucket (everything
|
|
87
|
+
# outside +RECOGNIZED_BLOCKS+) and any +CARVE_OUT_BLOCKS+ entries
|
|
88
|
+
# are split into chunks of +cap+. +ATOMIC_BLOCKS+ entries become
|
|
89
|
+
# one partition atomically — if a single atomic block alone
|
|
90
|
+
# exceeds +cap+, we cannot sub-split it (its codepoints are
|
|
91
|
+
# contiguous and we don't have finer-grained boundaries to use),
|
|
92
|
+
# so raise.
|
|
60
93
|
def sub_split_by_block(plane_num, entries, cap)
|
|
61
|
-
buckets =
|
|
94
|
+
buckets = bucket_by_recognized_block(entries)
|
|
62
95
|
partitions = []
|
|
63
96
|
suffix = "a"
|
|
64
97
|
|
|
65
98
|
buckets.each do |label, bucket_entries|
|
|
66
|
-
if
|
|
99
|
+
if atomic_block?(label) && bucket_entries.size > cap
|
|
67
100
|
raise PartitionCapExceededError.new(
|
|
68
101
|
block_label: label,
|
|
69
102
|
actual: bucket_entries.size,
|
|
@@ -83,8 +116,8 @@ module Fontisan
|
|
|
83
116
|
partitions
|
|
84
117
|
end
|
|
85
118
|
|
|
86
|
-
def
|
|
87
|
-
|
|
119
|
+
def atomic_block?(label)
|
|
120
|
+
ATOMIC_BLOCKS.key?(label)
|
|
88
121
|
end
|
|
89
122
|
|
|
90
123
|
# Split +entries+ into sub-arrays of at most +cap+ each.
|
|
@@ -92,17 +125,16 @@ module Fontisan
|
|
|
92
125
|
entries.each_slice(cap).to_a
|
|
93
126
|
end
|
|
94
127
|
|
|
95
|
-
# Bucket entries by which
|
|
96
|
-
# into. Entries outside any known
|
|
97
|
-
# which is then packed into its own partition(s).
|
|
98
|
-
|
|
128
|
+
# Bucket entries by which recognized sub-plane block (or :other)
|
|
129
|
+
# they fall into. Entries outside any known block go into :other,
|
|
130
|
+
# which is then packed into its own partition(s). Order: :other
|
|
131
|
+
# first if non-empty, then recognized blocks in declaration order.
|
|
132
|
+
def bucket_by_recognized_block(entries)
|
|
99
133
|
buckets = { other: [] }
|
|
100
|
-
|
|
101
|
-
buckets[label] = []
|
|
102
|
-
end
|
|
134
|
+
RECOGNIZED_BLOCKS.each_key { |label| buckets[label] = [] }
|
|
103
135
|
|
|
104
136
|
entries.each do |cp, label|
|
|
105
|
-
block =
|
|
137
|
+
block = find_recognized_block(cp)
|
|
106
138
|
if block
|
|
107
139
|
buckets[block] << [cp, label]
|
|
108
140
|
else
|
|
@@ -110,18 +142,16 @@ module Fontisan
|
|
|
110
142
|
end
|
|
111
143
|
end
|
|
112
144
|
|
|
113
|
-
# Drop empty buckets; preserve order (other first if non-empty,
|
|
114
|
-
# then CJK blocks in declaration order).
|
|
115
145
|
ordered = []
|
|
116
146
|
ordered << [:other, buckets[:other]] unless buckets[:other].empty?
|
|
117
|
-
|
|
147
|
+
RECOGNIZED_BLOCKS.each_key do |label|
|
|
118
148
|
ordered << [label, buckets[label]] unless buckets[label].empty?
|
|
119
149
|
end
|
|
120
150
|
ordered
|
|
121
151
|
end
|
|
122
152
|
|
|
123
|
-
def
|
|
124
|
-
|
|
153
|
+
def find_recognized_block(codepoint)
|
|
154
|
+
RECOGNIZED_BLOCKS.find do |_label, range|
|
|
125
155
|
range.include?(codepoint)
|
|
126
156
|
end&.first
|
|
127
157
|
end
|