├── .gitignore ├── example.png ├── LICENSE ├── README.md ├── FONT_LICENSE ├── template.sfd └── gen_tofu.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.otf 2 | *.ttf 3 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanPotato/Tofu/HEAD/example.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Hasan Ibraheem 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tofu 2 | 3 | ![screenshot](example.png) 4 | For the curious, this is [font-manager](https://fontmanager.github.io/) 5 | 6 | ### What is this? 7 | This is an attempt by me to make a font (collection) that is just a box with the unicode code point inside. These are usually not in a font but rather programmatically generated by programs as they encounter characters that have no associated glyph. 8 | 9 | ### Why? 10 | Because I wanted zero width characters in my code to be not zero width. I was trying out vss code (pretty nice imo) and found I should be able to use some CSS magic to make it so that certain characters are displayed by this font. ([see also](https://graphicdesign.stackexchange.com/questions/95687/making-a-tofu-font)) 11 | 12 | ### Things that need to get fixed 13 | * It's not very readable when small. Make better tofu template 14 | * The size of the generated font collection can probably be made smaller (for example by excluding unassigned characters) 15 | * The progress bar isn't smooth since the fontforge lib provides no way to measure its progress when loading the generated .sfd files 16 | 17 | ### This is stupid 18 | Yeah probably, but for its very specific use case it's not terribly bad. 19 | 20 | ### Generating a font 21 | Caching should take care of the intensive IO issue, if not then use a ramdisk. 22 | 23 | I used python3 for this, it may work in python2. If it does not please feel free to make a pull request to fix it but I am not actively supporting python2. 24 | 25 | The syntax for the program is as follows. `gen_tofu.py 0000-FFFD [10000-1FFFF] [-s X]` 26 | `0000` is the start of the first range 27 | `FFFD` is the end of the first range 28 | `10000` is the start of the second range (optional, you can use as many ranges as you want) 29 | `1FFFF` is the end of the second range (optional) 30 | `X` is the number of characters that each font will contain (optional) 31 | 32 | ### License 33 | MIT, not sure if the font needs a special license. ▯ 34 | -------------------------------------------------------------------------------- /FONT_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Hasan Ibraheem, 2 | with Reserved Font Name Tofu. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /template.sfd: -------------------------------------------------------------------------------- 1 | SplineFontDB: 3.0 2 | FontName: Tofu 3 | FullName: Tofu Template 4 | FamilyName: Tofu 5 | Weight: Regular 6 | Copyright: Copyright (c) 2017, Hasan Ibraheem 7 | Version: 0.1 8 | UnderlinePosition: -100 9 | UnderlineWidth: 50 10 | Ascent: 800 11 | Descent: 200 12 | LayerCount: 2 13 | Layer: 0 0 "Back" 1 14 | Layer: 1 0 "Fore" 0 15 | DEI: 91125 16 | Encoding: ISO8859-1 17 | NameList: AGL For New Fonts 18 | AntiAlias: 1 19 | BeginChars: 65536 65536 20 | 21 | StartChar: tbase 22 | Encoding: 0 -1 0 23 | Flags: HW 24 | Fore 25 | SplineSet 26 | 10 790 m 1 27 | 990 790 l 1 28 | 990 -190 l 1 29 | 10 -190 l 1 30 | 10 790 l 1 31 | 20 780 m 1 32 | 20 -180 l 1 33 | 980 -180 l 1 34 | 980 780 l 1 35 | 20 780 l 1 36 | EndSplineSet 37 | EndChar 38 | 39 | StartChar: t0 40 | Encoding: 1 -1 1 41 | Flags: HW 42 | Fore 43 | SplineSet 44 | 0 425 m 1 45 | 255 425 l 1 46 | 255 0 l 1 47 | 0 0 l 1 48 | 0 425 l 1 49 | 85 340 m 1 50 | 85 85 l 1 51 | 170 85 l 1 52 | 170 340 l 1 53 | 85 340 l 1 54 | EndSplineSet 55 | EndChar 56 | 57 | StartChar: t1 58 | Encoding: 2 -1 2 59 | Flags: HW 60 | Fore 61 | SplineSet 62 | 0 425 m 1 63 | 170 425 l 1 64 | 170 85 l 1 65 | 255 85 l 1 66 | 255 0 l 1 67 | 0 0 l 1 68 | 0 85 l 1 69 | 85 85 l 1 70 | 85 340 l 1 71 | 0 340 l 1 72 | 0 425 l 1 73 | EndSplineSet 74 | EndChar 75 | 76 | StartChar: t2 77 | Encoding: 3 -1 3 78 | Flags: HW 79 | Fore 80 | SplineSet 81 | 0 425 m 1 82 | 255 425 l 1 83 | 255 170 l 1 84 | 85 170 l 1 85 | 85 85 l 1 86 | 255 85 l 1 87 | 255 0 l 1 88 | 0 0 l 1 89 | 0 255 l 1 90 | 170 255 l 1 91 | 170 340 l 1 92 | 0 340 l 1 93 | 0 425 l 1 94 | EndSplineSet 95 | EndChar 96 | 97 | StartChar: t3 98 | Encoding: 4 -1 4 99 | Flags: HW 100 | Fore 101 | SplineSet 102 | 0 425 m 1 103 | 255 425 l 1 104 | 255 0 l 1 105 | 0 0 l 1 106 | 0 85 l 1 107 | 170 85 l 1 108 | 170 170 l 1 109 | 0 170 l 1 110 | 0 255 l 1 111 | 170 255 l 1 112 | 170 340 l 1 113 | 0 340 l 1 114 | 0 425 l 1 115 | EndSplineSet 116 | EndChar 117 | 118 | StartChar: t4 119 | Encoding: 5 -1 5 120 | Flags: HW 121 | Fore 122 | SplineSet 123 | 0 425 m 1 124 | 85 425 l 1 125 | 85 255 l 1 126 | 170 255 l 1 127 | 170 425 l 1 128 | 255 425 l 1 129 | 255 0 l 1 130 | 170 0 l 1 131 | 170 170 l 1 132 | 0 170 l 1 133 | 0 425 l 1 134 | EndSplineSet 135 | EndChar 136 | 137 | StartChar: t5 138 | Encoding: 6 -1 6 139 | Flags: HW 140 | Fore 141 | SplineSet 142 | 0 425 m 1 143 | 255 425 l 1 144 | 255 340 l 1 145 | 85 340 l 1 146 | 85 255 l 1 147 | 255 255 l 1 148 | 255 0 l 1 149 | 0 0 l 1 150 | 0 85 l 1 151 | 170 85 l 1 152 | 170 170 l 1 153 | 0 170 l 1 154 | 0 425 l 1 155 | EndSplineSet 156 | EndChar 157 | 158 | StartChar: t6 159 | Encoding: 7 -1 7 160 | Flags: HW 161 | Fore 162 | SplineSet 163 | 0 425 m 1 164 | 255 425 l 1 165 | 255 340 l 1 166 | 85 340 l 1 167 | 85 255 l 1 168 | 255 255 l 1 169 | 255 0 l 1 170 | 0 0 l 1 171 | 0 425 l 1 172 | 85 170 m 1 173 | 85 85 l 1 174 | 170 85 l 1 175 | 170 170 l 1 176 | 85 170 l 1 177 | EndSplineSet 178 | EndChar 179 | 180 | StartChar: t7 181 | Encoding: 8 -1 8 182 | Flags: HW 183 | Fore 184 | SplineSet 185 | 0 425 m 1 186 | 255 425 l 1 187 | 255 0 l 1 188 | 170 0 l 1 189 | 170 340 l 1 190 | 0 340 l 1 191 | 0 425 l 1 192 | EndSplineSet 193 | EndChar 194 | 195 | StartChar: t8 196 | Encoding: 9 -1 9 197 | Flags: HW 198 | Fore 199 | SplineSet 200 | 0 425 m 1 201 | 255 425 l 1 202 | 255 0 l 1 203 | 0 0 l 1 204 | 0 425 l 1 205 | 85 340 m 1 206 | 85 255 l 1 207 | 170 255 l 1 208 | 170 340 l 1 209 | 85 340 l 1 210 | 85 170 m 1 211 | 85 85 l 1 212 | 170 85 l 1 213 | 170 170 l 1 214 | 85 170 l 1 215 | EndSplineSet 216 | EndChar 217 | 218 | StartChar: t9 219 | Encoding: 10 -1 10 220 | Flags: HW 221 | Fore 222 | SplineSet 223 | 0 425 m 1 224 | 255 425 l 1 225 | 255 0 l 1 226 | 170 0 l 1 227 | 170 170 l 1 228 | 0 170 l 1 229 | 0 425 l 1 230 | 85 340 m 1 231 | 85 255 l 1 232 | 170 255 l 1 233 | 170 340 l 1 234 | 85 340 l 1 235 | EndSplineSet 236 | EndChar 237 | 238 | StartChar: tA 239 | Encoding: 11 -1 11 240 | Flags: HW 241 | Fore 242 | SplineSet 243 | 0 425 m 1 244 | 255 425 l 1 245 | 255 0 l 1 246 | 170 0 l 1 247 | 170 170 l 1 248 | 85 170 l 1 249 | 85 0 l 1 250 | 0 0 l 1 251 | 0 425 l 1 252 | 85 340 m 1 253 | 85 255 l 1 254 | 170 255 l 1 255 | 170 340 l 1 256 | 85 340 l 1 257 | EndSplineSet 258 | EndChar 259 | 260 | StartChar: tB 261 | Encoding: 12 -1 12 262 | Flags: HW 263 | Fore 264 | SplineSet 265 | 0 425 m 1 266 | 170 425 l 1 267 | 170 340 l 1 268 | 85 340 l 1 269 | 85 255 l 1 270 | 255 255 l 1 271 | 255 340 l 1 272 | 170 340 l 1 273 | 170 170 l 1 274 | 85 170 l 1 275 | 85 85 l 1 276 | 255 85 l 1 277 | 255 170 l 1 278 | 170 170 l 1 279 | 170 0 l 1 280 | 0 0 l 1 281 | 0 425 l 1 282 | EndSplineSet 283 | EndChar 284 | 285 | StartChar: tC 286 | Encoding: 13 -1 13 287 | Flags: HW 288 | Fore 289 | SplineSet 290 | 0 425 m 1 291 | 255 425 l 1 292 | 255 340 l 1 293 | 85 340 l 1 294 | 85 85 l 1 295 | 255 85 l 1 296 | 255 0 l 1 297 | 0 0 l 1 298 | 0 425 l 1 299 | EndSplineSet 300 | EndChar 301 | 302 | StartChar: tD 303 | Encoding: 14 -1 14 304 | Flags: HW 305 | Width: 3 306 | Fore 307 | SplineSet 308 | 0 425 m 1 309 | 170 425 l 1 310 | 170 340 l 1 311 | 85 340 l 1 312 | 85 85 l 1 313 | 170 85 l 1 314 | 170 340 l 1 315 | 255 340 l 1 316 | 255 85 l 1 317 | 170 85 l 1 318 | 170 0 l 1 319 | 0 0 l 1 320 | 0 425 l 1 321 | EndSplineSet 322 | EndChar 323 | 324 | StartChar: tE 325 | Encoding: 15 -1 15 326 | Flags: HW 327 | Fore 328 | SplineSet 329 | 0 425 m 1 330 | 255 425 l 1 331 | 255 340 l 1 332 | 85 340 l 1 333 | 85 255 l 1 334 | 255 255 l 1 335 | 255 170 l 1 336 | 85 170 l 1 337 | 85 85 l 1 338 | 255 85 l 1 339 | 255 0 l 1 340 | 0 0 l 1 341 | 0 425 l 1 342 | EndSplineSet 343 | EndChar 344 | 345 | StartChar: tF 346 | Encoding: 16 -1 16 347 | Flags: HW 348 | Fore 349 | SplineSet 350 | 0 425 m 1 351 | 255 425 l 1 352 | 255 340 l 1 353 | 85 340 l 1 354 | 85 255 l 1 355 | 255 255 l 1 356 | 255 170 l 1 357 | 85 170 l 1 358 | 85 0 l 1 359 | 0 0 l 1 360 | 0 425 l 1 361 | EndSplineSet 362 | EndChar 363 | 364 | -------------------------------------------------------------------------------- /gen_tofu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import fontforge 4 | import progressbar 5 | import argparse 6 | import os 7 | import itertools 8 | import math 9 | import string 10 | 11 | FONT_LICENSE = open('FONT_LICENSE', 'r').read() 12 | TEMPLATE = open('template.sfd', 'r').read() 13 | 14 | def irange(start, end=None): 15 | if end is None: 16 | return range(start+1) 17 | else: 18 | return range(start, end+1) 19 | 20 | def ranges_mergeable(r1, r2): 21 | return r1[0] <= (r2[-1]+1) and r2[0] <= (r1[-1]+1) 22 | 23 | def range_combine(r1, r2): 24 | return irange(min(r1[0], r2[0]), max(r1[-1], r2[-1])) 25 | 26 | class range_formatter(string.Formatter): 27 | def format_field(self, value, spec): 28 | if isinstance(value, range): 29 | spec = spec.split(':', 1) 30 | value = '{0}{2:04X}{1}{0}{3:04X}'.format( 31 | spec[0], 32 | ''.join(spec[1:]) or '-', # empty separators are replaced by '-' 33 | value[0], value[-1] 34 | ) 35 | spec = '' 36 | return super(range_formatter, self).format_field(value, spec) 37 | 38 | def merge_ranges(ranges): 39 | found_overlap = True 40 | while found_overlap: 41 | found_overlap = False 42 | for i, j in itertools.permutations(range(len(ranges)), 2): 43 | if ranges_mergeable(ranges[i], ranges[j]): 44 | new_range = range_combine(ranges[i], ranges[j]) 45 | print(range_formatter().format( 46 | 'Notice: merged ranges {} and {} to {}', 47 | ranges[i], ranges[j], new_range 48 | )) 49 | ranges[i] = new_range 50 | del ranges[j] 51 | found_overlap = True 52 | break 53 | return ranges 54 | 55 | def parse_hex_range(s): 56 | ret = s.split('-') 57 | if len(ret) > 2: 58 | raise argparse.ArgumentTypeError('More than two hyphen separated values provided') 59 | 60 | start = None 61 | end = None 62 | try: 63 | start = int(ret[0], 16) 64 | end = int(ret[-1], 16) 65 | except ValueError: 66 | raise argparse.ArgumentTypeError( 67 | 'Failed to read {} value for range "{}"'.format('start' if start == None else 'end', s) 68 | ) 69 | 70 | if start > 0xFFFFFF or end > 0xFFFFFF: 71 | raise argparse.ArgumentTypeError('Range {} is out of bounds'.format(s)) 72 | 73 | if len(ret) == 2 and start == end: 74 | print('Warning: range "{}" has same start and end value ({})'.format(s, start)) 75 | 76 | if start > end: 77 | start, end = end, start 78 | print('Warning: range "{}" has been specified in reverse.'.format(s)) 79 | 80 | return irange(start, end) 81 | 82 | 83 | 84 | def format_codepoint(codepoint): 85 | hex_str = '{:X}'.format(codepoint) 86 | return hex_str.zfill(6 if len(hex_str) > 4 else 4) 87 | 88 | def align_point(i, count, item_size, spacing, total_size, reverse=False): 89 | if reverse: 90 | i = count - i - 1 91 | # remaining_space / 2 + current_item * size_of_one_item 92 | # remaining_space = total_size - space_taken_by_all_items 93 | return (total_size - (count * (item_size + spacing) - spacing)) / 2 + i * (item_size + spacing) 94 | 95 | class font_builder(object): 96 | def __init__(self, start): 97 | self.start = start 98 | self.id_font = 17 99 | self.data = [TEMPLATE] 100 | self.needs_save = False 101 | self.last_codepoint = None 102 | 103 | def save(self): 104 | start_str = format_codepoint(self.start) 105 | end_str = format_codepoint(self.last_codepoint) 106 | base_name = 'tofu_{}_{}'.format(start_str, end_str) 107 | save_name_tmp = '{}.sfd'.format(base_name) 108 | with open(save_name_tmp, 'w') as file: 109 | file.write('\n'.join(self.data)) 110 | 111 | font = fontforge.open(save_name_tmp) 112 | os.remove(save_name_tmp) 113 | font.familyname = 'Tofu' 114 | font.fontname = 'Tofu' 115 | font.fullname = 'Tofu {} - {}'.format(start_str, end_str) 116 | font.comment = 'The complete opposite of a font' 117 | font.version = '0.2' 118 | font.copyright = FONT_LICENSE 119 | 120 | self.needs_save = False 121 | return font 122 | 123 | def add_char(self, codepoint): 124 | hex_str = format_codepoint(codepoint) 125 | 126 | x_count = len(hex_str) // 2 127 | y_count = len(hex_str) // x_count 128 | 129 | # todo: figure out if trowing away translate matricies, making a template for 130 | # each of the 4 positions for each of the 16 glyphs actually saves space 131 | # (testing shows that not using any matrix can save ~8 bytes per character) 132 | references = ['Refer: 0 -1 N 1 0 0 1 0 0 2'] 133 | for i,c in enumerate(hex_str): 134 | references.append( 135 | 'Refer: {id_glyph} -1 N 1 0 0 1 {x} {y} 2'.format( 136 | id_glyph=int(c, 16) + 1, 137 | x=align_point(i % x_count, x_count, 255, 85, 1000), 138 | y=align_point(i // x_count, y_count, 425, 90, 1000, True) - 200 139 | ) 140 | ) 141 | 142 | self.data.append(( 143 | 'StartChar: uni{codepoint}\n' 144 | 'Encoding: {id_font} {id_uni} {id_glyph}\n' 145 | 'Width: 1000\n' 146 | 'Flags: HW\n' 147 | 'LayerCount: 2\n' 148 | 'Fore\n' 149 | '{references}\n' 150 | 'EndChar\n' 151 | ).format( 152 | codepoint=hex_str, 153 | id_font=self.id_font, 154 | id_uni=codepoint, 155 | id_glyph=self.id_font, 156 | references='\n'.join(references), 157 | ) 158 | ) 159 | 160 | self.id_font += 1 161 | self.last_codepoint = codepoint 162 | self.needs_save = True 163 | 164 | 165 | def main(): 166 | parser = argparse.ArgumentParser(description='Generate Tofu font.') 167 | 168 | parser.add_argument('-s', '--split', default=8192, type=int, nargs='?', 169 | help='how many characters to add to a font before creating a new one') 170 | 171 | parser.add_argument('ranges', metavar='range', type=parse_hex_range, nargs='*', 172 | help='hex ranges for chars to generate (inclusive) Ex. 0000-1000 1F00-2F00') 173 | 174 | parser.add_argument('--release', action='store_true', 175 | help='generate release fonts for plane 0 and 1') 176 | 177 | args = parser.parse_args() 178 | 179 | if args.release or len(args.ranges) <= 0: 180 | print('Generating release fonts (plane 0 and 1)') 181 | args.ranges = [irange(0, 0x1FFFF)] 182 | args.split = 8192 183 | 184 | if args.split <= 1024: 185 | args.split = parser.get_default('split') 186 | print('Warning: Specified split value too small, it has been reverted to the default ({})'.format(args.split)) 187 | 188 | ranges = merge_ranges(args.ranges) 189 | save_name = 'Tofu_{}.ttc'.format('_'.join([range_formatter().format('{::_}', r) for r in ranges])) 190 | char_count = sum([len(r) for r in ranges]) 191 | font_count = math.ceil(char_count / args.split) 192 | 193 | print('Generating Tofu for unicode characters between {} ({} chars, {} font(s))'.format( 194 | ', '.join([range_formatter().format('{:U+}', r) for r in ranges]), char_count, font_count 195 | )) 196 | 197 | 198 | # return 199 | progressbar.streams.wrap_stderr() 200 | bar = progressbar.ProgressBar(max_value=char_count) 201 | 202 | i = 0 203 | fonts = [] 204 | font = None 205 | for codepoint in itertools.chain.from_iterable(ranges): 206 | if i % args.split == 0: 207 | if font: 208 | fonts.append(font.save()) 209 | font = font_builder(codepoint) 210 | font.add_char(codepoint) 211 | 212 | i += 1 213 | bar.update(i) 214 | 215 | if font.needs_save: 216 | fonts.append(font.save()) 217 | 218 | bar.finish() 219 | 220 | print('saving as {}'.format(save_name), flush=True) 221 | try: 222 | fonts[0].generateTtc(save_name, fonts[1:], layer=1, 223 | flags=('short-post',), ttcflags=('merge',) 224 | ) 225 | except OSError: 226 | print('Error saving, filename is probably too long, saving as tofu.ttc', flush=True) 227 | fonts[0].generateTtc('tofu.ttc', fonts[1:], layer=1, 228 | flags=('short-post',), ttcflags=('merge',) 229 | ) 230 | 231 | 232 | if __name__ == '__main__': 233 | main() --------------------------------------------------------------------------------